WT 1.1.20(4) Training plan improvements
This commit is contained in:
parent
211307e63e
commit
48a0c3f7de
BIN
asset/image/drop_set.png
Executable file
BIN
asset/image/drop_set.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 179 KiB |
BIN
asset/image/pict_1rm.png
Normal file
BIN
asset/image/pict_1rm.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
BIN
asset/image/pict_history.png
Normal file
BIN
asset/image/pict_history.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 37 KiB |
33
i18n/en.json
33
i18n/en.json
@ -476,5 +476,36 @@
|
||||
"Want to update?": "Please update the app",
|
||||
"Attention!": "Attention!",
|
||||
"The safe and exact execution of this exercise you need a training buddy or a trainer": "The safe and exact execution of this exercise you need a training buddy or a trainer",
|
||||
"Execution at your own risk!": "Execution at your own risk!"
|
||||
"Execution at your own risk!": "Execution at your own risk!",
|
||||
"With the registration I accept the data policy and the terms of use.": "With the registration I accept the data policy and the terms of use.",
|
||||
"Terms Of Use": "Terms Of Use",
|
||||
"Execute at least 3 sets with maximum repeats, without resting time, with decreasing the weight.": "Execute at least 3 sets with maximum repeats, without resting time, with decreasing the weight.",
|
||||
"The goal is to completly exhaust your muscle without lifting a ridiculous weight end the end.": "The goal is to completly exhaust your muscle without lifting a ridiculous weight end the end.",
|
||||
"DROP": "DROP",
|
||||
"TESZT": "TEST",
|
||||
"Drop Set": "Drop Set",
|
||||
"Training Buddy": "Training Buddy",
|
||||
"I Execute My First Test Now": "I Execute My First Test Now",
|
||||
"Olympic Squat": "Olympic Squat",
|
||||
"Exercise description": "How to execute the the exercisein the right way?",
|
||||
"Training Summary": "Training Summary",
|
||||
"Duration": "Duration",
|
||||
"Total Lift": "Total Lift",
|
||||
"Maximum Repeats": "Maximum Repeats",
|
||||
"skipped": "skipped",
|
||||
"Strongly Growing": "Strongly Growing",
|
||||
"Growing": "Growing",
|
||||
"Sinking": "Sinking",
|
||||
"Strongly Sinking": "Strongly Sinking",
|
||||
"Stagnant": "Stagnant",
|
||||
"Details": "Details",
|
||||
"Start of the Exercise": "Start of the Exercise",
|
||||
"Your One Rep Max": "Your One Rep Max",
|
||||
"Your One Rep Max Ever": "Your One Rep Max Ever",
|
||||
"Total Lift Ever": "Total Lift Ever",
|
||||
"Your result is": "Your result is",
|
||||
"Register": "Sign Up",
|
||||
"Reach all basic functions, suggestions and": "Reach all basic functions, suggestions and",
|
||||
"optimized training plans, customized to your fitness state and strength:": "optimized training plans, customized to your fitness state and strength:",
|
||||
"Soon! Check back later for the plan details": "Soon! Check back later for the plan details"
|
||||
}
|
33
i18n/hu.json
33
i18n/hu.json
@ -474,5 +474,36 @@
|
||||
"Want to update?": "Kérlek töltsd le",
|
||||
"Attention!": "Figyelem!",
|
||||
"The safe and exact execution of this exercise you need a training buddy or a trainer": "A gyakorlat biztonságos és maximális pontosságú végrehajtásához edzőtárs vagy edző segítsége szükséges",
|
||||
"Execution at your own risk!": "Végrehajtás CSAK saját felelőségre!"
|
||||
"Execution at your own risk!": "Végrehajtás CSAK saját felelőségre!",
|
||||
"With the registration I accept the data policy and the terms of use.": "A regisztrációval elfogadom az adatkezelési szabályzatot és a felhasználási feltételeket.",
|
||||
"Terms Of Use": "Felh. feltételek",
|
||||
"Execute at least 3 sets with maximum repeats, without resting time, with decreasing the weight.": "Végezz legalább 3 sorozatot pihenés nélkül és maximális ismétlésszámmal úgy, hogy folyamatosan csökkented a súlyt a szettek között.",
|
||||
"The goal is to completly exhaust your muscle without lifting a ridiculous weight end the end.": "Cél, hogy a végén nevetségesen kis súlyt se bírj megmozdítani és elérd a maximális bedurranást.",
|
||||
"DROP": "VETKŐZÉS",
|
||||
"TESZT": "TESZT",
|
||||
"Drop Set": "Vetkőző sorozat",
|
||||
"Training Buddy": "Edzőtárs",
|
||||
"I Execute My First Test Now": "Végrehajtom az első tesztem, most",
|
||||
"Olympic Squat": "Guggolás",
|
||||
"Exercise descripton": "Hogyan hajtsd végre helyesen a gyakorlatot?",
|
||||
"Training Summary": "Edzés összefoglalása",
|
||||
"Duration": "Időtartam",
|
||||
"Total Lift": "Összes megmozgatott súly",
|
||||
"Maximum Repeats": "Maximum ismétlésszám",
|
||||
"skipped": "kihagyva",
|
||||
"Strongly Growing": "Erősen fejlődő",
|
||||
"Growing": "Fejlődő",
|
||||
"Sinking": "Csökkenő",
|
||||
"Strongly Sinking": "Erősen csökkenő",
|
||||
"Stagnant": "Stagnáló",
|
||||
"Details": "Részletek",
|
||||
"Start of the Exercise": "A gyakorlat kezdete",
|
||||
"Your One Rep Max": "Egyismétléses maximumod (1RM)",
|
||||
"Your One Rep Max Ever": "Valaha volt legjobb 1RM",
|
||||
"Total Lift Ever": "Valaha volt legtöbb megmozgatott súly",
|
||||
"Your result is": "Az eredményed",
|
||||
"Register": "Regisztráció",
|
||||
"Reach all basic functions, suggestions and": "Kattints a regisztrációra, hogy elérd az alap funkciókat, javaslatokat,",
|
||||
"optimized training plans, customized to your fitness state and strength:": "optiomalizált edzés terveket, amelyeket a te erő- és fitnesz állapododra szabunk:",
|
||||
"Soon! Check back later for the plan details": "Nemsokára! Nézz vissza később, amikor már aktiváltuk az edzésterv részleteit"
|
||||
}
|
@ -211,6 +211,8 @@ PODS:
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- Sentry (~> 7.0.3)
|
||||
- share (0.0.1):
|
||||
- Flutter
|
||||
- shared_preferences (0.0.1):
|
||||
- Flutter
|
||||
- sign_in_with_apple (0.0.1):
|
||||
@ -250,6 +252,7 @@ DEPENDENCIES:
|
||||
- path_provider (from `.symlinks/plugins/path_provider/ios`)
|
||||
- purchases_flutter (from `.symlinks/plugins/purchases_flutter/ios`)
|
||||
- sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`)
|
||||
- share (from `.symlinks/plugins/share/ios`)
|
||||
- shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
|
||||
- sign_in_with_apple (from `.symlinks/plugins/sign_in_with_apple/ios`)
|
||||
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
||||
@ -331,6 +334,8 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/purchases_flutter/ios"
|
||||
sentry_flutter:
|
||||
:path: ".symlinks/plugins/sentry_flutter/ios"
|
||||
share:
|
||||
:path: ".symlinks/plugins/share/ios"
|
||||
shared_preferences:
|
||||
:path: ".symlinks/plugins/shared_preferences/ios"
|
||||
sign_in_with_apple:
|
||||
@ -394,6 +399,7 @@ SPEC CHECKSUMS:
|
||||
PurchasesHybridCommon: d65a799a61d688588534b80338edbcbf604ca93d
|
||||
Sentry: 5b16f877da362d23716d827e04db642455b26b40
|
||||
sentry_flutter: 602dc1902e152269256115e2386e1029511f3440
|
||||
share: 0b2c3e82132f5888bccca3351c504d0003b3b410
|
||||
shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d
|
||||
sign_in_with_apple: 34f3f5456a45fd7ac5fb42905e2ad31dae061b4a
|
||||
sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
|
||||
|
@ -388,7 +388,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_TEAM = SFJJBDCU6Z;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
@ -531,7 +531,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_TEAM = SFJJBDCU6Z;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
@ -566,7 +566,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_TEAM = SFJJBDCU6Z;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
|
@ -36,11 +36,11 @@ class ExerciseNewBloc extends Bloc<ExerciseNewEvent, ExerciseNewState> with Logg
|
||||
double bmiAngle = 0;
|
||||
double bmiTop = 0;
|
||||
double bmiLeft = 0;
|
||||
late double weight;
|
||||
late double height;
|
||||
double? weight;
|
||||
double? height;
|
||||
double bmrEnergy = 0;
|
||||
late int birthYear;
|
||||
late String fitnessLevel;
|
||||
int? birthYear;
|
||||
String? fitnessLevel;
|
||||
bool changedWeight = false;
|
||||
bool changedSizes = false;
|
||||
|
||||
@ -155,6 +155,12 @@ class ExerciseNewBloc extends Bloc<ExerciseNewEvent, ExerciseNewState> with Logg
|
||||
Cache().initBadges();
|
||||
Track().track(TrackingEvent.exercise_new, eventValue: exerciseRepository.exerciseType!.name);
|
||||
yield ExerciseNewSaved();
|
||||
} else if (event is ExerciseNewSubmitNoRegistration) {
|
||||
yield ExerciseNewLoading();
|
||||
exerciseRepository.addExerciseNoRegistration();
|
||||
menuBloc.add(MenuTreeDown(parent: 0));
|
||||
Track().track(TrackingEvent.exercise_new_no_registration, eventValue: exerciseRepository.exerciseType!.name);
|
||||
yield ExerciseNewSaved();
|
||||
} else if (event is ExerciseNewBMIAnimate) {
|
||||
yield ExerciseNewLoading();
|
||||
yield ExerciseNewReady();
|
||||
@ -168,18 +174,18 @@ class ExerciseNewBloc extends Bloc<ExerciseNewEvent, ExerciseNewState> with Logg
|
||||
}
|
||||
|
||||
double getBMI() {
|
||||
if (height == 0 || weight == 0) {
|
||||
if (weight == null || height == null || height == 0 || weight == 0) {
|
||||
this.bmi = 0;
|
||||
return 0;
|
||||
}
|
||||
this.bmi = weight / (height * height / 10000);
|
||||
this.bmi = weight! / (height! * height! / 10000);
|
||||
this.getGoalBMI();
|
||||
return this.bmi;
|
||||
}
|
||||
|
||||
double getBMR() {
|
||||
var date = DateTime.now();
|
||||
if (height == 0 || weight == 0) {
|
||||
if (weight == null || height == null || height == 0 || weight == 0) {
|
||||
this.bmi = 0;
|
||||
return 0;
|
||||
}
|
||||
@ -188,10 +194,10 @@ class ExerciseNewBloc extends Bloc<ExerciseNewEvent, ExerciseNewState> with Logg
|
||||
|
||||
if (customerRepository.customer!.sex == "m") {
|
||||
//66.47 + ( 13.75 × tömeg kg-ban ) + ( 5.003 × magasság cm-ben ) − ( 6.755 × életkor évben kifejezve )
|
||||
bmr = 66.47 + (13.75 * weight) + (5.003 * height) - (6.755 * (year - customerRepository.customer!.birthYear!));
|
||||
bmr = 66.47 + (13.75 * weight!) + (5.003 * height!) - (6.755 * (year - customerRepository.customer!.birthYear!));
|
||||
} else {
|
||||
//BMR = 655.1 + ( 9.563 × ömeg kg-ban ) + ( 1.85 × magasság cm-ben) − ( 4.676 × életkor évben kifejezve )
|
||||
bmr = 655.1 + (9.563 * weight) + (1.85 * height) - (4.676 * (year - customerRepository.customer!.birthYear!));
|
||||
bmr = 655.1 + (9.563 * weight!) + (1.85 * height!) - (4.676 * (year - customerRepository.customer!.birthYear!));
|
||||
}
|
||||
bmrEnergy = bmr;
|
||||
|
||||
@ -208,6 +214,9 @@ class ExerciseNewBloc extends Bloc<ExerciseNewEvent, ExerciseNewState> with Logg
|
||||
}
|
||||
|
||||
double getGoalBMI() {
|
||||
if (weight == null || height == null) {
|
||||
return 0;
|
||||
}
|
||||
if (this.bmi == 0) {
|
||||
getBMI();
|
||||
}
|
||||
@ -226,8 +235,8 @@ class ExerciseNewBloc extends Bloc<ExerciseNewEvent, ExerciseNewState> with Logg
|
||||
}
|
||||
|
||||
goalBMI = 24;
|
||||
this.goalWeight = goalBMI * (height * height / 10000);
|
||||
this.goalMilestoneWeight = goalMilestoneBMI * (height * height / 10000);
|
||||
this.goalWeight = goalBMI * (height! * height! / 10000);
|
||||
this.goalMilestoneWeight = goalMilestoneBMI * (height! * height! / 10000);
|
||||
|
||||
//print("Angle: " + bmiAngle.toStringAsFixed(1));
|
||||
|
||||
|
@ -83,6 +83,10 @@ class ExerciseNewSubmit extends ExerciseNewEvent {
|
||||
const ExerciseNewSubmit();
|
||||
}
|
||||
|
||||
class ExerciseNewSubmitNoRegistration extends ExerciseNewEvent {
|
||||
const ExerciseNewSubmitNoRegistration();
|
||||
}
|
||||
|
||||
class ExerciseNewAddError extends ExerciseNewEvent {
|
||||
final String message;
|
||||
const ExerciseNewAddError({required this.message});
|
||||
|
@ -29,6 +29,7 @@ class SalesBloc extends Bloc<SalesEvent, SalesState> with Logging {
|
||||
|
||||
String? salesText;
|
||||
String? premiumFunctions = "";
|
||||
String? trial = "";
|
||||
|
||||
Future<void> init() async {
|
||||
if (Cache().userLoggedIn == null) {
|
||||
@ -47,6 +48,11 @@ class SalesBloc extends Bloc<SalesEvent, SalesState> with Logging {
|
||||
premiumFunctions = "";
|
||||
}
|
||||
|
||||
trial = descriptionRepository.getDescriptionByName("trial");
|
||||
if (trial == null || trial!.isEmpty) {
|
||||
trial = "";
|
||||
}
|
||||
|
||||
await RevenueCatPurchases().getOfferings();
|
||||
this.getProductSet();
|
||||
Track().track(TrackingEvent.sales_page);
|
||||
|
274
lib/bloc/training_evaluation/training_evaluation_bloc.dart
Normal file
274
lib/bloc/training_evaluation/training_evaluation_bloc.dart
Normal file
@ -0,0 +1,274 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:aitrainer_app/bloc/training_plan/training_plan_bloc.dart';
|
||||
import 'package:aitrainer_app/model/cache.dart';
|
||||
import 'package:aitrainer_app/model/customer_training_plan_details.dart';
|
||||
import 'package:aitrainer_app/model/exercise_plan_detail.dart';
|
||||
import 'package:aitrainer_app/model/training_evaluation_exercise.dart';
|
||||
import 'package:aitrainer_app/util/common.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
part 'training_evaluation_event.dart';
|
||||
part 'training_evaluation_state.dart';
|
||||
|
||||
class TrainingEvaluationBloc extends Bloc<TrainingEvaluationEvent, TrainingEvaluationState> {
|
||||
final TrainingPlanBloc trainingPlanBloc;
|
||||
final String day;
|
||||
TrainingEvaluationBloc({required this.trainingPlanBloc, required this.day}) : super(TrainingEvaluationInitial());
|
||||
|
||||
String duration = "-";
|
||||
String totalLift = "0";
|
||||
String maxTotalLift = "0";
|
||||
String maxRepeats = "0";
|
||||
DateTime? end;
|
||||
|
||||
List<TrainingEvaluationExercise> evaluationList = [];
|
||||
|
||||
@override
|
||||
Stream<TrainingEvaluationState> mapEventToState(
|
||||
TrainingEvaluationEvent event,
|
||||
) async* {
|
||||
try {
|
||||
if (event is TrainingEvaluationLoad) {
|
||||
yield TrainingEvaluationLoading();
|
||||
await saveResult();
|
||||
getDuration();
|
||||
getTotalLift();
|
||||
getMaxRepeats();
|
||||
createEvaluationData();
|
||||
//getMaxTotalLift();
|
||||
if (end == null || DateTime.now().difference(end!).inMinutes > 5) {
|
||||
yield TrainingEvaluationReady();
|
||||
} else {
|
||||
yield TrainingEvaluationVictoryReady();
|
||||
}
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
yield TrainingEvaluationError(message: e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> saveResult() async {}
|
||||
|
||||
void createEvaluationData() {
|
||||
if (trainingPlanBloc.getMyPlan() == null || trainingPlanBloc.getMyPlan()!.days[day] == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
trainingPlanBloc.getMyPlan()!.days[day]!.forEach((element) {
|
||||
if (!element.state.equalsTo(ExercisePlanDetailState.extra)) {
|
||||
if (!findExerciseInEvaluationList(element.exerciseTypeId!)) {
|
||||
addEvaluationExercise(element);
|
||||
} else {
|
||||
//editEvaluationExercise(element);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool findExerciseInEvaluationList(int exerciseTypeId) {
|
||||
bool found = false;
|
||||
for (var exercise in evaluationList) {
|
||||
if (exercise.exerciseTypeId == exerciseTypeId) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
void addEvaluationExercise(CustomerTrainingPlanDetails detail) {
|
||||
TrainingEvaluationExercise exercise = TrainingEvaluationExercise();
|
||||
exercise.exerciseTypeId = detail.exerciseTypeId!;
|
||||
exercise.name = detail.exerciseType!.nameTranslation;
|
||||
exercise.state = detail.state;
|
||||
|
||||
if (detail.state.equalsTo(ExercisePlanDetailState.skipped)) {
|
||||
TrainingEvaluationExercise exercise = TrainingEvaluationExercise();
|
||||
exercise.exerciseTypeId = detail.exerciseTypeId!;
|
||||
exercise.name = detail.exerciseType!.nameTranslation;
|
||||
exercise.state = ExercisePlanDetailState.skipped;
|
||||
exercise.type = TrainingEvaluationExerciseType.weightBased;
|
||||
this.evaluationList.add(exercise);
|
||||
} else {
|
||||
if (detail.exerciseType!.unitQuantityUnit == null) {
|
||||
exercise.type = TrainingEvaluationExerciseType.repeatBased;
|
||||
exercise.repeats = detail.repeats;
|
||||
exercise.maxRepeats = getMaxRepeatsByExerciseType(detail.exerciseTypeId!);
|
||||
exercise.trend = getTrendEvaluationRepeats(exercise);
|
||||
} else {
|
||||
exercise.type = TrainingEvaluationExerciseType.weightBased;
|
||||
exercise.oneRepMax = Common.calculate1RM(detail.weight!, detail.repeats!.toDouble());
|
||||
exercise.max1RM = getMax1RMByExerciseType(detail.exerciseTypeId!);
|
||||
exercise.totalLift = (detail.weight! * detail.repeats!).round();
|
||||
exercise.maxTotalLift = getMaxTotalLiftByExerciseType(detail.exerciseTypeId!).round();
|
||||
exercise.trend = this.getTrendWeight(exercise.oneRepMax!, exercise.max1RM, exercise.totalLift, exercise.maxTotalLift);
|
||||
}
|
||||
exercise.trendText = getTrendText(exercise.trend!);
|
||||
exercise.trendColor = getTrendColor(exercise.trend!);
|
||||
this.evaluationList.add(exercise);
|
||||
}
|
||||
}
|
||||
|
||||
double getTrendEvaluationRepeats(TrainingEvaluationExercise exercise) {
|
||||
double rate = exercise.repeats! / exercise.maxRepeats!;
|
||||
return rate;
|
||||
}
|
||||
|
||||
String getTrendText(double rate) {
|
||||
if (rate > 1.10) {
|
||||
return "Strongly Growing";
|
||||
} else if (rate > 1.05) {
|
||||
return "Growing";
|
||||
} else if (rate < 0.90) {
|
||||
return "Strongly Sinking";
|
||||
} else if (rate < 0.95) {
|
||||
return "Sinking";
|
||||
} else {
|
||||
return "Stagnant";
|
||||
}
|
||||
}
|
||||
|
||||
Color getTrendColor(double rate) {
|
||||
if (rate > 1.10) {
|
||||
return Colors.green[900]!;
|
||||
} else if (rate > 1.05) {
|
||||
return Colors.green[300]!;
|
||||
} else if (rate < 0.90) {
|
||||
return Colors.red[900]!;
|
||||
} else if (rate < 0.95) {
|
||||
return Colors.green[400]!;
|
||||
} else {
|
||||
return Colors.white;
|
||||
}
|
||||
}
|
||||
|
||||
void getDuration() {
|
||||
if (trainingPlanBloc.getMyPlan() == null || trainingPlanBloc.getMyPlan()!.days[day] == null) {
|
||||
return;
|
||||
}
|
||||
int index = 0;
|
||||
DateTime? start;
|
||||
|
||||
trainingPlanBloc.getMyPlan()!.days[day]!.forEach((element) {
|
||||
if (element.state.equalsTo(ExercisePlanDetailState.finished)) {
|
||||
if (index++ == 0) {
|
||||
start = element.exercises[0].dateAdd!;
|
||||
} else {
|
||||
if (element.exercises.length > 0) {
|
||||
this.end = element.exercises[element.exercises.length - 1].dateAdd!;
|
||||
}
|
||||
}
|
||||
//print("Exercise Date ${element.exercises[0].dateAdd!}");
|
||||
}
|
||||
});
|
||||
if (start == null) {
|
||||
this.duration = "00:00:00";
|
||||
} else if (this.end == null) {
|
||||
this.duration = "00:01:00";
|
||||
this.end = start;
|
||||
} else {
|
||||
this.duration = end!.difference(start!).inMinutes.toString();
|
||||
}
|
||||
}
|
||||
|
||||
void getTotalLift() {
|
||||
if (trainingPlanBloc.getMyPlan() == null || trainingPlanBloc.getMyPlan()!.days[day] == null) {
|
||||
return;
|
||||
}
|
||||
double total = 0;
|
||||
trainingPlanBloc.getMyPlan()!.days[day]!.forEach((element) {
|
||||
if (element.state.equalsTo(ExercisePlanDetailState.finished) && element.exerciseType!.unitQuantityUnit != null) {
|
||||
total += element.weight! * element.repeats!;
|
||||
}
|
||||
});
|
||||
totalLift = total.toStringAsFixed(0);
|
||||
}
|
||||
|
||||
void getMaxRepeats() {
|
||||
if (trainingPlanBloc.getMyPlan() == null || trainingPlanBloc.getMyPlan()!.days[day] == null) {
|
||||
return;
|
||||
}
|
||||
int max = 0;
|
||||
trainingPlanBloc.getMyPlan()!.days[day]!.forEach((element) {
|
||||
if (element.state.equalsTo(ExercisePlanDetailState.finished) && element.exerciseType!.unit != "second") {
|
||||
print("Repeats ${element.repeats}");
|
||||
if (max < element.repeats!) {
|
||||
max = element.repeats!;
|
||||
}
|
||||
}
|
||||
});
|
||||
maxRepeats = max.toStringAsFixed(0);
|
||||
}
|
||||
|
||||
double getMaxTotalLiftByExerciseType(int exerciseTypeId) {
|
||||
if (Cache().getExercises() == null || Cache().getExercises()!.length == 0) {
|
||||
return 0;
|
||||
}
|
||||
double maxTotal = 0;
|
||||
Cache().getExercises()!.forEach((element) {
|
||||
if (element.exerciseTypeId == exerciseTypeId) {
|
||||
final double total = element.quantity! * element.unitQuantity!;
|
||||
if (maxTotal < total) {
|
||||
maxTotal = total;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return maxTotal;
|
||||
}
|
||||
|
||||
double getMax1RMByExerciseType(int exerciseTypeId) {
|
||||
if (Cache().getExercises() == null || Cache().getExercises()!.length == 0) {
|
||||
return 0;
|
||||
}
|
||||
double max1RM = 0;
|
||||
Cache().getExercises()!.forEach((element) {
|
||||
if (element.exerciseTypeId == exerciseTypeId) {
|
||||
final double oneRepMax = Common.calculate1RM(element.unitQuantity!, element.quantity!);
|
||||
if (max1RM < oneRepMax) {
|
||||
max1RM = oneRepMax;
|
||||
}
|
||||
}
|
||||
});
|
||||
return max1RM;
|
||||
}
|
||||
|
||||
int getMaxRepeatsByExerciseType(int exerciseTypeId) {
|
||||
if (Cache().getExercises() == null || Cache().getExercises()!.length == 0) {
|
||||
return 0;
|
||||
}
|
||||
int maxRepeats = 0;
|
||||
Cache().getExercises()!.forEach((element) {
|
||||
if (element.exerciseTypeId == exerciseTypeId) {
|
||||
final int repeats = element.quantity!.toInt();
|
||||
if (maxRepeats < repeats) {
|
||||
maxRepeats = repeats;
|
||||
}
|
||||
}
|
||||
});
|
||||
return maxRepeats;
|
||||
}
|
||||
|
||||
double getOneRepMax(CustomerTrainingPlanDetails detail) {
|
||||
if (detail.weight == null) {
|
||||
return 0;
|
||||
}
|
||||
return Common.calculate1RM(detail.weight!, detail.repeats!.toDouble());
|
||||
}
|
||||
|
||||
double getTotalLiftExercise(CustomerTrainingPlanDetails detail) {
|
||||
if (detail.weight == null) {
|
||||
return 0;
|
||||
}
|
||||
return detail.weight! * detail.repeats!;
|
||||
}
|
||||
|
||||
double getTrendWeight(double oneRepMax, max1RM, totalLift, maxTotalLift) {
|
||||
double oneRepMaxRate = oneRepMax / max1RM;
|
||||
double totalLiftRate = totalLift / maxTotalLift;
|
||||
return (oneRepMaxRate + totalLiftRate) / 2;
|
||||
}
|
||||
}
|
12
lib/bloc/training_evaluation/training_evaluation_event.dart
Normal file
12
lib/bloc/training_evaluation/training_evaluation_event.dart
Normal file
@ -0,0 +1,12 @@
|
||||
part of 'training_evaluation_bloc.dart';
|
||||
|
||||
abstract class TrainingEvaluationEvent extends Equatable {
|
||||
const TrainingEvaluationEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class TrainingEvaluationLoad extends TrainingEvaluationEvent {
|
||||
const TrainingEvaluationLoad();
|
||||
}
|
32
lib/bloc/training_evaluation/training_evaluation_state.dart
Normal file
32
lib/bloc/training_evaluation/training_evaluation_state.dart
Normal file
@ -0,0 +1,32 @@
|
||||
part of 'training_evaluation_bloc.dart';
|
||||
|
||||
abstract class TrainingEvaluationState extends Equatable {
|
||||
const TrainingEvaluationState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class TrainingEvaluationInitial extends TrainingEvaluationState {
|
||||
const TrainingEvaluationInitial();
|
||||
}
|
||||
|
||||
class TrainingEvaluationLoading extends TrainingEvaluationState {
|
||||
const TrainingEvaluationLoading();
|
||||
}
|
||||
|
||||
class TrainingEvaluationReady extends TrainingEvaluationState {
|
||||
const TrainingEvaluationReady();
|
||||
}
|
||||
|
||||
class TrainingEvaluationVictoryReady extends TrainingEvaluationState {
|
||||
const TrainingEvaluationVictoryReady();
|
||||
}
|
||||
|
||||
class TrainingEvaluationError extends TrainingEvaluationState {
|
||||
final String message;
|
||||
const TrainingEvaluationError({required this.message});
|
||||
|
||||
@override
|
||||
List<Object> get props => [message];
|
||||
}
|
@ -11,7 +11,9 @@ import 'package:aitrainer_app/model/workout_menu_tree.dart';
|
||||
import 'package:aitrainer_app/repository/training_plan_repository.dart';
|
||||
import 'package:aitrainer_app/service/exercise_service.dart';
|
||||
import 'package:aitrainer_app/util/app_language.dart';
|
||||
import 'package:aitrainer_app/util/common.dart';
|
||||
import 'package:aitrainer_app/util/enums.dart';
|
||||
import 'package:aitrainer_app/util/track.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -56,12 +58,22 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
|
||||
this.activateDays();
|
||||
Cache().myTrainingPlan = _myPlan;
|
||||
await Cache().saveMyTrainingPlan();
|
||||
Track().track(TrackingEvent.training_plan_start, eventValue: event.trainingPlanId.toString());
|
||||
yield TrainingPlanFinished();
|
||||
} else if (event is TrainingPlanWeightChange) {
|
||||
yield TrainingPlanExerciseLoading();
|
||||
event.detail.weight = event.weight;
|
||||
|
||||
yield TrainingPlanExerciseReady();
|
||||
yield TrainingPlanReady();
|
||||
} else if (event is TrainingPlanWeightChangeRecalculate) {
|
||||
yield TrainingPlanExerciseLoading();
|
||||
|
||||
event.detail.repeats =
|
||||
Common.reCalculateRepeatsByChangedWeight(event.detail.weight!, event.detail.repeats!.toDouble(), event.weight);
|
||||
event.detail.weight = event.weight;
|
||||
print(" weight: ${event.weight} new repeats: ${event.detail.repeats}");
|
||||
|
||||
yield TrainingPlanReady();
|
||||
} else if (event is TrainingPlanRepeatsChange) {
|
||||
yield TrainingPlanExerciseLoading();
|
||||
@ -77,38 +89,51 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
|
||||
yield TrainingPlanReady();
|
||||
} else if (event is TrainingPlanSaveExercise) {
|
||||
yield TrainingPlanLoading();
|
||||
|
||||
event.detail.state = ExercisePlanDetailState.inProgress;
|
||||
final Exercise exercise = Exercise();
|
||||
exercise.customerId = Cache().userLoggedIn!.customerId!;
|
||||
exercise.exerciseTypeId = event.detail.exerciseTypeId;
|
||||
exercise.quantity = event.detail.repeats!.toDouble();
|
||||
exercise.unit = event.detail.exerciseType!.unit;
|
||||
exercise.unitQuantity = event.detail.weight;
|
||||
exercise.dateAdd = DateTime.now();
|
||||
event.detail.exercises.add(exercise);
|
||||
if (event.detail.exercises.length >= event.detail.set!) {
|
||||
if (event.detail.weight == -3) {
|
||||
print("DropSet");
|
||||
event.detail.state = ExercisePlanDetailState.finished;
|
||||
} else if (event.detail.exercises.length >= 0) {
|
||||
event.detail.state = ExercisePlanDetailState.inProgress;
|
||||
}
|
||||
// recalculate the weight to the original planned repeats for the next details
|
||||
if (exercise.unitQuantity != null && exercise.unitQuantity! > 0) {
|
||||
for (var nextDetail in _myPlan!.details) {
|
||||
if (nextDetail.exerciseTypeId == event.detail.exerciseTypeId && nextDetail.weight == -2) {
|
||||
trainingPlanRepository.recalculateDetail(_myPlan!.trainingPlanId!, event.detail, nextDetail);
|
||||
yield TrainingPlanReady();
|
||||
} else {
|
||||
final Exercise exercise = Exercise();
|
||||
exercise.customerId = Cache().userLoggedIn!.customerId!;
|
||||
exercise.exerciseTypeId = event.detail.exerciseTypeId;
|
||||
exercise.quantity = event.detail.repeats!.toDouble();
|
||||
exercise.unit = event.detail.exerciseType!.unit;
|
||||
exercise.unitQuantity = event.detail.weight;
|
||||
exercise.dateAdd = DateTime.now();
|
||||
event.detail.exercises.add(exercise);
|
||||
if (event.detail.exercises.length >= event.detail.set!) {
|
||||
event.detail.state = ExercisePlanDetailState.finished;
|
||||
} else if (event.detail.exercises.length >= 0) {
|
||||
event.detail.state = ExercisePlanDetailState.inProgress;
|
||||
}
|
||||
// recalculate the weight to the original planned repeats for the next details
|
||||
|
||||
if (exercise.unitQuantity != null && exercise.unitQuantity! > 0) {
|
||||
for (var nextDetail in _myPlan!.details) {
|
||||
double weightFromPlan = trainingPlanRepository.getOriginalWeight(this.getMyPlan()!.trainingPlanId!, nextDetail);
|
||||
if (nextDetail.exerciseTypeId == event.detail.exerciseTypeId &&
|
||||
nextDetail.weight == -2 &&
|
||||
nextDetail.customerTrainingPlanDetailsId != event.detail.customerTrainingPlanDetailsId) {
|
||||
print("recalculating -2 ${event.detail.customerTrainingPlanDetailsId}");
|
||||
trainingPlanRepository.recalculateDetail(_myPlan!.trainingPlanId!, event.detail, nextDetail);
|
||||
} else if (nextDetail.exerciseTypeId == event.detail.exerciseTypeId && weightFromPlan == -1 && nextDetail.set! > 1) {
|
||||
print("recalculating -1 ${event.detail.customerTrainingPlanDetailsId}");
|
||||
nextDetail = trainingPlanRepository.recalculateDetailFixRepeats(_myPlan!.trainingPlanId!, nextDetail);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exercise.trainingPlanDetailsId = _myPlan!.trainingPlanId;
|
||||
|
||||
// save Exercise
|
||||
Exercise savedExercise = await ExerciseApi().addExercise(exercise);
|
||||
Cache().addExercise(savedExercise);
|
||||
|
||||
Cache().myTrainingPlan = _myPlan;
|
||||
await Cache().saveMyTrainingPlan();
|
||||
}
|
||||
|
||||
exercise.trainingPlanDetailsId = _myPlan!.trainingPlanId;
|
||||
|
||||
// save Exercise
|
||||
Exercise savedExercise = await ExerciseApi().addExercise(exercise);
|
||||
Cache().addExercise(savedExercise);
|
||||
|
||||
Cache().myTrainingPlan = _myPlan;
|
||||
await Cache().saveMyTrainingPlan();
|
||||
Track().track(TrackingEvent.training_plan_execute, eventValue: event.detail.exerciseType!.name);
|
||||
|
||||
if (isDayDone()) {
|
||||
this.add(TrainingPlanFinishDay());
|
||||
@ -128,6 +153,7 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
|
||||
} else if (event is TrainingPlanFinishDay) {
|
||||
yield TrainingPlanLoading();
|
||||
celebrating = true;
|
||||
Track().track(TrackingEvent.training_plan_finished);
|
||||
yield TrainingPlanDayFinished();
|
||||
} else if (event is TrainingPlanGoToRestart) {
|
||||
yield TrainingPlanLoading();
|
||||
@ -178,6 +204,7 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
|
||||
} else {
|
||||
_myDetail!.weight = 0;
|
||||
}
|
||||
Track().track(TrackingEvent.training_plan_custom, eventValue: event.exerciseType.name);
|
||||
yield TrainingPlanReady();
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
@ -186,6 +213,9 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
|
||||
}
|
||||
|
||||
void addNewPlan() {
|
||||
if (Cache().userLoggedIn == null) {
|
||||
throw Exception("Please log in");
|
||||
}
|
||||
if (_myPlan == null) {
|
||||
_myPlan = CustomerTrainingPlan();
|
||||
} else {
|
||||
@ -254,6 +284,7 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
|
||||
}
|
||||
_myPlan!.days[dayName]!.add(element);
|
||||
});
|
||||
this.addExtraExerciseType("Stretching", previousDay);
|
||||
|
||||
if (dayNames.length == 0) {
|
||||
dayName = "";
|
||||
@ -280,6 +311,7 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
|
||||
detail.parallel = false;
|
||||
detail.restingTime = 0;
|
||||
detail.exerciseType = exerciseType;
|
||||
detail.state = ExercisePlanDetailState.extra;
|
||||
if (_myPlan!.days[dayName] == null) {
|
||||
_myPlan!.days[dayName] = [];
|
||||
}
|
||||
@ -372,7 +404,10 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
|
||||
bool isDone = true;
|
||||
final String day = dayNames[activeDayIndex];
|
||||
for (var detail in _myPlan!.days[day]!) {
|
||||
if (!detail.state.equalsTo(ExercisePlanDetailState.finished) && !detail.state.equalsTo(ExercisePlanDetailState.skipped)) {
|
||||
//print("daydone ${detail.state}");
|
||||
if (!detail.state.equalsTo(ExercisePlanDetailState.extra) &&
|
||||
!detail.state.equalsTo(ExercisePlanDetailState.finished) &&
|
||||
!detail.state.equalsTo(ExercisePlanDetailState.skipped)) {
|
||||
isDone = false;
|
||||
}
|
||||
}
|
||||
@ -400,6 +435,7 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
|
||||
}
|
||||
int index = indexInStart > indexInProgress ? indexInStart : indexInProgress;
|
||||
offset = index * 80;
|
||||
print("Offset: $offset day: $day ($indexInStart, $indexInProgress)");
|
||||
return offset;
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,15 @@ class TrainingPlanWeightChange extends TrainingPlanEvent {
|
||||
List<Object> get props => [weight, detail];
|
||||
}
|
||||
|
||||
class TrainingPlanWeightChangeRecalculate extends TrainingPlanEvent {
|
||||
final CustomerTrainingPlanDetails detail;
|
||||
final double weight;
|
||||
const TrainingPlanWeightChangeRecalculate({required this.weight, required this.detail});
|
||||
|
||||
@override
|
||||
List<Object> get props => [weight, detail];
|
||||
}
|
||||
|
||||
class TrainingPlanRepeatsChange extends TrainingPlanEvent {
|
||||
final CustomerTrainingPlanDetails detail;
|
||||
final int repeats;
|
||||
|
@ -13,10 +13,10 @@ typedef Future<List<T>> DropdownSearchOnFind<T>(String text);
|
||||
typedef String DropdownSearchItemAsString<T>(T item);
|
||||
typedef bool DropdownSearchFilterFn<T>(T item, String filter);
|
||||
typedef bool DropdownSearchCompareFn<T>(T item, T selectedItem);
|
||||
typedef Widget DropdownSearchBuilder<T>(BuildContext context, T selectedItem, String itemAsString);
|
||||
typedef Widget DropdownSearchBuilder<T>(BuildContext context, T? selectedItem, String itemAsString);
|
||||
typedef Widget DropdownSearchPopupItemBuilder<T>(
|
||||
BuildContext context,
|
||||
T item,
|
||||
T? item,
|
||||
bool isSelected,
|
||||
);
|
||||
typedef bool DropdownSearchPopupItemEnabled<T>(T item);
|
||||
@ -59,7 +59,7 @@ class DropdownSearch<T> extends StatefulWidget {
|
||||
final DropdownSearchOnFind<T>? onFind;
|
||||
|
||||
///called when a new item is selected
|
||||
final ValueChanged<T>? onChanged;
|
||||
final ValueChanged<T?>? onChanged;
|
||||
|
||||
///to customize list of items UI
|
||||
final DropdownSearchBuilder<T>? dropdownBuilder;
|
||||
@ -77,7 +77,7 @@ class DropdownSearch<T> extends StatefulWidget {
|
||||
final Widget? popupTitle;
|
||||
|
||||
///customize the fields the be shown
|
||||
final DropdownSearchItemAsString<T>? itemAsString;
|
||||
final DropdownSearchItemAsString<T?>? itemAsString;
|
||||
|
||||
/// custom filter function
|
||||
final DropdownSearchFilterFn<T>? filterFn;
|
||||
|
@ -63,6 +63,7 @@ import 'bloc/session/session_bloc.dart';
|
||||
import 'bloc/settings/settings_bloc.dart';
|
||||
import 'bloc/timer/timer_bloc.dart';
|
||||
import 'model/cache.dart';
|
||||
import 'view/training_evaluation_page.dart';
|
||||
|
||||
const dsn = 'https://0f635b7225564abc9089f8106f25eb5c@sentry.aitrainer.app/1';
|
||||
|
||||
@ -109,7 +110,6 @@ Future<Null> main() async {
|
||||
if (isInDebugMode) {
|
||||
// In development mode simply print to console.
|
||||
FlutterError.dumpErrorToConsole(details);
|
||||
FlurryData.logEvent("enter_test");
|
||||
} else {
|
||||
// In production mode report to the application zone to report to
|
||||
// Sentry.
|
||||
@ -147,6 +147,7 @@ Future<Null> main() async {
|
||||
|
||||
print(" -- FireBase init..");
|
||||
await FirebaseApi().initializeFlutterFire();
|
||||
FlurryData.logEvent("enter_test");
|
||||
|
||||
runApp(MultiBlocProvider(
|
||||
providers: [
|
||||
@ -177,7 +178,8 @@ Future<Null> main() async {
|
||||
BlocProvider<TestSetExecuteBloc>(
|
||||
create: (BuildContext context) => TestSetExecuteBloc(),
|
||||
),
|
||||
BlocProvider<TutorialBloc>(create: (BuildContext context) => TutorialBloc(tutorialName: ActivityDone.tutorialBasic.toStr())),
|
||||
BlocProvider<TutorialBloc>(
|
||||
create: (BuildContext context) => TutorialBloc(tutorialName: ActivityDone.tutorialExecuteFirstTest.toStr())),
|
||||
BlocProvider<TrainingPlanBloc>(create: (context) {
|
||||
final MenuBloc menuBloc = BlocProvider.of<MenuBloc>(context);
|
||||
return TrainingPlanBloc(menuBloc: menuBloc, trainingPlanRepository: TrainingPlanRepository());
|
||||
@ -276,6 +278,7 @@ class WorkoutTestApp extends StatelessWidget {
|
||||
'myTrainingPlanActivate': (context) => TrainingPlanActivatePage(),
|
||||
'myTrainingPlanExecute': (context) => TrainingPlanExecutePage(),
|
||||
'myTrainingPlanExercise': (context) => TrainingPlanExercise(),
|
||||
'myTrainingEvaluation': (context) => TrainingEvaluationPage(),
|
||||
},
|
||||
initialRoute: 'home',
|
||||
title: 'WorkoutTest',
|
||||
|
@ -65,6 +65,7 @@ enum SharePrefsChange {
|
||||
*/
|
||||
|
||||
enum ActivityDone {
|
||||
tutorialExecuteFirstTest,
|
||||
tutorialBasic,
|
||||
tutorialBasicChestPress,
|
||||
tutorialBasicLegPress,
|
||||
|
@ -3,7 +3,7 @@ import 'dart:convert';
|
||||
import 'package:aitrainer_app/model/exercise.dart';
|
||||
import 'package:aitrainer_app/model/exercise_type.dart';
|
||||
|
||||
enum ExercisePlanDetailState { start, inProgress, skipped, finished }
|
||||
enum ExercisePlanDetailState { start, inProgress, skipped, finished, extra }
|
||||
|
||||
extension ExericisePlanDetailStateExt on ExercisePlanDetailState {
|
||||
bool equalsTo(ExercisePlanDetailState state) => this.toString() == state.toString();
|
||||
|
33
lib/model/training_evaluation_exercise.dart
Normal file
33
lib/model/training_evaluation_exercise.dart
Normal file
@ -0,0 +1,33 @@
|
||||
import 'package:aitrainer_app/model/exercise_plan_detail.dart';
|
||||
import 'package:aitrainer_app/model/exercise_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum TrainingEvaluationExerciseType { weightBased, repeatBased, secondBased }
|
||||
|
||||
extension TrainingEvaluationExerciseTypeExt on TrainingEvaluationExerciseType {
|
||||
String toStr() => this.toString().split(".").last;
|
||||
bool equalsTo(TrainingEvaluationExerciseType value) => this.toString() == value.toString();
|
||||
bool equalsStringTo(String value) => this.toString() == value;
|
||||
}
|
||||
|
||||
class TrainingEvaluationExercise {
|
||||
late int exerciseTypeId;
|
||||
late String name;
|
||||
late TrainingEvaluationExerciseType type;
|
||||
late ExerciseType exerciseType;
|
||||
|
||||
int? repeats;
|
||||
int? maxRepeats;
|
||||
|
||||
int? totalLift;
|
||||
int? maxTotalLift;
|
||||
|
||||
double? oneRepMax;
|
||||
double? max1RM;
|
||||
|
||||
late double? trend;
|
||||
late String trendText;
|
||||
late Color trendColor;
|
||||
|
||||
late ExercisePlanDetailState state;
|
||||
}
|
@ -16,6 +16,7 @@ class ExerciseRepository {
|
||||
List<Exercise>? exerciseList;
|
||||
List<Exercise>? exerciseLogList = [];
|
||||
List<Exercise>? actualExerciseList = [];
|
||||
bool noRegistration = false;
|
||||
|
||||
double rmWendler = 0;
|
||||
double rmMcglothlin = 0;
|
||||
@ -100,6 +101,19 @@ class ExerciseRepository {
|
||||
return savedExercise;
|
||||
}
|
||||
|
||||
void addExerciseNoRegistration() {
|
||||
final Exercise modelExercise = this.exercise!;
|
||||
modelExercise.exerciseTypeId = this.exerciseType!.exerciseTypeId;
|
||||
if (exerciseType!.unitQuantity != "1") {
|
||||
modelExercise.unitQuantity = null;
|
||||
}
|
||||
Exercise copy = modelExercise.copy();
|
||||
this.actualExerciseList!.add(copy);
|
||||
this.exerciseList = [];
|
||||
this.exerciseList!.add(copy);
|
||||
this.noRegistration = true;
|
||||
}
|
||||
|
||||
void initExercise() {
|
||||
this.createNew();
|
||||
this.exerciseType = exerciseType;
|
||||
@ -271,7 +285,9 @@ class ExerciseRepository {
|
||||
}
|
||||
|
||||
void getSameExercise(int exerciseTypeId, String day) {
|
||||
this.actualExerciseList = [];
|
||||
if (!this.noRegistration) {
|
||||
this.actualExerciseList = [];
|
||||
}
|
||||
if (exerciseList != null) {
|
||||
int index = 0;
|
||||
for (int i = 0; i < this.exerciseList!.length; i++) {
|
||||
@ -299,7 +315,7 @@ class ExerciseRepository {
|
||||
return average;
|
||||
}
|
||||
|
||||
double getBest1RMPercent(Exercise exercise) {
|
||||
double getBest1RM(Exercise exercise) {
|
||||
double result = 0;
|
||||
if (this.exerciseList == null || this.exerciseList!.isEmpty) {
|
||||
this.exerciseList = Cache().getExercises();
|
||||
@ -307,9 +323,13 @@ class ExerciseRepository {
|
||||
|
||||
final int exerciseTypeId = exercise.exerciseTypeId!;
|
||||
double toCompare = this.calculate1RM(exercise);
|
||||
result = toCompare;
|
||||
|
||||
final String today = DateFormat("yyyy-MM-dd", AppLanguage().appLocal.toString()).format(DateTime.now());
|
||||
List<Exercise> oldExercises = [];
|
||||
if (exerciseList == null) {
|
||||
return toCompare;
|
||||
}
|
||||
this.exerciseList!.forEach((exercise) {
|
||||
final String exerciseDate = DateFormat("yyyy-MM-dd", AppLanguage().appLocal.toString()).format(exercise.dateAdd!);
|
||||
if (exercise.exerciseTypeId == exerciseTypeId && exerciseDate.compareTo(today) < 0) {
|
||||
@ -333,7 +353,8 @@ class ExerciseRepository {
|
||||
|
||||
double withCompare = this.calculate1RM(oldExercises.last);
|
||||
|
||||
result = toCompare >= withCompare ? (1 - toCompare / withCompare) * 100 : (1 - toCompare / withCompare) * -100;
|
||||
//result = toCompare >= withCompare ? (1 - toCompare / withCompare) * 100 : (1 - toCompare / withCompare) * -100;
|
||||
result = toCompare >= withCompare ? toCompare : withCompare;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -349,6 +370,10 @@ class ExerciseRepository {
|
||||
|
||||
final String today = DateFormat("yyyy-MM-dd", AppLanguage().appLocal.toString()).format(exercise.dateAdd!);
|
||||
List<Exercise> oldExercises = [];
|
||||
|
||||
if (exerciseList == null) {
|
||||
return result;
|
||||
}
|
||||
this.exerciseList!.forEach((exercise) {
|
||||
final String exerciseDate = DateFormat("yyyy-MM-dd", AppLanguage().appLocal.toString()).format(exercise.dateAdd!);
|
||||
if (exercise.exerciseTypeId == exerciseTypeId && exerciseDate.compareTo(today) < 0) {
|
||||
@ -363,7 +388,7 @@ class ExerciseRepository {
|
||||
return result;
|
||||
}
|
||||
|
||||
double getBestExercisePercent(Exercise exercise) {
|
||||
double getBestVolume(Exercise exercise) {
|
||||
double result = 0;
|
||||
if (this.exerciseList == null || this.exerciseList!.isEmpty) {
|
||||
this.exerciseList = Cache().getExercises();
|
||||
@ -371,9 +396,13 @@ class ExerciseRepository {
|
||||
|
||||
final int exerciseTypeId = exercise.exerciseTypeId!;
|
||||
double toCompare = exercise.unitQuantity != null ? exercise.quantity! * exercise.unitQuantity! : exercise.quantity!;
|
||||
result = toCompare;
|
||||
|
||||
final String today = DateFormat("yyyy-MM-dd", AppLanguage().appLocal.toString()).format(DateTime.now());
|
||||
List<Exercise> oldExercises = [];
|
||||
if (exerciseList == null) {
|
||||
return toCompare;
|
||||
}
|
||||
this.exerciseList!.forEach((exercise) {
|
||||
final String exerciseDate = DateFormat("yyyy-MM-dd", AppLanguage().appLocal.toString()).format(exercise.dateAdd!);
|
||||
if (exercise.exerciseTypeId == exerciseTypeId && exerciseDate.compareTo(today) < 0) {
|
||||
@ -399,8 +428,9 @@ class ExerciseRepository {
|
||||
? oldExercises.last.quantity! * oldExercises.last.unitQuantity!
|
||||
: oldExercises.last.quantity!;
|
||||
|
||||
result = toCompare >= withCompare ? (1 - toCompare / withCompare) * 100 : (1 - toCompare / withCompare) * -100;
|
||||
print("Last Best: ${oldExercises.last} - result: $result");
|
||||
//result = toCompare >= withCompare ? (1 - toCompare / withCompare) * 100 : (1 - toCompare / withCompare) * -100;
|
||||
//print("Last Best: ${oldExercises.last} - result: $result");
|
||||
result = toCompare >= withCompare ? toCompare : withCompare;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -416,6 +446,9 @@ class ExerciseRepository {
|
||||
|
||||
final String today = DateFormat("yyyy-MM-dd", AppLanguage().appLocal.toString()).format(exercise.dateAdd!);
|
||||
List<Exercise> oldExercises = [];
|
||||
if (exerciseList == null) {
|
||||
return result;
|
||||
}
|
||||
this.exerciseList!.forEach((exercise) {
|
||||
final String exerciseDate = DateFormat("yyyy-MM-dd", AppLanguage().appLocal.toString()).format(exercise.dateAdd!);
|
||||
if (exercise.exerciseTypeId == exerciseTypeId && exerciseDate.compareTo(today) < 0) {
|
||||
|
@ -172,6 +172,48 @@ class TrainingPlanRepository {
|
||||
return detail;
|
||||
}
|
||||
|
||||
int getOriginalRepeats(int trainingPlanId, CustomerTrainingPlanDetails detail) {
|
||||
TrainingPlan? plan = getTrainingPlanById(trainingPlanId);
|
||||
if (plan == null) {
|
||||
return 0;
|
||||
}
|
||||
int originalRepeats = 0;
|
||||
plan.details!.forEach((element) {
|
||||
if (element.trainingPlanDetailId == detail.trainingPlanDetailsId) {
|
||||
originalRepeats = element.repeats ?? 0;
|
||||
}
|
||||
});
|
||||
return originalRepeats;
|
||||
}
|
||||
|
||||
double getOriginalWeight(int trainingPlanId, CustomerTrainingPlanDetails detail) {
|
||||
TrainingPlan? plan = getTrainingPlanById(trainingPlanId);
|
||||
if (plan == null) {
|
||||
return 0;
|
||||
}
|
||||
double originalWeight = 0;
|
||||
plan.details!.forEach((element) {
|
||||
if (element.trainingPlanDetailId == detail.trainingPlanDetailsId) {
|
||||
originalWeight = element.weight ?? 0;
|
||||
}
|
||||
});
|
||||
return originalWeight;
|
||||
}
|
||||
|
||||
CustomerTrainingPlanDetails recalculateDetailFixRepeats(int trainingPlanId, CustomerTrainingPlanDetails detail) {
|
||||
TrainingPlan? plan = getTrainingPlanById(trainingPlanId);
|
||||
if (plan == null) {
|
||||
return detail;
|
||||
}
|
||||
int originalRepeats = getOriginalRepeats(trainingPlanId, detail);
|
||||
|
||||
detail.weight = Common.calculateWeigthByChangedQuantity(detail.weight!, detail.repeats!.toDouble(), originalRepeats.toDouble());
|
||||
detail.weight = Common.roundWeight(detail.weight!);
|
||||
print("Recalculated weight: ${detail.weight}");
|
||||
detail.repeats = originalRepeats;
|
||||
return detail;
|
||||
}
|
||||
|
||||
CustomerTrainingPlanDetails recalculateDetail(
|
||||
int trainingPlanId, CustomerTrainingPlanDetails detail, CustomerTrainingPlanDetails nextDetail) {
|
||||
CustomerTrainingPlanDetails recalculatedDetail = nextDetail;
|
||||
@ -188,7 +230,6 @@ class TrainingPlanRepository {
|
||||
int originalRepeats = detail.repeats!;
|
||||
plan.details!.forEach((element) {
|
||||
if (element.trainingPlanDetailId == detail.trainingPlanDetailsId) {
|
||||
print("element $element");
|
||||
originalRepeats = element.repeats ?? 0;
|
||||
}
|
||||
});
|
||||
@ -198,7 +239,7 @@ class TrainingPlanRepository {
|
||||
Common.calculateWeigthByChangedQuantity(detail.weight!, detail.repeats!.toDouble(), originalRepeats.toDouble());
|
||||
recalculatedDetail.weight = Common.roundWeight(recalculatedDetail.weight!);
|
||||
print("recalculated repeats for $originalRepeats: ${recalculatedDetail.weight}");
|
||||
recalculatedDetail.repeats = originalRepeats;
|
||||
//recalculatedDetail.repeats = originalRepeats;
|
||||
|
||||
return recalculatedDetail;
|
||||
}
|
||||
|
@ -400,8 +400,8 @@ class FirebaseApi with logging.Logging {
|
||||
try {
|
||||
remoteConfig = RemoteConfig.instance;
|
||||
await remoteConfig.setConfigSettings(RemoteConfigSettings(
|
||||
fetchTimeout: const Duration(seconds: 60),
|
||||
minimumFetchInterval: const Duration(hours: 6),
|
||||
fetchTimeout: const Duration(seconds: 10),
|
||||
minimumFetchInterval: const Duration(seconds: 1),
|
||||
));
|
||||
|
||||
RemoteConfigValue(null, ValueSource.valueStatic);
|
||||
@ -412,6 +412,8 @@ class FirebaseApi with logging.Logging {
|
||||
await remoteConfig.setDefaults(<String, dynamic>{
|
||||
'sales_page_text_a': '',
|
||||
'product_set_2': '',
|
||||
'registration_skip_color': '',
|
||||
'email_checkbox': '',
|
||||
});
|
||||
Cache().setRemoteConfig(remoteConfig);
|
||||
}
|
||||
|
@ -154,7 +154,7 @@ mixin Common {
|
||||
|
||||
double rmWendler = weight * repeat * 0.0333 + weight;
|
||||
double rmOconner = weight * (1 + repeat / 40);
|
||||
//print("Weight: $weight repeat: $repeat, $rmWendler, Oconner: $rmOconner");
|
||||
print("Weight: $weight repeat: $repeat, $rmWendler, Oconner: $rmOconner");
|
||||
double average = (rmWendler + rmOconner) / 2;
|
||||
|
||||
return average;
|
||||
@ -200,7 +200,19 @@ mixin Common {
|
||||
final double repeatWendler = (rmWendler - weight) / 0.0333 / weight;
|
||||
final double repeatOconner = (rmOconner / weight - 1) * 40;
|
||||
final newRepeat = ((repeatOconner + repeatWendler) / 2).ceil();
|
||||
//print("Initial 1RM: $initialRM Weight: $weight repeatWendler: $repeatWendler repeat Oconner: $repeatOconner. NEW REPEAT: $newRepeat");
|
||||
print("Initial 1RM: $initialRM Weight: $weight repeatWendler: $repeatWendler repeat Oconner: $repeatOconner. NEW REPEAT: $newRepeat");
|
||||
return newRepeat;
|
||||
}
|
||||
|
||||
static int reCalculateRepeatsByChangedWeight(double weight, double repeat, double changedWeight) {
|
||||
final double rmWendler = weight * repeat * 0.0333 + weight;
|
||||
final double rmOconner = weight * (1 + repeat / 40);
|
||||
|
||||
final double repeatWendler = (rmWendler - changedWeight) / 0.0333 / changedWeight;
|
||||
final double repeatOconner = (rmOconner / changedWeight - 1) * 40;
|
||||
final newRepeat = ((repeatOconner + repeatWendler) / 2).ceil();
|
||||
print(
|
||||
"Weight: $weight changedWeight: $changedWeight repeatWendler: $repeatWendler repeat Oconner: $repeatOconner. NEW REPEAT: $newRepeat");
|
||||
return newRepeat;
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,7 @@ enum TrackingEvent {
|
||||
purchase_request,
|
||||
purchase_successful,
|
||||
exercise_new,
|
||||
exercise_new_no_registration,
|
||||
exercise_new_paralell,
|
||||
result,
|
||||
exercise_log,
|
||||
@ -50,7 +51,12 @@ enum TrackingEvent {
|
||||
tutorial_activate,
|
||||
terms_of_use,
|
||||
data_privacy,
|
||||
faq
|
||||
faq,
|
||||
training_plan_open,
|
||||
training_plan_start,
|
||||
training_plan_execute,
|
||||
training_plan_finished,
|
||||
training_plan_custom,
|
||||
}
|
||||
|
||||
T enumFromString<T>(Iterable<T> values, String value) {
|
||||
|
@ -288,9 +288,9 @@ class _CustomerFitnessPageState extends State<CustomerFitnessPage> with Trans {
|
||||
},
|
||||
showSelectedItem: true,
|
||||
selectedItem: selected,
|
||||
itemAsString: (data) => t(data.sportNameTranslation),
|
||||
itemAsString: (data) => t(data!.sportNameTranslation),
|
||||
onChanged: (data) {
|
||||
bloc.add(CustomerSportChange(sport: data));
|
||||
bloc.add(CustomerSportChange(sport: data!));
|
||||
},
|
||||
dropdownBuilder: _customDropDownItem,
|
||||
popupItemBuilder: _customMenuBuilder,
|
||||
@ -305,7 +305,7 @@ class _CustomerFitnessPageState extends State<CustomerFitnessPage> with Trans {
|
||||
//items: FitnessItem().toList()));
|
||||
}
|
||||
|
||||
Widget _customMenuBuilder(BuildContext context, Sport sport, bool isSelected) {
|
||||
Widget _customMenuBuilder(BuildContext context, Sport? sport, bool isSelected) {
|
||||
return Container(
|
||||
decoration: !isSelected
|
||||
? BoxDecoration(color: Colors.grey[300])
|
||||
@ -317,7 +317,7 @@ class _CustomerFitnessPageState extends State<CustomerFitnessPage> with Trans {
|
||||
child: ListTile(
|
||||
selected: isSelected,
|
||||
title: Text(
|
||||
t(sport.sportNameTranslation),
|
||||
t(sport!.sportNameTranslation),
|
||||
style: GoogleFonts.archivoBlack(fontSize: 20, color: Colors.blue[600]),
|
||||
),
|
||||
subtitle: Text(
|
||||
|
@ -281,86 +281,16 @@ class CustomerModifyPage extends StatelessWidget with Trans {
|
||||
Divider(
|
||||
color: Colors.transparent,
|
||||
),
|
||||
SfLinearGauge(
|
||||
minimum: 40,
|
||||
maximum: 150,
|
||||
labelPosition: LinearLabelPosition.outside,
|
||||
tickPosition: LinearElementPosition.outside,
|
||||
markerPointers: [
|
||||
LinearWidgetPointer(
|
||||
value: customerBloc.weight,
|
||||
offset: 5,
|
||||
position: LinearElementPosition.outside,
|
||||
markerAlignment: LinearMarkerAlignment.center,
|
||||
child: Container(
|
||||
height: 14,
|
||||
width: 44,
|
||||
color: Colors.transparent,
|
||||
child: Text(customerBloc.weight.toStringAsFixed(1),
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.indigo,
|
||||
)),
|
||||
),
|
||||
),
|
||||
LinearShapePointer(
|
||||
position: LinearElementPosition.inside,
|
||||
shapeType: LinearShapePointerType.triangle,
|
||||
value: customerBloc.weight,
|
||||
height: 55,
|
||||
width: 25,
|
||||
color: Colors.blue,
|
||||
onValueChanged: (value) => {
|
||||
customerBloc.add(CustomerWeightChange(weight: value)),
|
||||
},
|
||||
),
|
||||
],
|
||||
orientation: LinearGaugeOrientation.horizontal,
|
||||
majorTickStyle: LinearTickStyle(length: 20),
|
||||
axisLabelStyle: TextStyle(fontSize: 12.0, color: Colors.black),
|
||||
axisTrackStyle: LinearAxisTrackStyle(
|
||||
color: Colors.cyan, edgeStyle: LinearEdgeStyle.bothFlat, thickness: 1.0, borderColor: Colors.grey)),
|
||||
NumberPickerWidget(
|
||||
minValue: 40,
|
||||
maxValue: 150,
|
||||
initalValue: customerBloc.weight.toInt(),
|
||||
unit: t("kg"),
|
||||
color: Colors.blue[800]!,
|
||||
onChange: (value) => customerBloc.add(CustomerWeightChange(weight: value))),
|
||||
Divider(
|
||||
color: Colors.transparent,
|
||||
),
|
||||
/* SfRadialGauge(
|
||||
axes: <RadialAxis>[
|
||||
RadialAxis(
|
||||
axisLineStyle: AxisLineStyle(
|
||||
thickness: 0.1,
|
||||
thicknessUnit: GaugeSizeUnit.factor,
|
||||
gradient: const SweepGradient(colors: <Color>[Color(0xffb4f500), Colors.blue], stops: <double>[0.1, 0.9]),
|
||||
),
|
||||
minimum: 40,
|
||||
maximum: 160,
|
||||
pointers: [
|
||||
WidgetPointer(
|
||||
value: customerBloc.weight.toDouble(),
|
||||
child: Container(
|
||||
height: 55,
|
||||
width: 60,
|
||||
color: Colors.transparent,
|
||||
child: Text(customerBloc.weight.toStringAsFixed(1),
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 14,
|
||||
color: Colors.indigo,
|
||||
)),
|
||||
)),
|
||||
NeedlePointer(
|
||||
needleColor: Colors.blue[200],
|
||||
knobStyle: KnobStyle(color: Colors.blue[800]),
|
||||
value: customerBloc.weight.toDouble(),
|
||||
enableAnimation: true,
|
||||
//enableDragging: true,
|
||||
needleStartWidth: 1,
|
||||
needleEndWidth: 12,
|
||||
//onValueChanged: (value) => {customerBloc.add(CustomerWeightChange(weight: value))},
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
), */
|
||||
]),
|
||||
),
|
||||
Divider(
|
||||
@ -455,19 +385,6 @@ class CustomerModifyPage extends StatelessWidget with Trans {
|
||||
},
|
||||
),
|
||||
Divider(),
|
||||
/* TextButton(
|
||||
onPressed: () => {customerBloc.add(CustomerSave())},
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Image.asset('asset/icon/gomb_orange_a.png', width: 140, height: 60),
|
||||
Text(
|
||||
fulldata ? t("Save") : t("Next"),
|
||||
style: TextStyle(fontSize: 16, color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
), */
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -1,7 +1,6 @@
|
||||
import 'dart:collection';
|
||||
import 'dart:ui';
|
||||
import 'package:aitrainer_app/bloc/tutorial/tutorial_bloc.dart';
|
||||
import 'package:aitrainer_app/util/enums.dart';
|
||||
import 'package:aitrainer_app/widgets/tutorial_widget.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:aitrainer_app/bloc/result/result_bloc.dart';
|
||||
@ -23,6 +22,7 @@ import 'package:modal_progress_hud_nsn/modal_progress_hud_nsn.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class EvaluationPage extends StatelessWidget with Trans {
|
||||
bool noRegistration = false;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
dynamic arguments = ModalRoute.of(context)!.settings.arguments;
|
||||
@ -33,9 +33,14 @@ class EvaluationPage extends StatelessWidget with Trans {
|
||||
exerciseRepository = ExerciseRepository();
|
||||
}
|
||||
|
||||
final TutorialBloc bloc = BlocProvider.of<TutorialBloc>(context);
|
||||
noRegistration = bloc.actualCheck == "directTest";
|
||||
|
||||
ResultType resultType = ResultType.none;
|
||||
String imageUrl = "";
|
||||
if (Cache().userLoggedIn!.sex == "m") {
|
||||
if (Cache().userLoggedIn == null) {
|
||||
imageUrl = 'asset/image/WT_Results_for_men.jpg';
|
||||
} else if (Cache().userLoggedIn!.sex == "m") {
|
||||
resultType = ResultType.man;
|
||||
imageUrl = 'asset/image/WT_Results_for_men.jpg';
|
||||
} else {
|
||||
@ -59,7 +64,7 @@ class EvaluationPage extends StatelessWidget with Trans {
|
||||
}
|
||||
|
||||
final TutorialBloc tutorialBloc = BlocProvider.of<TutorialBloc>(context);
|
||||
print("Evaluation page tutorial isActive? ${tutorialBloc.isActive}");
|
||||
print("Evaluation page tutorial isActive? ${tutorialBloc.isActive} ${exerciseRepository.exercise!.quantity}");
|
||||
if (tutorialBloc.isActive == false) {
|
||||
TutorialWidget().close();
|
||||
}
|
||||
@ -107,6 +112,26 @@ class EvaluationPage extends StatelessWidget with Trans {
|
||||
String exerciseName = AppLanguage().appLocal == Locale("en")
|
||||
? resultBloc.exerciseRepository.exerciseType!.name
|
||||
: resultBloc.exerciseRepository.exerciseType!.nameTranslation;
|
||||
|
||||
String? volume, volumeEver, oneRepMax, oneRepMaxEver;
|
||||
|
||||
if (resultBloc.exerciseRepository.actualExerciseList![0].unitQuantity != null) {
|
||||
oneRepMax = resultBloc.exerciseRepository.calculate1RM(resultBloc.exerciseRepository.actualExerciseList![0]).toStringAsFixed(1) +
|
||||
" " +
|
||||
t("kg");
|
||||
|
||||
volume = (resultBloc.exerciseRepository.actualExerciseList![0].quantity! *
|
||||
resultBloc.exerciseRepository.actualExerciseList![0].unitQuantity!)
|
||||
.toStringAsFixed(0) +
|
||||
" " +
|
||||
t("kg");
|
||||
volumeEver = resultBloc.exerciseRepository.getBestVolume(resultBloc.exerciseRepository.actualExerciseList![0]).toStringAsFixed(0) +
|
||||
" " +
|
||||
t("kg");
|
||||
oneRepMaxEver =
|
||||
resultBloc.exerciseRepository.getBest1RM(resultBloc.exerciseRepository.actualExerciseList![0]).toStringAsFixed(1) + " " + t("kg");
|
||||
}
|
||||
|
||||
return Container(
|
||||
padding: EdgeInsets.only(left: 10, right: 10),
|
||||
child: CustomScrollView(scrollDirection: Axis.vertical, slivers: [
|
||||
@ -123,8 +148,8 @@ class EvaluationPage extends StatelessWidget with Trans {
|
||||
maxLines: 3,
|
||||
//softWrap: true,
|
||||
style: GoogleFonts.archivoBlack(
|
||||
fontSize: 20,
|
||||
color: Colors.yellow[300],
|
||||
fontSize: 28,
|
||||
color: Colors.orange[300],
|
||||
shadows: <Shadow>[
|
||||
Shadow(
|
||||
offset: Offset(5.0, 5.0),
|
||||
@ -140,61 +165,268 @@ class EvaluationPage extends StatelessWidget with Trans {
|
||||
)),
|
||||
),
|
||||
),
|
||||
|
||||
//getResultSummary(resultBloc),
|
||||
SliverList(
|
||||
delegate: SliverChildListDelegate([
|
||||
Text(DateFormat('y-M-d HH:mm', AppLanguage().appLocal.toString()).format(resultBloc.exerciseRepository.start!),
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2,
|
||||
softWrap: true,
|
||||
style: GoogleFonts.archivoBlack(
|
||||
fontSize: 20,
|
||||
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,
|
||||
),
|
||||
],
|
||||
)),
|
||||
Divider(color: Colors.transparent),
|
||||
Divider(color: Colors.transparent),
|
||||
Text(t("Summary of your test"),
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2,
|
||||
softWrap: true,
|
||||
style: GoogleFonts.archivoBlack(
|
||||
fontSize: 20,
|
||||
color: Colors.yellow[300],
|
||||
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,
|
||||
),
|
||||
],
|
||||
)),
|
||||
]),
|
||||
),
|
||||
getResultSummary(resultBloc),
|
||||
delegate: SliverChildListDelegate([
|
||||
getEvaluationWidget(resultBloc),
|
||||
getSummary(resultBloc),
|
||||
Divider(),
|
||||
summaryRow("asset/image/pict_time_h.png", "Start of the Exercise",
|
||||
DateFormat('y-M-d HH:mm', AppLanguage().appLocal.toString()).format(resultBloc.exerciseRepository.start!)),
|
||||
Divider(),
|
||||
oneRepMax != null ? summaryRow("asset/image/pict_1rm.png", "Your One Rep Max", oneRepMax) : Offstage(),
|
||||
Divider(),
|
||||
resultBloc.exerciseRepository.actualExerciseList![0].unitQuantity != null
|
||||
? summaryRow("asset/image/pict_weight_volumen_tonna.png", "Total Lift", volume!)
|
||||
: Offstage(),
|
||||
Divider(),
|
||||
resultBloc.exerciseRepository.actualExerciseList![0].unitQuantity != null
|
||||
? summaryRow("asset/image/pict_history.png", "Total Lift Ever", volumeEver!)
|
||||
: Offstage(),
|
||||
Divider(),
|
||||
resultBloc.exerciseRepository.actualExerciseList![0].unitQuantity != null
|
||||
? summaryRow("asset/image/pict_1rm.png", "Your One Rep Max Ever", oneRepMaxEver!)
|
||||
: Offstage(),
|
||||
])),
|
||||
|
||||
cta(resultBloc),
|
||||
getSuggestionTitle(resultBloc),
|
||||
getSuggestion(resultBloc),
|
||||
emptySliver(),
|
||||
//emptySliver(),
|
||||
//getResultTitle(resultBloc),
|
||||
//getResults(resultBloc),
|
||||
]));
|
||||
}
|
||||
|
||||
Widget getEvaluationWidget(ResultBloc bloc) {
|
||||
List<Widget> resultList = [];
|
||||
print("Act ${bloc.exerciseRepository.actualExerciseList}");
|
||||
if (bloc.exerciseRepository.actualExerciseList == null || bloc.exerciseRepository.actualExerciseList!.isEmpty) {
|
||||
return Offstage();
|
||||
}
|
||||
final int exerciseTypeId = bloc.exerciseRepository.actualExerciseList![0].exerciseTypeId!;
|
||||
final double quantity = bloc.exerciseRepository.actualExerciseList![0].quantity!;
|
||||
String eval = bloc.evaluationRepository.getEvaluationTextByExerciseType(exerciseTypeId, quantity);
|
||||
Color color = bloc.evaluationRepository.getEvaluationColor(eval);
|
||||
|
||||
//if (!EvaluationText.fair.equalsStringTo(eval)) {
|
||||
resultList.add(RichText(
|
||||
text: TextSpan(
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
children: [
|
||||
TextSpan(text: t("Your result is") + ": "),
|
||||
TextSpan(
|
||||
text: t(eval),
|
||||
style: GoogleFonts.archivoBlack(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
]),
|
||||
));
|
||||
|
||||
resultList.add(Divider(color: Colors.transparent));
|
||||
//}
|
||||
|
||||
return Column(children: resultList);
|
||||
}
|
||||
|
||||
Widget cta(ResultBloc resultBloc) {
|
||||
return SliverList(
|
||||
delegate: SliverChildListDelegate(
|
||||
ctaSales(resultBloc),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> ctaSales(ResultBloc resultBloc) {
|
||||
final List<Widget> resultList = [];
|
||||
|
||||
if (this.noRegistration) {
|
||||
resultList.add(Divider());
|
||||
resultList.add(RichText(
|
||||
text: TextSpan(
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Colors.white,
|
||||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: t('Reach all basic functions, suggestions and'),
|
||||
style: GoogleFonts.inter(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
TextSpan(text: " "),
|
||||
TextSpan(
|
||||
text: t('optimized training plans, customized to your fitness state and strength:'),
|
||||
style: GoogleFonts.inter(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
])));
|
||||
resultList.add(
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pushNamed("registration"),
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Image.asset('asset/icon/gomb_orange_a.png', width: 140, height: 60),
|
||||
Text(
|
||||
t("Register"),
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
if (!Cache().hasPurchased) {
|
||||
resultList.add(Divider());
|
||||
resultList.add(Divider());
|
||||
resultList.add(RichText(
|
||||
text: TextSpan(
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Colors.white,
|
||||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: t('How can serve you this result?'),
|
||||
style: GoogleFonts.inter(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
]),
|
||||
));
|
||||
resultList.add(RichText(
|
||||
text: TextSpan(
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Colors.white,
|
||||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: t('Get the Fastlane to your'),
|
||||
style: GoogleFonts.inter(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
]),
|
||||
));
|
||||
resultList.add(RichText(
|
||||
text: TextSpan(
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Colors.white,
|
||||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: t('Development'),
|
||||
style: GoogleFonts.inter(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.yellow[300],
|
||||
),
|
||||
)
|
||||
])));
|
||||
|
||||
resultList.add(TextButton(
|
||||
onPressed: () => {Navigator.of(context).pushNamed("salesPage")},
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Image.asset('asset/icon/gomb_orange_a.png', width: 140, height: 60),
|
||||
Text(
|
||||
t("Go"),
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return resultList;
|
||||
}
|
||||
|
||||
Widget summaryRow(String imageUrl, String title, String data) {
|
||||
return Row(
|
||||
children: [
|
||||
Image.asset(
|
||||
imageUrl,
|
||||
height: 40,
|
||||
),
|
||||
SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
flex: 1,
|
||||
child: Text(t(title),
|
||||
textAlign: TextAlign.start,
|
||||
maxLines: 2,
|
||||
softWrap: true,
|
||||
style: GoogleFonts.archivoBlack(
|
||||
fontSize: 18,
|
||||
color: Colors.orange[400],
|
||||
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,
|
||||
),
|
||||
],
|
||||
))),
|
||||
SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Text(data,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2,
|
||||
softWrap: true,
|
||||
style: GoogleFonts.archivoBlack(
|
||||
fontSize: 20,
|
||||
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,
|
||||
),
|
||||
],
|
||||
))
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget getSuggestionTitle(ResultBloc resultBloc) {
|
||||
if (resultBloc.exerciseRepository.exerciseType!.unitQuantityUnit != null) {
|
||||
return SliverList(
|
||||
@ -294,6 +526,14 @@ class EvaluationPage extends StatelessWidget with Trans {
|
||||
String unitQuantityUnit = resultBloc.exerciseRepository.exerciseType!.unitQuantityUnit == null
|
||||
? ""
|
||||
: resultBloc.exerciseRepository.exerciseType!.unitQuantityUnit!;
|
||||
|
||||
String weight = resultBloc.calculate1RM(percent: percent).toStringAsFixed(0);
|
||||
if (this.noRegistration) {
|
||||
repeats = "____";
|
||||
weight = "_____";
|
||||
restTime = "__";
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Text(t(title),
|
||||
@ -353,7 +593,7 @@ class EvaluationPage extends StatelessWidget with Trans {
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Text(t("Weight") + ": " + resultBloc.calculate1RM(percent: percent).toStringAsFixed(0) + " " + unitQuantityUnit,
|
||||
Text(t("Weight") + ": " + weight + " " + unitQuantityUnit,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2,
|
||||
softWrap: true,
|
||||
@ -454,225 +694,14 @@ class EvaluationPage extends StatelessWidget with Trans {
|
||||
return Column(children: resultList);
|
||||
}
|
||||
|
||||
Widget getEvaluationWidget(ResultBloc bloc) {
|
||||
List<Widget> resultList = [];
|
||||
print("Act ${bloc.exerciseRepository.actualExerciseList}");
|
||||
if (bloc.exerciseRepository.actualExerciseList == null || bloc.exerciseRepository.actualExerciseList!.isEmpty) {
|
||||
return Offstage();
|
||||
}
|
||||
final int exerciseTypeId = bloc.exerciseRepository.actualExerciseList![0].exerciseTypeId!;
|
||||
final double quantity = bloc.exerciseRepository.actualExerciseList![0].quantity!;
|
||||
String eval = bloc.evaluationRepository.getEvaluationTextByExerciseType(exerciseTypeId, quantity);
|
||||
Color color = bloc.evaluationRepository.getEvaluationColor(eval);
|
||||
double compareBest = bloc.exerciseRepository.getBestExercisePercent(bloc.exerciseRepository.actualExerciseList![0]);
|
||||
double compareLast = bloc.exerciseRepository.getLastExercisePercent(bloc.exerciseRepository.actualExerciseList![0]);
|
||||
bool has1RM = bloc.exerciseRepository.actualExerciseList![0].unitQuantity != null;
|
||||
double? bestCompared1RM;
|
||||
double? lastCompared1RM;
|
||||
if (has1RM) {
|
||||
lastCompared1RM = bloc.exerciseRepository.getLast1RMPercent(bloc.exerciseRepository.actualExerciseList![0]);
|
||||
bestCompared1RM = bloc.exerciseRepository.getBest1RMPercent(bloc.exerciseRepository.actualExerciseList![0]);
|
||||
}
|
||||
|
||||
if (!EvaluationText.fair.equalsStringTo(eval)) {
|
||||
resultList.add(RichText(
|
||||
text: TextSpan(
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 30,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
children: [
|
||||
TextSpan(text: t("Your result is: ")),
|
||||
TextSpan(
|
||||
text: eval,
|
||||
style: GoogleFonts.archivoBlack(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
]),
|
||||
));
|
||||
|
||||
resultList.add(Divider(color: Colors.transparent));
|
||||
}
|
||||
|
||||
resultList.add(RichText(
|
||||
text: TextSpan(
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 20,
|
||||
color: Colors.white,
|
||||
),
|
||||
children: [
|
||||
TextSpan(text: t("Compared with...")),
|
||||
]),
|
||||
));
|
||||
|
||||
resultList.add(RichText(
|
||||
text: TextSpan(
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 20,
|
||||
color: Colors.white,
|
||||
),
|
||||
children: [
|
||||
TextSpan(text: t("your best")),
|
||||
TextSpan(text: " "),
|
||||
TextSpan(text: has1RM ? t("volumen") : t("exercise")),
|
||||
TextSpan(text: ": "),
|
||||
TextSpan(
|
||||
text: compareBest.toStringAsFixed(1) + "%",
|
||||
style: GoogleFonts.archivoBlack(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: compareBest >= 0 ? Colors.green : Colors.red[600],
|
||||
),
|
||||
),
|
||||
]),
|
||||
));
|
||||
|
||||
resultList.add(RichText(
|
||||
text: TextSpan(
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 20,
|
||||
color: Colors.white,
|
||||
),
|
||||
children: [
|
||||
TextSpan(text: t("your last")),
|
||||
TextSpan(text: " "),
|
||||
TextSpan(text: has1RM ? t("volumen") : t("exercise")),
|
||||
TextSpan(text: ": "),
|
||||
TextSpan(
|
||||
text: compareLast.toStringAsFixed(1) + "%",
|
||||
style: GoogleFonts.archivoBlack(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: compareLast >= 0 ? Colors.green : Colors.red[600],
|
||||
),
|
||||
),
|
||||
]),
|
||||
));
|
||||
resultList.add(Divider(color: Colors.transparent));
|
||||
|
||||
if (has1RM) {
|
||||
resultList.add(RichText(
|
||||
text: TextSpan(
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 20,
|
||||
color: Colors.white,
|
||||
),
|
||||
children: [
|
||||
TextSpan(text: t("best")),
|
||||
TextSpan(text: " "),
|
||||
TextSpan(text: t("1RM")),
|
||||
TextSpan(text: ": "),
|
||||
TextSpan(
|
||||
text: bestCompared1RM!.toStringAsFixed(1) + "%",
|
||||
style: GoogleFonts.archivoBlack(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: bestCompared1RM >= 0 ? Colors.green : Colors.red[600],
|
||||
),
|
||||
),
|
||||
]),
|
||||
));
|
||||
resultList.add(RichText(
|
||||
text: TextSpan(
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 20,
|
||||
color: Colors.white,
|
||||
),
|
||||
children: [
|
||||
TextSpan(text: t("last")),
|
||||
TextSpan(text: " "),
|
||||
TextSpan(text: t("1RM")),
|
||||
TextSpan(text: ": "),
|
||||
TextSpan(
|
||||
text: lastCompared1RM!.toStringAsFixed(1) + "%",
|
||||
style: GoogleFonts.archivoBlack(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: lastCompared1RM >= 0 ? Colors.green : Colors.red[600],
|
||||
),
|
||||
),
|
||||
]),
|
||||
));
|
||||
}
|
||||
|
||||
if (!Cache().hasPurchased) {
|
||||
resultList.add(Divider());
|
||||
resultList.add(Divider());
|
||||
resultList.add(RichText(
|
||||
text: TextSpan(
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Colors.white,
|
||||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: t('How can serve you this result?'),
|
||||
style: GoogleFonts.inter(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
]),
|
||||
));
|
||||
resultList.add(RichText(
|
||||
text: TextSpan(
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Colors.white,
|
||||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: t('Get the Fastlane to your'),
|
||||
style: GoogleFonts.inter(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
]),
|
||||
));
|
||||
resultList.add(RichText(
|
||||
text: TextSpan(
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Colors.white,
|
||||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: t('Development'),
|
||||
style: GoogleFonts.inter(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.yellow[300],
|
||||
),
|
||||
)
|
||||
])));
|
||||
|
||||
resultList.add(TextButton(
|
||||
onPressed: () => {Navigator.of(context).pushNamed("salesPage")},
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Image.asset('asset/icon/gomb_orange_a.png', width: 140, height: 60),
|
||||
Text(
|
||||
t("Go"),
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
return Column(children: resultList);
|
||||
}
|
||||
|
||||
Widget getResultSummary(ResultBloc resultBloc) {
|
||||
return SliverList(
|
||||
delegate: SliverChildListDelegate(
|
||||
[Divider(color: Colors.transparent), getSummary(resultBloc), Divider(color: Colors.transparent), getEvaluationWidget(resultBloc)],
|
||||
[
|
||||
Divider(color: Colors.transparent),
|
||||
getSummary(resultBloc),
|
||||
Divider(color: Colors.transparent),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -436,8 +436,8 @@ class _UnitQuantityControlState extends State<UnitQuantityControl> with Trans {
|
||||
height: 20,
|
||||
),
|
||||
NumberPickerWidget(
|
||||
minValue: (widget.exerciseBloc.unitQuantity - 10).round(),
|
||||
maxValue: (widget.exerciseBloc.unitQuantity + 10).round(),
|
||||
minValue: (widget.exerciseBloc.unitQuantity - 30).round(),
|
||||
maxValue: (widget.exerciseBloc.unitQuantity + 30).round(),
|
||||
initalValue: widget.exerciseBloc.unitQuantity.round(),
|
||||
unit: t("kg"),
|
||||
color: Colors.yellow[50]!,
|
||||
|
@ -15,7 +15,6 @@ import 'package:aitrainer_app/util/trans.dart';
|
||||
import 'package:aitrainer_app/widgets/app_bar.dart';
|
||||
import 'package:aitrainer_app/widgets/bmi_widget.dart';
|
||||
import 'package:aitrainer_app/widgets/bmr_widget.dart';
|
||||
import 'package:aitrainer_app/widgets/bottom_bar_multiple_exercises.dart';
|
||||
import 'package:aitrainer_app/widgets/dialog_common.dart';
|
||||
import 'package:aitrainer_app/widgets/exercise_save.dart';
|
||||
import 'package:aitrainer_app/widgets/size_widget.dart';
|
||||
@ -72,14 +71,21 @@ class _ExerciseNewPageState extends State<ExerciseNewPage> with Trans, Logging {
|
||||
// ignore: close_sinks
|
||||
final bloc = BlocProvider.of<ExerciseNewBloc>(context);
|
||||
|
||||
if (bloc.exerciseRepository.exerciseType!.unitQuantityUnit == null) {
|
||||
// ignore: close_sinks
|
||||
final TutorialBloc tutorialBloc = BlocProvider.of<TutorialBloc>(context);
|
||||
if (tutorialBloc.actualCheck == "directTest") {
|
||||
args['exerciseRepository'] = bloc.exerciseRepository;
|
||||
Navigator.of(context).pushNamed('evaluationPage', arguments: args);
|
||||
} else if (menuBloc.ability!.equalsTo(ExerciseAbility.oneRepMax)) {
|
||||
args['exerciseRepository'] = bloc.exerciseRepository;
|
||||
args['percent'] = 0.75;
|
||||
args['readonly'] = false;
|
||||
Navigator.of(context).pushNamed('exerciseControlPage', arguments: args);
|
||||
} else {
|
||||
if (bloc.exerciseRepository.exerciseType!.unitQuantityUnit == null) {
|
||||
args['exerciseRepository'] = bloc.exerciseRepository;
|
||||
Navigator.of(context).pushNamed('evaluationPage', arguments: args);
|
||||
} else if (menuBloc.ability!.equalsTo(ExerciseAbility.oneRepMax)) {
|
||||
args['exerciseRepository'] = bloc.exerciseRepository;
|
||||
args['percent'] = 0.75;
|
||||
args['readonly'] = false;
|
||||
Navigator.of(context).pushNamed('exerciseControlPage', arguments: args);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -99,25 +105,13 @@ class _ExerciseNewPageState extends State<ExerciseNewPage> with Trans, Logging {
|
||||
|
||||
Widget getExerciseSaveWidget(ExerciseNewBloc exerciseBloc, ExerciseType exerciseType, MenuBloc menuBloc) {
|
||||
if (exerciseBloc.exerciseRepository.exerciseType!.name == "BMR") {
|
||||
if (Cache().userLoggedIn == null) {
|
||||
exerciseBloc.add(ExerciseNewAddError(message: "Please log in"));
|
||||
} else {
|
||||
return BMR(exerciseBloc: exerciseBloc);
|
||||
}
|
||||
return BMR(exerciseBloc: exerciseBloc);
|
||||
}
|
||||
if (exerciseBloc.exerciseRepository.exerciseType!.name == "BMI") {
|
||||
if (Cache().userLoggedIn == null) {
|
||||
exerciseBloc.add(ExerciseNewAddError(message: "Please log in"));
|
||||
} else {
|
||||
return BMI(exerciseBloc: exerciseBloc);
|
||||
}
|
||||
return BMI(exerciseBloc: exerciseBloc);
|
||||
}
|
||||
if (exerciseBloc.exerciseRepository.exerciseType!.name == "Sizes") {
|
||||
if (Cache().userLoggedIn == null) {
|
||||
exerciseBloc.add(ExerciseNewAddError(message: "Please log in"));
|
||||
} else {
|
||||
return SizeWidget(exerciseBloc: exerciseBloc);
|
||||
}
|
||||
return SizeWidget(exerciseBloc: exerciseBloc);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
@ -159,10 +153,10 @@ class _ExerciseNewPageState extends State<ExerciseNewPage> with Trans, Logging {
|
||||
style: GoogleFonts.inter(fontWeight: FontWeight.bold, fontSize: 12),
|
||||
),
|
||||
),
|
||||
bottomNavigationBar: BottomBarMultipleExercises(
|
||||
/* bottomNavigationBar: BottomBarMultipleExercises(
|
||||
isSet: false,
|
||||
exerciseTypeId: exerciseType.exerciseTypeId,
|
||||
),
|
||||
), */
|
||||
);
|
||||
}
|
||||
|
||||
@ -192,11 +186,11 @@ class _ExerciseNewPageState extends State<ExerciseNewPage> with Trans, Logging {
|
||||
if (executeBloc != null && executeBloc.existsActivePlan() == true) {
|
||||
confirmationOverride(bloc);
|
||||
} else {
|
||||
confirmationSave(bloc, menuBloc);
|
||||
confirmationSave(bloc, menuBloc, tutorialBloc);
|
||||
}
|
||||
}
|
||||
|
||||
void confirmationSave(ExerciseNewBloc bloc, MenuBloc menuBloc) {
|
||||
void confirmationSave(ExerciseNewBloc bloc, MenuBloc menuBloc, TutorialBloc tutorialBloc) {
|
||||
if (bloc.exerciseRepository.exercise!.quantity == null) {
|
||||
return;
|
||||
}
|
||||
@ -251,7 +245,11 @@ class _ExerciseNewPageState extends State<ExerciseNewPage> with Trans, Logging {
|
||||
onPressed: () {
|
||||
if (Cache().userLoggedIn == null) {
|
||||
Navigator.pop(context);
|
||||
bloc.add(ExerciseNewAddError(message: "Please log in, because we can calculate the best suggestions for you"));
|
||||
if (tutorialBloc.actualCheck == "directTest") {
|
||||
bloc.add(ExerciseNewSubmitNoRegistration());
|
||||
} else {
|
||||
bloc.add(ExerciseNewAddError(message: "Please log in, because we can calculate the best suggestions for you"));
|
||||
}
|
||||
} else {
|
||||
saveAll(bloc);
|
||||
if (executeBloc.existsActivePlan() == true) {
|
||||
|
@ -6,7 +6,7 @@ import 'package:aitrainer_app/repository/user_repository.dart';
|
||||
import 'package:aitrainer_app/util/trans.dart';
|
||||
import 'package:aitrainer_app/widgets/app_bar_min.dart';
|
||||
import 'package:aitrainer_app/widgets/dialog_common.dart';
|
||||
import 'package:aitrainer_app/widgets/dialog_long.dart';
|
||||
import 'package:aitrainer_app/widgets/dialog_web_browser.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@ -233,7 +233,7 @@ class LoginPage extends StatelessWidget with Trans {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return DialogGDPR();
|
||||
return DialogWebBrowser(url: 'https://workouttest.com/privacy/', javascriptEnabled: true);
|
||||
})
|
||||
}),
|
||||
]),
|
||||
|
@ -285,7 +285,6 @@ class _MyDevelopmentMuscleState extends State<MyDevelopmentMusclePage> with Comm
|
||||
getTextStyles: (_) => TextStyle(fontSize: 8, color: Colors.blueGrey),
|
||||
getTitles: (double value) {
|
||||
var date = new DateTime.fromMillisecondsSinceEpoch(value.toInt());
|
||||
//String strDate = DateFormat('MM.dd.', AppLanguage().appLocal.toString()).format(date);
|
||||
String strDate = getDatePart(date, bloc.dateRate);
|
||||
return strDate;
|
||||
},
|
||||
@ -293,7 +292,8 @@ class _MyDevelopmentMuscleState extends State<MyDevelopmentMusclePage> with Comm
|
||||
leftTitles: SideTitles(
|
||||
showTitles: true,
|
||||
getTextStyles: (_) => TextStyle(fontSize: 8, color: Colors.blueGrey),
|
||||
interval: bloc.listChartData[element.exerciseTypeId] == null
|
||||
interval: bloc.listChartData[element.exerciseTypeId] == null ||
|
||||
bloc.listChartData[element.exerciseTypeId]!.interval == 0
|
||||
? 100
|
||||
: bloc.listChartData[element.exerciseTypeId]!.interval,
|
||||
margin: 10,
|
||||
|
@ -6,7 +6,7 @@ import 'package:aitrainer_app/repository/user_repository.dart';
|
||||
import 'package:aitrainer_app/util/trans.dart';
|
||||
import 'package:aitrainer_app/widgets/app_bar_min.dart';
|
||||
import 'package:aitrainer_app/widgets/dialog_common.dart';
|
||||
import 'package:aitrainer_app/widgets/dialog_long.dart';
|
||||
import 'package:aitrainer_app/widgets/dialog_web_browser.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@ -102,9 +102,9 @@ class RegistrationPage extends StatelessWidget with Trans {
|
||||
GestureDetector(
|
||||
onTap: () => loginBloc.add(LoginSkip()),
|
||||
child: Text(
|
||||
t("Skip"),
|
||||
t("I Execute My First Test Now"),
|
||||
textAlign: TextAlign.right,
|
||||
style: GoogleFonts.inter(color: loginBloc.testColor, decoration: TextDecoration.underline),
|
||||
style: GoogleFonts.inter(color: loginBloc.testColor, decoration: TextDecoration.underline, fontWeight: FontWeight.bold),
|
||||
)),
|
||||
SizedBox(
|
||||
height: 120,
|
||||
@ -207,6 +207,16 @@ class RegistrationPage extends StatelessWidget with Trans {
|
||||
Divider(
|
||||
color: Colors.transparent,
|
||||
),
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
Icons.check,
|
||||
color: Colors.green,
|
||||
),
|
||||
title: Text(
|
||||
t("With the registration I accept the data policy and the terms of use."),
|
||||
style: GoogleFonts.inter(color: Colors.indigo),
|
||||
),
|
||||
),
|
||||
Row(mainAxisAlignment: MainAxisAlignment.start, children: <Widget>[
|
||||
TextButton(
|
||||
key: LibraryKeys.loginOKButton,
|
||||
@ -236,7 +246,20 @@ class RegistrationPage extends StatelessWidget with Trans {
|
||||
),
|
||||
onTap: () => Navigator.of(context).pushNamed('login'),
|
||||
),
|
||||
Spacer(flex: 2),
|
||||
Spacer(flex: 1),
|
||||
InkWell(
|
||||
child: Text(
|
||||
t('Terms Of Use'),
|
||||
style: GoogleFonts.inter(decoration: TextDecoration.underline),
|
||||
),
|
||||
onTap: () => {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return DialogWebBrowser(url: 'https://workouttest.com/terms-of-use/', javascriptEnabled: true);
|
||||
})
|
||||
}),
|
||||
Spacer(flex: 1),
|
||||
InkWell(
|
||||
child: Text(
|
||||
t('Privacy'),
|
||||
@ -246,39 +269,11 @@ class RegistrationPage extends StatelessWidget with Trans {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return DialogGDPR();
|
||||
return DialogWebBrowser(url: 'https://workouttest.com/privacy/', javascriptEnabled: true);
|
||||
})
|
||||
}),
|
||||
]),
|
||||
])),
|
||||
);
|
||||
}
|
||||
|
||||
Widget getDataProtection(LoginBloc loginBloc) {
|
||||
return CheckboxListTile(
|
||||
title: Text(t("Please accept our data protection policy.")),
|
||||
subtitle: Text(t("For more information please click on 'Privacy'")),
|
||||
dense: true,
|
||||
value: loginBloc.dataPolicyAllowed,
|
||||
activeColor: Colors.indigo,
|
||||
onChanged: (value) {
|
||||
loginBloc.add(DataProtectionClicked(marked: value!));
|
||||
},
|
||||
controlAffinity: ListTileControlAffinity.leading, // <-- leading Checkbox
|
||||
);
|
||||
}
|
||||
|
||||
Widget getEmailSubscription(LoginBloc loginBloc) {
|
||||
return CheckboxListTile(
|
||||
title: Text(t("Email notifications")),
|
||||
subtitle: Text(t("We may ask you about your opinion, send events in email")),
|
||||
dense: true,
|
||||
value: loginBloc.emailSubscription,
|
||||
activeColor: Colors.indigo,
|
||||
onChanged: (value) {
|
||||
loginBloc.add(EmailSubscriptionClicked(marked: value!));
|
||||
},
|
||||
controlAffinity: ListTileControlAffinity.leading, // <-- leading Checkbox
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -232,7 +232,7 @@ class SalesPage extends StatelessWidget with Trans, Logging {
|
||||
}).toList(),
|
||||
),
|
||||
|
||||
getTrialDescription(),
|
||||
getTrialDescription(bloc),
|
||||
Html(
|
||||
data: bloc.premiumFunctions,
|
||||
//Optional parameters:
|
||||
@ -297,22 +297,15 @@ class SalesPage extends StatelessWidget with Trans, Logging {
|
||||
style: GoogleFonts.inter(fontSize: 12, color: Colors.white),
|
||||
)),
|
||||
Divider(),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 55, right: 55),
|
||||
child: Text(
|
||||
t("Account will be charged for renewal within 24 hours prior to the end of the current period"),
|
||||
style: GoogleFonts.inter(fontSize: 12, color: Colors.white),
|
||||
)),
|
||||
])),
|
||||
]));
|
||||
}
|
||||
|
||||
Widget getTrialDescription() {
|
||||
final trialText = t("Try free for 3 days!");
|
||||
Widget getTrialDescription(SalesBloc bloc) {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(left: 55, right: 55),
|
||||
child: Html(
|
||||
data: "<p>" + trialText + "</p>",
|
||||
data: bloc.trial,
|
||||
//Optional parameters:
|
||||
style: {
|
||||
"p": Style(
|
||||
@ -329,9 +322,28 @@ class SalesPage extends StatelessWidget with Trans, Logging {
|
||||
],
|
||||
),
|
||||
"strong": Style(
|
||||
color: Colors.yellow[600],
|
||||
color: Colors.orange[600],
|
||||
fontSize: FontSize(13),
|
||||
),
|
||||
"h2": Style(
|
||||
color: Colors.orange[600],
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: FontSize(18),
|
||||
textAlign: TextAlign.center,
|
||||
textShadow: <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,
|
||||
),
|
||||
],
|
||||
//padding: const EdgeInsets.all(4),
|
||||
),
|
||||
},
|
||||
));
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import 'package:aitrainer_app/util/track.dart';
|
||||
import 'package:aitrainer_app/util/trans.dart';
|
||||
import 'package:aitrainer_app/widgets/app_bar_min.dart';
|
||||
import 'package:aitrainer_app/widgets/bottom_nav.dart';
|
||||
import 'package:aitrainer_app/widgets/dialog_web_browser.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@ -157,7 +158,7 @@ class SettingsPage extends StatelessWidget with Trans {
|
||||
inactiveFgColor: Colors.grey[900],
|
||||
labels: [t('Basic Tutorial'), t('Activate')],
|
||||
onToggle: (index) {
|
||||
ActivityDone activity = ActivityDone.tutorialBasic;
|
||||
ActivityDone activity = ActivityDone.tutorialExecuteFirstTest;
|
||||
if (Cache().userLoggedIn != null) {
|
||||
if (Cache().userLoggedIn!.sex == "m") {
|
||||
activity = ActivityDone.tutorialBasicChestPress;
|
||||
@ -190,7 +191,11 @@ class SettingsPage extends StatelessWidget with Trans {
|
||||
),
|
||||
onPressed: () => {
|
||||
Track().track(TrackingEvent.terms_of_use),
|
||||
_launchInBrowser("https://workouttest.com/terms-of-use/"),
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return DialogWebBrowser(url: 'https://workouttest.com/terms-of-use/', javascriptEnabled: true);
|
||||
})
|
||||
},
|
||||
),
|
||||
);
|
||||
@ -213,7 +218,11 @@ class SettingsPage extends StatelessWidget with Trans {
|
||||
),
|
||||
onPressed: () => {
|
||||
Track().track(TrackingEvent.data_privacy),
|
||||
_launchInBrowser("https://workouttest.com/privacy/"),
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return DialogWebBrowser(url: 'https://workouttest.com/privacy/', javascriptEnabled: true);
|
||||
})
|
||||
},
|
||||
),
|
||||
);
|
||||
@ -242,18 +251,6 @@ class SettingsPage extends StatelessWidget with Trans {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _launchInBrowser(String url) async {
|
||||
if (await canLaunch(url)) {
|
||||
await launch(
|
||||
url,
|
||||
forceSafariVC: false,
|
||||
forceWebView: false,
|
||||
);
|
||||
} else {
|
||||
throw 'Could not launch $url';
|
||||
}
|
||||
}
|
||||
|
||||
ListTile getVersion() {
|
||||
final String version = Cache().packageInfo != null ? Cache().packageInfo!.version + "+" + Cache().packageInfo!.buildNumber : "";
|
||||
return ListTile(
|
||||
|
442
lib/view/training_evaluation_page.dart
Normal file
442
lib/view/training_evaluation_page.dart
Normal file
@ -0,0 +1,442 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:aitrainer_app/bloc/training_evaluation/training_evaluation_bloc.dart';
|
||||
import 'package:aitrainer_app/bloc/training_plan/training_plan_bloc.dart';
|
||||
import 'package:aitrainer_app/library/custom_icon_icons.dart';
|
||||
import 'package:aitrainer_app/model/cache.dart';
|
||||
import 'package:aitrainer_app/model/exercise_plan_detail.dart';
|
||||
import 'package:aitrainer_app/model/training_evaluation_exercise.dart';
|
||||
import 'package:aitrainer_app/util/trans.dart';
|
||||
import 'package:aitrainer_app/widgets/app_bar_min.dart';
|
||||
import 'package:aitrainer_app/widgets/bottom_nav.dart';
|
||||
import 'package:aitrainer_app/widgets/menu_image.dart';
|
||||
import 'package:aitrainer_app/widgets/victory_widget.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_nsn/modal_progress_hud_nsn.dart';
|
||||
import 'package:timeline_tile/timeline_tile.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class TrainingEvaluationPage extends StatelessWidget with Trans {
|
||||
TrainingEvaluationPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
setContext(context);
|
||||
String imageUrl = "";
|
||||
if (Cache().userLoggedIn == null) {
|
||||
imageUrl = 'asset/image/WT_Results_for_men.jpg';
|
||||
} else if (Cache().userLoggedIn!.sex == "m") {
|
||||
imageUrl = 'asset/image/WT_Results_for_men.jpg';
|
||||
} else {
|
||||
imageUrl = 'asset/image/WT_Results_for_female.jpg';
|
||||
}
|
||||
final HashMap args = ModalRoute.of(context)!.settings.arguments as HashMap;
|
||||
final TrainingPlanBloc trainingPlanBloc = args["bloc"];
|
||||
final dayName = args["day"];
|
||||
return Scaffold(
|
||||
appBar: AppBarMin(
|
||||
back: true,
|
||||
),
|
||||
body: Container(
|
||||
height: double.infinity,
|
||||
width: double.infinity,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage(imageUrl),
|
||||
fit: BoxFit.cover,
|
||||
alignment: Alignment.topCenter,
|
||||
),
|
||||
),
|
||||
child: BlocProvider(
|
||||
create: (context) =>
|
||||
TrainingEvaluationBloc(trainingPlanBloc: trainingPlanBloc, day: dayName)..add(TrainingEvaluationLoad()),
|
||||
child: BlocConsumer<TrainingEvaluationBloc, TrainingEvaluationState>(listener: (context, state) {
|
||||
if (state is TrainingEvaluationError) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(backgroundColor: Colors.orange, content: Text(state.message, style: TextStyle(color: Colors.white))));
|
||||
} else if (state is TrainingEvaluationVictoryReady) {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
builder: (BuildContext context) {
|
||||
return Victory(
|
||||
victory: true,
|
||||
);
|
||||
});
|
||||
}
|
||||
}, builder: (context, state) {
|
||||
final bloc = BlocProvider.of<TrainingEvaluationBloc>(context);
|
||||
return ModalProgressHUD(
|
||||
child: getEvaluationWidgets(bloc, dayName),
|
||||
inAsyncCall: state is TrainingEvaluationLoading,
|
||||
opacity: 0.5,
|
||||
color: Colors.black54,
|
||||
progressIndicator: CircularProgressIndicator(),
|
||||
);
|
||||
}))),
|
||||
bottomNavigationBar: BottomNavigator(bottomNavIndex: 0));
|
||||
}
|
||||
|
||||
Widget getEvaluationWidgets(TrainingEvaluationBloc bloc, String dayName) {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(left: 10, right: 10),
|
||||
child: CustomScrollView(scrollDirection: Axis.vertical, slivers: [
|
||||
SliverAppBar(
|
||||
pinned: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
expandedHeight: 100.0,
|
||||
collapsedHeight: 100,
|
||||
toolbarHeight: 40,
|
||||
automaticallyImplyLeading: false,
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
title: Column(children: [
|
||||
Divider(),
|
||||
Text(bloc.trainingPlanBloc.getMyPlan()!.name!,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 4,
|
||||
style: GoogleFonts.archivoBlack(
|
||||
fontSize: 24,
|
||||
color: Colors.orange[300],
|
||||
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,
|
||||
),
|
||||
],
|
||||
)),
|
||||
Text(t("Training Summary"),
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 3,
|
||||
style: GoogleFonts.archivoBlack(
|
||||
fontSize: 20,
|
||||
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,
|
||||
),
|
||||
],
|
||||
)),
|
||||
]),
|
||||
),
|
||||
),
|
||||
|
||||
//getResultSummary(resultBloc),
|
||||
SliverList(
|
||||
delegate: SliverChildListDelegate([
|
||||
summaryRow("asset/image/pict_time_h.png", "Duration", bloc.duration + " " + t("mins")),
|
||||
Divider(color: Colors.transparent),
|
||||
summaryRow("asset/image/pict_weight_volumen_tonna.png", "Total Lift", bloc.totalLift + " " + t("kg")),
|
||||
Divider(color: Colors.transparent),
|
||||
summaryRow("asset/image/pict_hypertrophy.png", "Maximum Repeats", "${bloc.maxRepeats}" + " " + t("reps")),
|
||||
Divider(color: Colors.transparent),
|
||||
//summaryRow("asset/image/pict_reps_volumen_db.png", "Total Lift Ever", "100 kg"),
|
||||
//Divider(color: Colors.transparent),
|
||||
Text(t("Details"),
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 3,
|
||||
style: GoogleFonts.archivoBlack(
|
||||
fontSize: 20,
|
||||
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,
|
||||
),
|
||||
],
|
||||
)),
|
||||
])),
|
||||
SliverList(
|
||||
delegate: SliverChildListDelegate(
|
||||
getExerciseLists(bloc, dayName),
|
||||
))
|
||||
]));
|
||||
}
|
||||
|
||||
Widget summaryRow(String imageUrl, String title, String data) {
|
||||
return Row(
|
||||
children: [
|
||||
Image.asset(
|
||||
imageUrl,
|
||||
height: 40,
|
||||
),
|
||||
SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
flex: 1,
|
||||
child: Text(t(title),
|
||||
textAlign: TextAlign.start,
|
||||
maxLines: 2,
|
||||
softWrap: true,
|
||||
style: GoogleFonts.archivoBlack(
|
||||
fontSize: 18,
|
||||
color: Colors.orange[400],
|
||||
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,
|
||||
),
|
||||
],
|
||||
))),
|
||||
SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Text(t(data),
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2,
|
||||
softWrap: true,
|
||||
style: GoogleFonts.archivoBlack(
|
||||
fontSize: 20,
|
||||
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,
|
||||
),
|
||||
],
|
||||
))
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> getExerciseLists(TrainingEvaluationBloc bloc, String dayName) {
|
||||
List<Widget> list = [];
|
||||
bloc.evaluationList.forEach((element) {
|
||||
list.add(ExerciseTile(bloc: bloc, exercise: element));
|
||||
});
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class ExerciseTile extends StatelessWidget with Trans {
|
||||
final TrainingEvaluationBloc bloc;
|
||||
final TrainingEvaluationExercise exercise;
|
||||
|
||||
ExerciseTile({required this.bloc, required this.exercise});
|
||||
|
||||
Widget getIndicator(ExercisePlanDetailState state) {
|
||||
if (state.equalsTo(ExercisePlanDetailState.inProgress)) {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(24.0),
|
||||
child: Container(
|
||||
color: Colors.green,
|
||||
child: Icon(
|
||||
CustomIcon.calendar_2,
|
||||
size: 28,
|
||||
color: Colors.white,
|
||||
)));
|
||||
} else if (state.equalsTo(ExercisePlanDetailState.finished)) {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(24.0),
|
||||
child: Container(
|
||||
color: Colors.white,
|
||||
child: Icon(
|
||||
CustomIcon.ok_circled,
|
||||
size: 40,
|
||||
color: Colors.green,
|
||||
)));
|
||||
} else if (state.equalsTo(ExercisePlanDetailState.skipped)) {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(24.0),
|
||||
child: Container(
|
||||
color: Colors.white,
|
||||
child: Icon(
|
||||
CustomIcon.stop_1,
|
||||
size: 40,
|
||||
color: Colors.grey,
|
||||
)));
|
||||
} else if (state.equalsTo(ExercisePlanDetailState.extra)) {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(24.0),
|
||||
child: Container(
|
||||
color: Colors.white,
|
||||
child: Icon(
|
||||
CustomIcon.stopwatch_20,
|
||||
size: 40,
|
||||
color: Colors.blue[800],
|
||||
)));
|
||||
} else {
|
||||
return Image.asset(
|
||||
"asset/image/pict_reps_volumen_db.png",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
setContext(context);
|
||||
bool hasWeight = exercise.type.equalsTo(TrainingEvaluationExerciseType.weightBased);
|
||||
bool skipped = exercise.state.equalsTo(ExercisePlanDetailState.skipped);
|
||||
|
||||
return Container(
|
||||
color: Colors.transparent,
|
||||
child: TimelineTile(
|
||||
alignment: TimelineAlign.manual,
|
||||
lineXY: 0.1,
|
||||
beforeLineStyle: const LineStyle(
|
||||
color: Color(0xffb4f500),
|
||||
thickness: 6,
|
||||
),
|
||||
afterLineStyle: const LineStyle(
|
||||
color: Color(0xffb4f500),
|
||||
thickness: 6,
|
||||
),
|
||||
indicatorStyle: IndicatorStyle(
|
||||
width: 40,
|
||||
height: 40,
|
||||
indicator: getIndicator(exercise.state),
|
||||
),
|
||||
endChild: Container(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Row(children: [
|
||||
Container(
|
||||
width: 120,
|
||||
height: 80,
|
||||
child: MenuImage(
|
||||
imageName: bloc.trainingPlanBloc.getActualImageName(exercise.exerciseTypeId),
|
||||
workoutTreeId: bloc.trainingPlanBloc.getActualWorkoutTreeId(exercise.exerciseTypeId)!,
|
||||
radius: 12,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Expanded(
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: exercise.name,
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.orange[500],
|
||||
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,
|
||||
),
|
||||
],
|
||||
)),
|
||||
TextSpan(text: "\n"),
|
||||
hasWeight
|
||||
? skipped
|
||||
? TextSpan(
|
||||
text: t("skipped") + "!",
|
||||
style: GoogleFonts.inter(fontSize: 12, color: Colors.grey[400], fontWeight: FontWeight.bold))
|
||||
: TextSpan(
|
||||
text: t("One Rep Max") + ": ",
|
||||
style: GoogleFonts.inter(fontSize: 12, color: Colors.yellow[400], fontWeight: FontWeight.bold))
|
||||
: TextSpan(),
|
||||
hasWeight
|
||||
? skipped
|
||||
? TextSpan()
|
||||
: TextSpan(
|
||||
text: exercise.oneRepMax!.toStringAsFixed(0) +
|
||||
" " +
|
||||
t("kg") +
|
||||
" (Max: " +
|
||||
exercise.max1RM!.toStringAsFixed(0) +
|
||||
" " +
|
||||
t("kg") +
|
||||
")",
|
||||
style: GoogleFonts.inter(fontSize: 12, color: Colors.white, fontWeight: FontWeight.bold))
|
||||
: TextSpan(),
|
||||
TextSpan(text: "\n"),
|
||||
hasWeight
|
||||
? skipped
|
||||
? TextSpan()
|
||||
: TextSpan(
|
||||
text: t("Total Lift") + ": ",
|
||||
style: GoogleFonts.inter(fontSize: 12, color: Colors.yellow[400], fontWeight: FontWeight.bold))
|
||||
: TextSpan(),
|
||||
hasWeight
|
||||
? skipped
|
||||
? TextSpan()
|
||||
: TextSpan(
|
||||
text: exercise.totalLift!.toStringAsFixed(0) +
|
||||
" " +
|
||||
t("kg") +
|
||||
" (Max: " +
|
||||
exercise.maxTotalLift!.toStringAsFixed(0) +
|
||||
" " +
|
||||
t("kg") +
|
||||
")",
|
||||
style: GoogleFonts.inter(fontSize: 12, color: Colors.white, fontWeight: FontWeight.bold))
|
||||
: skipped
|
||||
? TextSpan()
|
||||
: TextSpan(
|
||||
text: exercise.repeats!.toStringAsFixed(0) +
|
||||
" " +
|
||||
t("reps") +
|
||||
" (Max: " +
|
||||
exercise.maxRepeats!.toStringAsFixed(0) +
|
||||
" " +
|
||||
t("reps") +
|
||||
")",
|
||||
style: GoogleFonts.inter(fontSize: 12, color: Colors.white, fontWeight: FontWeight.bold)),
|
||||
TextSpan(text: "\n"),
|
||||
skipped
|
||||
? TextSpan()
|
||||
: TextSpan(
|
||||
text: t("Trend") + ": ",
|
||||
style: GoogleFonts.inter(fontSize: 12, color: Colors.yellow[400], fontWeight: FontWeight.bold)),
|
||||
skipped
|
||||
? TextSpan()
|
||||
: TextSpan(
|
||||
text: exercise.trendText,
|
||||
style: GoogleFonts.inter(fontSize: 12, color: Colors.white, fontWeight: FontWeight.bold)),
|
||||
]),
|
||||
)),
|
||||
]),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -93,12 +93,21 @@ class TrainingPlanActivatePage extends StatelessWidget with Trans {
|
||||
|
||||
List<Widget> _getTreeChildren(TrainingPlanBloc bloc) {
|
||||
final List<TrainingPlan> plans = bloc.trainingPlanRepository.getPlansByParent(parentName);
|
||||
final String parentTitle =
|
||||
bloc.trainingPlanRepository.parentTree != null ? bloc.trainingPlanRepository.parentTree!.nameTranslation : "";
|
||||
final String parentDescription =
|
||||
bloc.trainingPlanRepository.parentTree != null && bloc.trainingPlanRepository.parentTree!.descriptionTranslation != null
|
||||
? bloc.trainingPlanRepository.parentTree!.descriptionTranslation!
|
||||
String parentDescription = "";
|
||||
String parentTitle = "";
|
||||
if (bloc.trainingPlanRepository.parentTree != null) {
|
||||
parentTitle = AppLanguage().appLocal.toString() == "en"
|
||||
? bloc.trainingPlanRepository.parentTree!.name
|
||||
: bloc.trainingPlanRepository.parentTree!.nameTranslation;
|
||||
|
||||
if (bloc.trainingPlanRepository.parentTree!.description != null) {
|
||||
parentDescription = bloc.trainingPlanRepository.parentTree!.descriptionTranslation != null
|
||||
? AppLanguage().appLocal.toString() == "en"
|
||||
? bloc.trainingPlanRepository.parentTree!.description!
|
||||
: bloc.trainingPlanRepository.parentTree!.descriptionTranslation!
|
||||
: "";
|
||||
}
|
||||
}
|
||||
List<Widget> listWidget = [];
|
||||
|
||||
Card explanation = Card(
|
||||
@ -237,7 +246,7 @@ class TrainingPlanActivatePage extends StatelessWidget with Trans {
|
||||
? Container(
|
||||
padding: EdgeInsets.only(bottom: 8),
|
||||
child: Text(
|
||||
"Soon! Check back later for the plan details",
|
||||
t("Soon! Check back later for the plan details"),
|
||||
style: GoogleFonts.inter(
|
||||
color: Colors.orange[800],
|
||||
shadows: <Shadow>[
|
||||
|
@ -10,11 +10,11 @@ import 'package:aitrainer_app/widgets/app_bar.dart';
|
||||
import 'package:aitrainer_app/widgets/dialog_common.dart';
|
||||
import 'package:aitrainer_app/widgets/dialog_html.dart';
|
||||
import 'package:aitrainer_app/widgets/menu_image.dart';
|
||||
import 'package:aitrainer_app/widgets/victory_widget.dart';
|
||||
import 'package:aitrainer_app/widgets/weight_control.dart';
|
||||
import 'package:badges/badges.dart';
|
||||
import 'package:extended_tabs/extended_tabs.dart';
|
||||
import 'package:ezanimation/ezanimation.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
@ -33,6 +33,7 @@ class _TrainingPlanExecutePageState extends State<TrainingPlanExecutePage> with
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final HashMap args = HashMap();
|
||||
bloc = BlocProvider.of<TrainingPlanBloc>(context);
|
||||
bloc!.activateDays();
|
||||
setContext(context);
|
||||
@ -52,15 +53,10 @@ class _TrainingPlanExecutePageState extends State<TrainingPlanExecutePage> with
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(backgroundColor: Colors.orange, content: Text(state.message, style: TextStyle(color: Colors.white))));
|
||||
} else if (state is TrainingPlanDayFinished) {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
builder: (BuildContext context) {
|
||||
return Victory(
|
||||
victory: true,
|
||||
);
|
||||
});
|
||||
bloc!.celebrating = false;
|
||||
args["bloc"] = bloc;
|
||||
args["day"] = bloc!.dayNames[bloc!.activeDayIndex];
|
||||
Navigator.of(context).pushNamed('myTrainingEvaluation', arguments: args);
|
||||
} else if (state is TrainingPlanDayReadyToRestart) {
|
||||
if (!bloc!.celebrating) {
|
||||
showCupertinoDialog(
|
||||
@ -100,9 +96,13 @@ class _TrainingPlanExecutePageState extends State<TrainingPlanExecutePage> with
|
||||
}),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
onPressed: () => bloc!.getNext() != null
|
||||
? _ExerciseListState.executeExercise(bloc!, bloc!.getNext()!, context)
|
||||
: Navigator.of(context).pushNamed('home'),
|
||||
onPressed: () => {
|
||||
args["bloc"] = bloc,
|
||||
args["day"] = bloc!.dayNames[bloc!.activeDayIndex],
|
||||
bloc!.getNext() != null
|
||||
? _ExerciseListState.executeExercise(bloc!, bloc!.getNext()!, context)
|
||||
: Navigator.of(context).pushNamed('myTrainingEvaluation', arguments: args),
|
||||
},
|
||||
backgroundColor: Colors.orange[800],
|
||||
icon: Icon(CustomIcon.weight_hanging),
|
||||
label: Text(
|
||||
@ -446,12 +446,14 @@ class _ExerciseListState extends State<ExerciseList> with Trans {
|
||||
bloc.getMyPlan()!.days[widget.dayName] != null &&
|
||||
bloc.getMyPlan()!.days[widget.dayName]!.isNotEmpty) {
|
||||
bloc.getMyPlan()!.days[widget.dayName]!.forEach((element) {
|
||||
tiles.add(GestureDetector(
|
||||
tiles.add(
|
||||
/* GestureDetector(
|
||||
onTap: () => bloc.getNext() != null ? executeExercise(bloc, bloc.getNext()!, context) : Navigator.of(context).pushNamed('home'),
|
||||
child: ExerciseTile(
|
||||
bloc: bloc,
|
||||
detail: element,
|
||||
)));
|
||||
child: */
|
||||
ExerciseTile(
|
||||
bloc: bloc,
|
||||
detail: element,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
@ -507,23 +509,31 @@ class ExerciseTile extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _ExerciseTileState extends State<ExerciseTile> with Trans {
|
||||
final EzAnimation animation = EzAnimation(1.0, 30.0, Duration(seconds: 3), reverseCurve: Curves.easeIn);
|
||||
GestureRecognizer? _tapRecognizer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
animation.start();
|
||||
animation.addStatusListener((status) {
|
||||
if (status == AnimationStatus.completed) {}
|
||||
});
|
||||
|
||||
_tapRecognizer = TapGestureRecognizer()..onTap = _onPlusMinusWeight;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
bool didUpdateWidget(ExerciseTile oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
Future.delayed(Duration(milliseconds: 400)).then((value) => animation.start());
|
||||
return true;
|
||||
void dispose() {
|
||||
if (_tapRecognizer != null) {
|
||||
_tapRecognizer!.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onPlusMinusWeight() {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return WeightControl(
|
||||
initialValue: widget.detail.weight != null ? widget.detail.weight! : 30,
|
||||
onTap: (value) => widget.bloc.add(TrainingPlanWeightChangeRecalculate(detail: widget.detail, weight: value)),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget getIndicator(ExercisePlanDetailState state) {
|
||||
@ -564,6 +574,16 @@ class _ExerciseTileState extends State<ExerciseTile> with Trans {
|
||||
size: 40,
|
||||
color: Colors.grey,
|
||||
)));
|
||||
} else if (state.equalsTo(ExercisePlanDetailState.extra)) {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(24.0),
|
||||
child: Container(
|
||||
color: Colors.white,
|
||||
child: Icon(
|
||||
CustomIcon.stopwatch_20,
|
||||
size: 40,
|
||||
color: Colors.blue[800],
|
||||
)));
|
||||
} else {
|
||||
return Image.asset(
|
||||
"asset/image/pict_reps_volumen_db.png",
|
||||
@ -574,7 +594,6 @@ class _ExerciseTileState extends State<ExerciseTile> with Trans {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
setContext(context);
|
||||
print("detail ${widget.detail}");
|
||||
final ExercisePlanDetailState state = widget.detail.state;
|
||||
final bool done = state.equalsTo(ExercisePlanDetailState.finished) || state.equalsTo(ExercisePlanDetailState.skipped);
|
||||
final String countSerie = widget.detail.set.toString();
|
||||
@ -582,13 +601,13 @@ class _ExerciseTileState extends State<ExerciseTile> with Trans {
|
||||
String weight = widget.detail.weight != null ? widget.detail.weight!.toStringAsFixed(1) : "-";
|
||||
bool isDrop = false;
|
||||
if (widget.detail.weight == -3) {
|
||||
weight = t("DROP");
|
||||
weight = "DROP";
|
||||
isDrop = true;
|
||||
}
|
||||
String restingTime = widget.detail.restingTime == null ? "" : widget.detail.restingTime!.toStringAsFixed(0);
|
||||
bool isTest = false;
|
||||
if (widget.detail.weight != null && widget.detail.weight! == -1) {
|
||||
weight = t("TEST");
|
||||
weight = "TEST";
|
||||
isTest = true;
|
||||
}
|
||||
String repeats = widget.detail.repeats!.toString();
|
||||
@ -741,11 +760,22 @@ class _ExerciseTileState extends State<ExerciseTile> with Trans {
|
||||
: TextSpan(),
|
||||
widget.detail.exerciseType!.unitQuantityUnit != null && !extraExercise
|
||||
? TextSpan(
|
||||
text: weight,
|
||||
text: t(weight),
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 12,
|
||||
))
|
||||
: TextSpan(),
|
||||
widget.detail.exerciseType!.unitQuantityUnit != null && !extraExercise && weight != "TEST" && weight != "DROP"
|
||||
? TextSpan(
|
||||
text: " - +",
|
||||
style: GoogleFonts.archivoBlack(
|
||||
color: Colors.blue,
|
||||
fontSize: 16,
|
||||
),
|
||||
recognizer: _tapRecognizer,
|
||||
mouseCursor: SystemMouseCursors.precise,
|
||||
)
|
||||
: TextSpan(),
|
||||
TextSpan(
|
||||
text: "\n",
|
||||
),
|
||||
@ -796,21 +826,46 @@ class _ExerciseTileState extends State<ExerciseTile> with Trans {
|
||||
]),
|
||||
)),
|
||||
isTest
|
||||
? AnimatedBuilder(
|
||||
animation: animation,
|
||||
builder: (context, snapshot) {
|
||||
return Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||
? Container(
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||
GestureDetector(
|
||||
onTap: () => showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return DialogCommon(
|
||||
warning: false,
|
||||
title: t("Why Test?"),
|
||||
descriptions: t("This is your first exercise after at least 3 weeks."),
|
||||
description2:
|
||||
t("The first exercise will be a test. The following sets will be recalculated base on your test."),
|
||||
description3: t("This is the most optimal way for your development"),
|
||||
text: "OK",
|
||||
onTap: () => Navigator.of(context).pop(),
|
||||
onCancel: () => {
|
||||
Navigator.of(context).pop(),
|
||||
},
|
||||
);
|
||||
}),
|
||||
child: Icon(
|
||||
CustomIcon.question_circle,
|
||||
color: Colors.yellowAccent[700],
|
||||
size: 16,
|
||||
)),
|
||||
]))
|
||||
: isDrop
|
||||
? Container(
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||
GestureDetector(
|
||||
onTap: () => showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return DialogCommon(
|
||||
warning: false,
|
||||
title: t("Why Test?"),
|
||||
descriptions: t("This is your first exercise after at least 3 weeks."),
|
||||
title: t("Drop Set"),
|
||||
descriptions: t(
|
||||
"Execute at least 3 sets with maximum repeats, without resting time, with decreasing the weight."),
|
||||
description2:
|
||||
t("The first exercise will be a test. The following sets will be recalculated base on your test."),
|
||||
description3: t("This is the most optimal way for your development"),
|
||||
t("The goal is to completly exhaust your muscle without lifting a ridiculous weight end the end."),
|
||||
text: "OK",
|
||||
onTap: () => Navigator.of(context).pop(),
|
||||
onCancel: () => {
|
||||
@ -820,39 +875,10 @@ class _ExerciseTileState extends State<ExerciseTile> with Trans {
|
||||
}),
|
||||
child: Icon(
|
||||
CustomIcon.question_circle,
|
||||
color: Colors.yellowAccent[700],
|
||||
color: Colors.orange[200],
|
||||
size: 16,
|
||||
)),
|
||||
]);
|
||||
})
|
||||
: isDrop
|
||||
? AnimatedBuilder(
|
||||
animation: animation,
|
||||
builder: (context, snapshot) {
|
||||
return Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||
GestureDetector(
|
||||
onTap: () => showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return DialogCommon(
|
||||
warning: false,
|
||||
title: t("Drop set"),
|
||||
descriptions: t("Drop set"),
|
||||
description2: t("Recommended method:"),
|
||||
text: "OK",
|
||||
onTap: () => Navigator.of(context).pop(),
|
||||
onCancel: () => {
|
||||
Navigator.of(context).pop(),
|
||||
},
|
||||
);
|
||||
}),
|
||||
child: Icon(
|
||||
CustomIcon.question_circle,
|
||||
color: Colors.orange[200],
|
||||
size: 16,
|
||||
)),
|
||||
]);
|
||||
})
|
||||
]))
|
||||
: Offstage()
|
||||
]),
|
||||
),
|
||||
|
@ -19,7 +19,7 @@ class TrainingPlanExercise extends StatelessWidget with Trans {
|
||||
final CustomerTrainingPlanDetails detail = args['customerTrainingPlanDetails'];
|
||||
// ignore: close_sinks
|
||||
final TrainingPlanBloc bloc = BlocProvider.of<TrainingPlanBloc>(context);
|
||||
|
||||
final bool isDropSet = detail.weight == -3;
|
||||
setContext(context);
|
||||
return Scaffold(
|
||||
appBar: AppBarNav(depth: 1),
|
||||
@ -75,7 +75,7 @@ class TrainingPlanExercise extends StatelessWidget with Trans {
|
||||
backgroundColor: Colors.orange[800],
|
||||
icon: Icon(CustomIcon.save),
|
||||
label: Text(
|
||||
t("Save"),
|
||||
isDropSet ? t("Done") : t("Save"),
|
||||
style: GoogleFonts.inter(fontWeight: FontWeight.bold, fontSize: 16),
|
||||
),
|
||||
),
|
||||
@ -83,147 +83,100 @@ class TrainingPlanExercise extends StatelessWidget with Trans {
|
||||
}
|
||||
|
||||
Widget getExercises(TrainingPlanBloc bloc, CustomerTrainingPlanDetails detail) {
|
||||
final String noTestTextWithWeight = "Please try to execute this exercise with exact weight and repeats what is suggested";
|
||||
final String noTestTextNoWeight = "Please try to execute this exercise with exact repeats what is suggested";
|
||||
final String testMaxRepeats = "Please repeat as much times as you can! MAXIMIZE it!";
|
||||
final String testWeight = "Please take a relative bigger weight and at least 12 times and do your best! MAXIMIZE it!";
|
||||
return ExerciseSave(
|
||||
exerciseName: detail.exerciseType!.nameTranslation,
|
||||
exerciseDescription: detail.exerciseType!.descriptionTranslation,
|
||||
exerciseTask: detail.exerciseType!.unitQuantityUnit != null
|
||||
? detail.weight == -1
|
||||
? t(testWeight)
|
||||
: detail.repeats == -1
|
||||
? t(testMaxRepeats)
|
||||
: t(noTestTextWithWeight)
|
||||
: detail.repeats == -1
|
||||
? t(testMaxRepeats)
|
||||
: noTestTextNoWeight,
|
||||
unit: detail.exerciseType!.unit,
|
||||
unitQuantityUnit: detail.exerciseType!.unitQuantityUnit,
|
||||
hasUnitQuantity: detail.exerciseType!.unitQuantityUnit != null,
|
||||
weight: detail.weight == -1 ? 30 : detail.weight,
|
||||
repeats: detail.repeats == -1 ? 99 : detail.repeats,
|
||||
set: detail.set,
|
||||
exerciseNr: detail.exercises.length + 1,
|
||||
onUnitQuantityChanged: (value) => bloc.add(TrainingPlanWeightChange(weight: value, detail: detail)),
|
||||
onQuantityChanged: (value) => bloc.add(TrainingPlanRepeatsChange(repeats: value.toInt(), detail: detail)),
|
||||
exerciseTypeId: detail.exerciseType!.exerciseTypeId,
|
||||
);
|
||||
int? originalQuantity = bloc.trainingPlanRepository.getOriginalRepeats(bloc.getMyPlan()!.trainingPlanId!, detail);
|
||||
if (detail.weight != -3) {
|
||||
return ExerciseSave(
|
||||
exerciseName: detail.exerciseType!.nameTranslation,
|
||||
exerciseDescription: detail.exerciseType!.descriptionTranslation,
|
||||
exerciseTask: getExerciseTask(detail),
|
||||
unit: detail.exerciseType!.unit,
|
||||
unitQuantityUnit: detail.exerciseType!.unitQuantityUnit,
|
||||
hasUnitQuantity: detail.exerciseType!.unitQuantityUnit != null,
|
||||
weight: detail.weight == -1 ? 0 : detail.weight,
|
||||
repeats: detail.repeats == -1 ? 99 : detail.repeats,
|
||||
set: detail.set,
|
||||
exerciseNr: detail.exercises.length + 1,
|
||||
onUnitQuantityChanged: (value) => bloc.add(TrainingPlanWeightChange(weight: value, detail: detail)),
|
||||
onQuantityChanged: (value) => bloc.add(TrainingPlanRepeatsChange(repeats: value.toInt(), detail: detail)),
|
||||
exerciseTypeId: detail.exerciseType!.exerciseTypeId,
|
||||
originalQuantity: originalQuantity,
|
||||
);
|
||||
} else {
|
||||
return getDropSet(bloc, detail);
|
||||
}
|
||||
}
|
||||
|
||||
/* Widget getExerciseForm(TrainingPlanBloc bloc, CustomerTrainingPlanDetails detail) {
|
||||
String getExerciseTask(CustomerTrainingPlanDetails detail) {
|
||||
String desc = "";
|
||||
if (detail.exerciseType!.unit == "second") {
|
||||
return desc;
|
||||
}
|
||||
if (detail.exerciseType!.unitQuantityUnit != null) {
|
||||
if (detail.weight == -1) {
|
||||
return "Please take a relative bigger weight and at least 12 times and do your best! MAXIMIZE it!";
|
||||
} else if (detail.repeats == -1) {
|
||||
return "Please repeat as much times as you can! MAXIMIZE it!";
|
||||
} else {
|
||||
return "Please try to execute this exercise with exact weight and repeats what is suggested";
|
||||
}
|
||||
} else {
|
||||
if (detail.repeats == -1) {
|
||||
return "Please repeat as much times as you can! MAXIMIZE it!";
|
||||
} else {
|
||||
return "Please try to execute this exercise with exact repeats what is suggested";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Widget getDropSet(TrainingPlanBloc bloc, CustomerTrainingPlanDetails detail) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.only(top: 10, left: 25, right: 25),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.vertical,
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
detail.exerciseType!.nameTranslation,
|
||||
style: GoogleFonts.archivoBlack(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 24,
|
||||
color: Colors.white,
|
||||
shadows: <Shadow>[
|
||||
Shadow(
|
||||
offset: Offset(2.0, 2.0),
|
||||
blurRadius: 6.0,
|
||||
color: Colors.black54,
|
||||
),
|
||||
Shadow(
|
||||
offset: Offset(-3.0, 3.0),
|
||||
blurRadius: 12.0,
|
||||
color: Colors.black54,
|
||||
),
|
||||
],
|
||||
),
|
||||
overflow: TextOverflow.fade,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2,
|
||||
softWrap: true,
|
||||
),
|
||||
Divider(
|
||||
color: Colors.transparent,
|
||||
),
|
||||
Divider(),
|
||||
detail.weight != -1 ? numberPickForm(bloc, detail) : numberPickForm(bloc, detail),
|
||||
],
|
||||
)));
|
||||
}
|
||||
|
||||
Widget numberPickForm(TrainingPlanBloc bloc, CustomerTrainingPlanDetails detail) {
|
||||
final String strTimes = detail.repeats!.toStringAsFixed(1); // : "maximum";
|
||||
|
||||
List<Widget> listWidgets = [
|
||||
GestureDetector(
|
||||
onTap: () => {},
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Colors.yellow[300],
|
||||
),
|
||||
children: [
|
||||
TextSpan(text: t("Please repeat with ")),
|
||||
TextSpan(
|
||||
text: detail.weight!.toStringAsFixed(1) + " " + detail.exerciseType!.unitQuantityUnit!,
|
||||
style: GoogleFonts.inter(
|
||||
decoration: TextDecoration.underline,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.yellow[100],
|
||||
),
|
||||
),
|
||||
TextSpan(text: t("hu_with") + " "),
|
||||
TextSpan(
|
||||
text: strTimes + " ",
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.yellow[100],
|
||||
)),
|
||||
TextSpan(
|
||||
text: t(
|
||||
"times!",
|
||||
)),
|
||||
]),
|
||||
)),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
NumberPickerWidget(
|
||||
minValue: 0,
|
||||
maxValue: 200,
|
||||
initalValue: detail.repeats!,
|
||||
unit: t("reps"),
|
||||
color: Colors.yellow[50]!,
|
||||
onChange: (value) => {}),
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
padding: EdgeInsets.all(0),
|
||||
primary: Colors.white,
|
||||
onSurface: Colors.blueAccent,
|
||||
),
|
||||
onPressed: () => {},
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Image.asset('asset/icon/gomb_orange_c.png', width: 140, height: 60),
|
||||
Text(
|
||||
t("Save"),
|
||||
style: TextStyle(fontSize: 16, color: Colors.white),
|
||||
),
|
||||
],
|
||||
)),
|
||||
],
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage("asset/image/drop_set.png"),
|
||||
//fit: BoxFit.cover,
|
||||
alignment: Alignment.center,
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: listWidgets,
|
||||
padding: EdgeInsets.only(top: 20, left: 30),
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Colors.yellow[300],
|
||||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: t("Drop Set"),
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.yellow[100],
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: "\n",
|
||||
),
|
||||
TextSpan(
|
||||
text: "\n",
|
||||
),
|
||||
TextSpan(text: t("Execute at least 3 sets with maximum repeats, without resting time, with decreasing the weight.")),
|
||||
TextSpan(
|
||||
text: "\n",
|
||||
),
|
||||
TextSpan(
|
||||
text: "\n",
|
||||
),
|
||||
TextSpan(
|
||||
text: t("The goal is to completly exhaust your muscle without lifting a ridiculous weight end the end."),
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.yellow[100],
|
||||
),
|
||||
),
|
||||
],
|
||||
)),
|
||||
);
|
||||
} */
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import 'dart:collection';
|
||||
import 'package:aitrainer_app/bloc/training_plan/training_plan_bloc.dart';
|
||||
import 'package:aitrainer_app/model/cache.dart';
|
||||
import 'package:aitrainer_app/service/logging.dart';
|
||||
import 'package:aitrainer_app/util/enums.dart';
|
||||
import 'package:aitrainer_app/util/track.dart';
|
||||
import 'package:aitrainer_app/util/trans.dart';
|
||||
import 'package:aitrainer_app/widgets/app_bar.dart';
|
||||
@ -112,7 +113,7 @@ class MyTrainingPlans extends StatelessWidget with Trans, Logging {
|
||||
left: 5,
|
||||
textColor: color,
|
||||
onTap: () {
|
||||
// if (Cache().userLoggedIn != null) {
|
||||
Track().track(TrackingEvent.training_plan_open, eventValue: route);
|
||||
if (route == "myTrainingPlanActivate") {
|
||||
HashMap<String, dynamic> args = HashMap();
|
||||
args['parentName'] = parentName;
|
||||
@ -142,7 +143,6 @@ class MyTrainingPlans extends StatelessWidget with Trans, Logging {
|
||||
} else {
|
||||
Navigator.of(context).pushNamed(route);
|
||||
}
|
||||
// }
|
||||
},
|
||||
isLocked: false,
|
||||
);
|
||||
|
@ -292,7 +292,9 @@ class _BMIState extends State<BMI> with Trans {
|
||||
}
|
||||
|
||||
Widget getHeightInput() {
|
||||
if (widget.exerciseBloc.customerRepository.customer!.birthYear! < 2003) {
|
||||
int birthYear = widget.exerciseBloc.birthYear != null ? widget.exerciseBloc.birthYear! : 0;
|
||||
double height = widget.exerciseBloc.height != null ? widget.exerciseBloc.height! : 0;
|
||||
if (birthYear < 2003) {
|
||||
return Flexible(
|
||||
child: TextFormField(
|
||||
focusNode: _nodeText2,
|
||||
@ -308,7 +310,7 @@ class _BMIState extends State<BMI> with Trans {
|
||||
borderSide: BorderSide(color: Colors.white12, width: 0.4),
|
||||
),
|
||||
),
|
||||
initialValue: widget.exerciseBloc.height.toStringAsFixed(0),
|
||||
initialValue: height.toStringAsFixed(0),
|
||||
keyboardType: TextInputType.numberWithOptions(decimal: false),
|
||||
textInputAction: TextInputAction.done,
|
||||
style: GoogleFonts.archivoBlack(fontSize: 20, color: Colors.yellow[300]),
|
||||
@ -327,6 +329,7 @@ class _BMIState extends State<BMI> with Trans {
|
||||
}
|
||||
|
||||
Widget getWeightInput() {
|
||||
double weight = widget.exerciseBloc.weight != null ? widget.exerciseBloc.weight! : 0;
|
||||
return Container(
|
||||
padding: EdgeInsets.only(top: 15, left: 65, right: 65, bottom: 10),
|
||||
alignment: Alignment.center,
|
||||
@ -338,50 +341,7 @@ class _BMIState extends State<BMI> with Trans {
|
||||
width: 10,
|
||||
),
|
||||
Flexible(
|
||||
child:
|
||||
/* SfLinearGauge(
|
||||
minimum: widget.exerciseBloc.weight > 0 ? (widget.exerciseBloc.weight - 10).floor().toDouble() : 40,
|
||||
maximum: widget.exerciseBloc.weight > 0 ? (widget.exerciseBloc.weight + 10).ceil().toDouble() : 150,
|
||||
labelPosition: LinearLabelPosition.outside,
|
||||
tickPosition: LinearElementPosition.outside,
|
||||
markerPointers: [
|
||||
LinearWidgetPointer(
|
||||
value: widget.exerciseBloc.weight,
|
||||
offset: 5,
|
||||
position: LinearElementPosition.outside,
|
||||
markerAlignment: LinearMarkerAlignment.center,
|
||||
child: Container(
|
||||
height: 14,
|
||||
width: 44,
|
||||
color: Colors.transparent,
|
||||
child: Text(widget.exerciseBloc.weight.toStringAsFixed(1),
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
)),
|
||||
),
|
||||
),
|
||||
LinearShapePointer(
|
||||
position: LinearElementPosition.inside,
|
||||
shapeType: LinearShapePointerType.triangle,
|
||||
value: widget.exerciseBloc.weight,
|
||||
height: 45,
|
||||
width: 20,
|
||||
color: Colors.yellow[200],
|
||||
onValueChanged: (value) => {widget.exerciseBloc.add(ExerciseNewWeightChange(value: value))},
|
||||
),
|
||||
],
|
||||
orientation: LinearGaugeOrientation.horizontal,
|
||||
majorTickStyle: LinearTickStyle(length: 20, color: Colors.yellow[50]),
|
||||
axisLabelStyle: TextStyle(fontSize: 12.0, color: Colors.yellow[50]),
|
||||
axisTrackStyle: LinearAxisTrackStyle(
|
||||
color: Colors.cyan,
|
||||
edgeStyle: LinearEdgeStyle.bothFlat,
|
||||
thickness: 1.0,
|
||||
borderColor: Colors
|
||||
.grey)), */
|
||||
TextFormField(
|
||||
child: TextFormField(
|
||||
focusNode: _nodeText1,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: EdgeInsets.only(left: 15, top: 5, bottom: 5),
|
||||
@ -395,7 +355,7 @@ class _BMIState extends State<BMI> with Trans {
|
||||
borderSide: BorderSide(color: Colors.white12, width: 0.4),
|
||||
),
|
||||
),
|
||||
initialValue: widget.exerciseBloc.weight.toStringAsFixed(1),
|
||||
initialValue: weight.toStringAsFixed(1),
|
||||
keyboardType: TextInputType.numberWithOptions(decimal: true),
|
||||
textInputAction: TextInputAction.done,
|
||||
style: GoogleFonts.archivoBlack(fontSize: 20, color: Colors.yellow[300]),
|
||||
|
@ -1,5 +1,4 @@
|
||||
import 'package:aitrainer_app/bloc/exercise_new/exercise_new_bloc.dart';
|
||||
//import 'package:aitrainer_app/library/dropdown_search.dart';
|
||||
import 'package:aitrainer_app/util/app_localization.dart';
|
||||
import 'package:aitrainer_app/model/fitness_state.dart';
|
||||
import 'package:aitrainer_app/util/trans.dart';
|
||||
@ -196,7 +195,12 @@ class _BMRState extends State<BMR> with Trans {
|
||||
}
|
||||
|
||||
Widget getHeightInput() {
|
||||
if (widget.exerciseBloc.customerRepository.customer!.birthYear! < 2003) {
|
||||
int birthYear =
|
||||
widget.exerciseBloc.customerRepository.customer != null && widget.exerciseBloc.customerRepository.customer!.birthYear != null
|
||||
? widget.exerciseBloc.customerRepository.customer!.birthYear!
|
||||
: 0;
|
||||
double height = widget.exerciseBloc.height != null ? widget.exerciseBloc.height! : 0;
|
||||
if (birthYear < 2003) {
|
||||
return Flexible(
|
||||
child: TextFormField(
|
||||
focusNode: _nodeText2,
|
||||
@ -212,7 +216,7 @@ class _BMRState extends State<BMR> with Trans {
|
||||
borderSide: BorderSide(color: Colors.white12, width: 0.4),
|
||||
),
|
||||
),
|
||||
initialValue: widget.exerciseBloc.height.toStringAsFixed(0),
|
||||
initialValue: height.toStringAsFixed(0),
|
||||
keyboardType: TextInputType.numberWithOptions(decimal: false),
|
||||
textInputAction: TextInputAction.done,
|
||||
style: GoogleFonts.archivoBlack(fontSize: 20, color: Colors.yellow[300]),
|
||||
@ -231,6 +235,7 @@ class _BMRState extends State<BMR> with Trans {
|
||||
}
|
||||
|
||||
Widget getBirthyearInput() {
|
||||
int birthYear = widget.exerciseBloc.birthYear != null ? widget.exerciseBloc.birthYear! : 0;
|
||||
return Flexible(
|
||||
child: TextFormField(
|
||||
focusNode: _nodeText3,
|
||||
@ -246,7 +251,7 @@ class _BMRState extends State<BMR> with Trans {
|
||||
borderSide: BorderSide(color: Colors.white12, width: 0.4),
|
||||
),
|
||||
),
|
||||
initialValue: widget.exerciseBloc.birthYear.toStringAsFixed(0),
|
||||
initialValue: birthYear.toStringAsFixed(0),
|
||||
keyboardType: TextInputType.numberWithOptions(decimal: false),
|
||||
textInputAction: TextInputAction.done,
|
||||
style: GoogleFonts.archivoBlack(fontSize: 20, color: Colors.yellow[300]),
|
||||
@ -261,7 +266,10 @@ class _BMRState extends State<BMR> with Trans {
|
||||
}
|
||||
|
||||
Widget getFitnessLevel() {
|
||||
String fitnessLevel = widget.exerciseBloc.fitnessLevel;
|
||||
String? fitnessLevel = widget.exerciseBloc.fitnessLevel;
|
||||
if (fitnessLevel == null) {
|
||||
return Container();
|
||||
}
|
||||
return Container(
|
||||
padding: EdgeInsets.only(left: 65, right: 65),
|
||||
child: DropdownSearch<FitnessState>(
|
||||
@ -281,9 +289,9 @@ class _BMRState extends State<BMR> with Trans {
|
||||
compareFn: (FitnessState i, FitnessState s) => i.isEqual(s),
|
||||
showSelectedItem: true,
|
||||
selectedItem: FitnessItem().getItem(fitnessLevel),
|
||||
itemAsString: (data) => t(data.stateText),
|
||||
itemAsString: (data) => t(data!.stateText),
|
||||
onChanged: (data) {
|
||||
widget.exerciseBloc.add(ExerciseNewFitnessLevelChange(value: data.value));
|
||||
widget.exerciseBloc.add(ExerciseNewFitnessLevelChange(value: data!.value));
|
||||
},
|
||||
dropdownBuilder: _customDropDownItem,
|
||||
popupItemBuilder: _customMenuBuilder,
|
||||
@ -296,8 +304,8 @@ class _BMRState extends State<BMR> with Trans {
|
||||
//items: FitnessItem().toList()));
|
||||
}
|
||||
|
||||
Widget _customMenuBuilder(BuildContext context, FitnessState item, bool isSelected) {
|
||||
bool selected = item.value == widget.exerciseBloc.fitnessLevel;
|
||||
Widget _customMenuBuilder(BuildContext context, FitnessState? item, bool isSelected) {
|
||||
bool selected = item!.value == widget.exerciseBloc.fitnessLevel;
|
||||
return Container(
|
||||
decoration: !selected
|
||||
? BoxDecoration(color: Colors.black54)
|
||||
@ -345,6 +353,7 @@ class _BMRState extends State<BMR> with Trans {
|
||||
}
|
||||
|
||||
Widget getWeightInput() {
|
||||
double weight = widget.exerciseBloc.weight != null ? widget.exerciseBloc.weight! : 0;
|
||||
return Container(
|
||||
padding: EdgeInsets.only(top: 15, left: 35, right: 35, bottom: 10),
|
||||
alignment: Alignment.center,
|
||||
@ -374,7 +383,7 @@ class _BMRState extends State<BMR> with Trans {
|
||||
borderSide: BorderSide(color: Colors.white12, width: 0.4),
|
||||
),
|
||||
),
|
||||
initialValue: widget.exerciseBloc.weight.toStringAsFixed(1),
|
||||
initialValue: weight.toStringAsFixed(1),
|
||||
keyboardType: TextInputType.numberWithOptions(decimal: true),
|
||||
textInputAction: TextInputAction.done,
|
||||
style: GoogleFonts.archivoBlack(fontSize: 20, color: Colors.yellow[300]),
|
||||
|
@ -1,403 +0,0 @@
|
||||
import 'package:aitrainer_app/util/trans.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
import 'package:flutter_html/style.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class DialogGDPR extends StatefulWidget {
|
||||
DialogGDPR({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_DialogPremiumState createState() {
|
||||
return _DialogPremiumState();
|
||||
}
|
||||
}
|
||||
|
||||
const htmlData = """
|
||||
<html>
|
||||
<h1>WorkoutTest Data Policy </h1>
|
||||
<h2><strong>Principle of anonymous data use</strong></h2>
|
||||
In principle, our website can be used without providing personal data. The use of individual services and offers (Workout Test app, workouttest.com website) on our website and in our apps can entail divergent regulations which in this case are explained separately below. The legal basis for data protection can be found in the General Data Protection Regulation (GDPR).
|
||||
|
||||
When you access our website or Apps, some information, such as IP address, is transferred. You are also providing information about the end device used (computer, smartphone, tablet etc.), the browser used (Internet Explorer, Safari, Firefox etc.), time of visit to the website, the so-called referrer and volume of data transferred.
|
||||
|
||||
We cannot use this data to identify an individual user. We only use this information to determine how attractive our offers are and to improve their performance or content, if necessary, and make their design even more appealing to you.
|
||||
|
||||
Please bear in mind, however, that in the case of a static
|
||||
|
||||
IP address, personal identification is possible by RIPE query in individual cases, although we do not perform this. Nevertheless, this website is accessible for both static and dynamic IP addresses assigned.
|
||||
|
||||
|
||||
<h2><strong>Collection and processing of personal data </strong></h2>
|
||||
We only collect personal data if you provide it to us, for example when you contact us, in particular by registering a Workout Test account, placing an order, requesting information or publishing personal data in our Workout Test in your profile or in the feed.
|
||||
|
||||
We use the personal data you provide only to the extent that your data is necessary for rendering or processing our services.
|
||||
|
||||
We store your data for as long as is necessary to achieve the intended purpose or until you delete your account or for as long as legal retention periods require data to be stored. Your data is subsequently deleted in accordance with legal requirements or processing is restricted.
|
||||
|
||||
In the case of use purely for information, i.e. if you do not register or send us information another way, we only collect personal data which your browser transfers to our servers. If you want to view our website, we collect the following data, which we require for technical purposes in order to show you our content and guarantee stability and security (legal basis is a legitimate interest pursuant to Article 6 (1) (f) GDPR).
|
||||
|
||||
In the context of the balance of interests in accordance with Article 6 (1) (f) GDPR, we have considered and weighed our interest in website and app provisioning and your interest in data protection compliant processing of your personal data. As the data below is technically required for the provision of our service in order to offer you our services and also guarantee stability and security, in particular protection against misuse, we have reached the conclusion that, with a state-of-the-art oriented data security guarantee, this data can be processed whereby appropriate consideration will be given to your interest in data protection compliant processing.
|
||||
|
||||
|
||||
|
||||
The collection of data for website provision and the storage of data in log files is imperative for website and operation. Consequently, users may not object to this.
|
||||
<h3><strong>Registering a Workout Test Account </strong></h3>
|
||||
Using our login system, you can create a Workout Test account for yourself that you can use to log in to all of our services (Workout Test, Workouttest.com). In the process, we use cookies – small files – on your browser in order to identify you. All data that you save to your account is stored at a database at the server of Contabo GmbH. Some of our services are only accessible if you have set up your Workout Test account. This includes Workout Test app. We will request the following data when registering (some of it is required). In addition, you must take note of our Data Protection Statement, as well as accept our General Terms and Conditions of Business and Withdrawal Policy.
|
||||
|
||||
|
||||
<h3><strong>Registering with Facebook or Google </strong></h3>
|
||||
We also offer you the opportunity to create your Workout Test account using your Facebook or Google account, or to link your Workout Test account to your Facebook or Google account. You can register or log in to Workout Test using your Facebook or Google account if you simply use Facebook or Google instead of the other options while registering your Workout Test account. You will then be forwarded to Facebook or Google (where you must be logged in or require an account) and receive an explanation of which of your data we need from Facebook or Google, namely your public profile information such as first and last name, gender, and the email address you are using there. This information is required for identification purposes in order to create a secure Workout Test account for you. When you log in using your Facebook account, this allows us to show you which of your friends are already with Workout Test. Your Facebook or Google account and your Workout Test account will be permanently linked using your email address. We store your email information in-house and will send you information using this address as needed. We can also tell that you have logged in using Facebook or Google. As soon as you log in to Facebook or Google, you can log in to Workout Test. We will not submit any information on you to Facebook or Google without your consent.
|
||||
|
||||
Important: We do not record your Facebook or Google login data in any way, and cannot post anything to your Facebook or Google profile without your having expressly consented to this.
|
||||
|
||||
You can learn how Facebook handles privacy settings using Facebook's privacy policy and terms of use; these also include the applicable conditions for the previously specified option of logging in and registering to Workout Test. You can learn how Google handles privacy settings using Google's privacy policy and terms of use; these also include the applicable conditions for the previously specified option of logging in and registering to Workout Test.
|
||||
|
||||
|
||||
<h2><strong>Data collection, processing and use in the context of Workout Test Service </strong></h2>
|
||||
<h2><strong>Community Profile </strong></h2>
|
||||
You need a Workout Test account in order to use our Workout Test Apps. The data collected for this purpose has already been explained above. To provide you with the best Workout Test experience possible, our approach is partly based on publishing specific information relating to our users within the Workout Test community, i.e. even your information. We are going to introduce our program to you in more detail below so that you can decide for yourself whether and which data you want to publish. This introduction includes the following data in particular which is visible to other users:
|
||||
<ul>
|
||||
<li>public profile (photo, first name, surname, nickname, weight, height, personal measurement data, training location, motivation, Workout Test level)</li>
|
||||
<li>fitness (progress, desired objective, skills)</li>
|
||||
<li>training information (duration and type of training unit, total number of training sessions, notes, photo)</li>
|
||||
<li>social media profile (Facebook, Twitter, Instagram)</li>
|
||||
<li>food intake (meal type, notes, photo)</li>
|
||||
<li>followed by/follows (persons who follow the user, persons followed by the user)</li>
|
||||
</ul>
|
||||
You become visible in Workout Test Apps with the data in your public profile. This information helps other users to find you in Workout Test Apps. At the same time, other users can see your name and surname (if provided), your training location, your motivation and your profile photo and they can recognize you using this information if necessary. You have already agreed under our General Terms and Conditions of Business that upon beginning your Workout Test journey and provided that you make no changes to your privacy settings, all Workout Test users will be able to view your profile, your training data, your posts, your training spots, etc., without special consent. This is designed to make it easier to follow you and/or support you during your journey with comments and motivation. If you do not want this anymore, you can set your privacy settings to ‘private’ at any time, which only allows select athletes to access the information referred to above.
|
||||
|
||||
Workout Test Apps save all your successfully completed training units or the meals you have consumed as well as related information such as self- uploaded photos or notes. Other users can see this data and can consider them as an incentive for themselves, leave comments or decide whether they want to follow you.
|
||||
|
||||
Moreover, our Workout Test Apps also provide users with the option to be followed (‘followed by’) to support or encourage them in their training experiences, or to compete with one another. For this purpose, users can be searched for by the name registered in the public profile or, after linking a Workout Test account to a Facebook account, via their Friends list. You will be notified about new followers in the mobile app and by push notification where applicable.
|
||||
|
||||
Consequently, some of your information is available to other users in Workout Test Apps. Our aim is that nobody is left alone with their training. Instead, users’ performance is appreciated and can become an incentive for new members.
|
||||
|
||||
If you do not want to make it possible to link your performance to yourself, you are free to refrain from providing any personal data on your profile or training sessions and linking your account to Facebook. Moreover, you should not save any training photos or enter any notes in this case. For this reason, we have ensured that each user can change their personal information, which can be viewed by other users, and each user is free to use their own name or a fictitious name in their Workout Test Apps.
|
||||
|
||||
|
||||
<h3><strong>Analysis of location data </strong></h3>
|
||||
Within the framework of our Workout Test Apps, in particular, Workout Test Running and Workout Test Training, it is possible to map runs. For this purpose, we need access to location data. This data allows us to calculate distances covered at the relevant time and thereby correctly determine the end for predefined distances or determine distances covered.
|
||||
|
||||
Based on your location, you can also find training spots and users near you, as well as adjust your training plan to your current environment. At training spots, you can check-in after your workout and show the other users in your community where you exercised. To determine the parts of the world in which most users are based, we perform a statistical analysis of your location data. If you edit your saved training, you can remove the selected training spot later and therefore withdraw your consent.
|
||||
|
||||
|
||||
<h2><strong>Access rights </strong></h2>
|
||||
We require these access options and information to ensure the technical function of our app and to provide the services offered with the app, in particular, to access your camera or your photos, to determine your running distances and your activity calories or to send you push-notifications to inform you about new followers or comments. During the installation procedure or before you use the app for the first time, we request permission to access individual functions and information. We will only access these functions with your approval. You can revoke access rights manually in the settings for each operating system. You can find out how this works in the manufacturer instructions for your mobile OS. However, please note that you can only use the app to a limited extent or you cannot use it at all without the relevant approval.
|
||||
|
||||
Before you either use the app for the first time or use a function for the first time, we ask the user for the permissions for the purpose described below:
|
||||
|
||||
|
||||
<h3><strong>Contact form </strong></h3>
|
||||
You have the option of contacting us via our e-mail address or the contact form.
|
||||
|
||||
We will, of course, use the personal data transmitted to us only for the purpose for which you make it available by contacting us.
|
||||
|
||||
The legal basis is in this respect our legitimate interest in accordance with Art. 6 para. 1 sent. 1(f) of the GDPR. If the aim of your request is to conclude a contract (e.g., purchasing a subscription), the legal basis for the processing of the communicated data is also the necessity for providing (pre-) contractual services, in accordance with Art. 6 para. 1 sent. 1(b) of the GDPR.
|
||||
|
||||
If we request entries in our contact form that are required for establishing contact, we have marked them as compulsory (*). Any information without an asterisk is optional. We use this information to put your request into specific terms and handle your concern more effectively. Providing this information is entirely voluntary and with your consent. If the information in question refers to communication channels (for example, e-mail address or phone number), you also give consent for us to contact you via these communication channels to respond to your concern.
|
||||
|
||||
You can, of course, withdraw your declarations of consent at any time with future effect. If you wish to withdraw your consent, please contact the office indicated at the end of this declaration.
|
||||
|
||||
|
||||
<h3><strong>Push notifications as part of the user experience </strong></h3>
|
||||
We require your consent if you wish to receive our push notifications on your mobile iOS device even if the app is not open. Our app only uses push notifications if you have given your explicit consent to these. You can disable push notifications in settings at any time. If you use an Android device, push notifications are permitted automatically unless you disable this in your settings.
|
||||
|
||||
|
||||
|
||||
|
||||
<h3><strong>Push notifications for marketing purposes </strong></h3>
|
||||
We require your consent if you wish to receive our push notifications for marketing purposes on your mobile iOS device even if the app is not open. Our app only uses push notifications for marketing purposes if you have given your explicit consent to these. You can disable push-notifications in settings at any time. If you use an Android device, push notifications are permitted automatically unless you disable this in your settings.
|
||||
|
||||
|
||||
<h3><strong>Apple Health Kit </strong></h3>
|
||||
We use the HealthKit framework from Apple (Apple Inc., 1 Infinite Loop, Cupertino, CA 95014, USA; “Apple”), which provides a central storage location for health and fitness data on the iPhone or Apple Watch and – with the express consent of the user – allows apps to communicate with the HealthKit store in order to access and share these data.
|
||||
|
||||
With your express approval, we will process your heart rate (where appropriate) as well as information about your workout (start and end of training (date)), training duration, type of training, indoor or outdoor), which is obtained through the HealthKit framework in order to track and display your health and fitness activities.
|
||||
|
||||
If you activate the HealthKit framework in your iPhone’s or Apple Watch settings, Workout Test is able to send, with your approval, the calories burned by the activity, route (walking and running), as well as your workouts (start and end of training [date], training duration, type of training, indoor or outdoor) to Apple, so that you can track and display your health and fitness activity.
|
||||
|
||||
New data attributes can be added to the HealthKit framework, which are then displayed in the product and must be approved. You can prevent Apple from accessing your data at any time, and thus prevent it from being shared, by changing your mobile device settings. You can find more information about HealthKit here: [https://developer.apple.com/documentation/healthkit] https://developer.apple.com/documentation/healthkit
|
||||
|
||||
|
||||
<h3><strong>Google Fit </strong></h3>
|
||||
We use Google Fit from Google (Google Ireland Limited, Gordon House, Barrow Street, Dublin 4, Ireland; “Google”), which provides a central storage location for health and fitness data on your Android phone and – with the express consent of the user – allows apps to communicate with Google Fit in order to access and share these data.
|
||||
|
||||
With your express approval, we will send information about your workout (start and end of training (date), training duration, type of training, and name of the workout) to Google Fit, so that you can track and display your health and fitness activity. New data attributes can be added to Google Fit, which is then displayed in the product and must be approved.
|
||||
|
||||
You can prevent Google from accessing your data at any time, and thus prevent it from being shared, by changing your mobile device settings. You can find more information about Google Fit here.
|
||||
|
||||
|
||||
<h3><strong>Cookies, tracking pixels and similar technologies </strong></h3>
|
||||
To improve our web service and make your experience as comfortable as possible, we use cookies, tracking pixels, or similar technologies. Cookies are small text files saved on your computer when you visit our website. They help us recognize your browser as yours. Cookies save information such as your language settings or the duration of your visit to our website. They save data entries you make on the website, like when you log in, so you don’t need to enter your data each time you use our services. Cookies help us to recognize your preferences and adjust our website to your areas of interest.
|
||||
|
||||
Every time our website loads, we record how often it is visited and clicked on by using tags on our website called tracking pixels. These tracking pixels do not interfere with your computer.
|
||||
|
||||
Most browsers accept cookies automatically. If you want to prevent cookies from being saved, you can select the
|
||||
|
||||
‘Accept no cookies’ option in your browser settings. To find out exactly how this works, you can consult your browser manufacturer’s instructions. You can delete cookies that have already been saved on your computer at any time. To find out exactly how this works, you can consult your browser manufacturer’s instructions. If you want to prevent performance cookies and advertising cookies from being saved, you can change your cookie preferences at any time by changing your cookie settings above.
|
||||
|
||||
We use cookies and tracking pixels for different purposes, which also means they have different legal bases and storage periods. You will find more information about that in the following sections:
|
||||
<h3><strong>Required and functional cookies </strong></h3>
|
||||
Some cookies are required for the site to function and cannot be disabled. Without these cookies, you will not be able to view our site properly. We also use functional cookies, which help us to improve our website performance.
|
||||
|
||||
- Analytical tools are used to analyze data based on your browser behaviour in order to improve the functionality and design of our site.
|
||||
|
||||
- Quality assurance tools are used to measure errors presented on a website, to make sure we fix bugs or any issues promptly.
|
||||
|
||||
- A/B testing tools or multivariate testing tools are used to ensure a consistent design of the website and a consistent user experience in the current and subsequent sessions.
|
||||
|
||||
The legal basis is in this respect our legitimate interest in accordance with Art. 6 para. 1 sent. 1(f) GDPR
|
||||
<h3><strong>Performance Cookies and Advertising Cookies </strong></h3>
|
||||
These cookies allow us to analyze site usage so we can measure and improve performance. They are also used by advertising companies to serve ads that are relevant to your interests. These cookies contain a unique key to distinguish individual users’ browsing habits. We also use these cookies to limit the number of times a user sees a particular ad on a website and to measure the effectiveness of a particular campaign. The identifier stored by these cookies is provided by our partners. We cannot use the same identifier in our own systems.
|
||||
|
||||
If you want to prevent performance cookies and advertising cookies from being saved, you can change your cookie preferences at any time by changing your cookie settings above.
|
||||
|
||||
The legal basis for performance cookies and advertising cookies is consent in accordance with Art. 6 para. 1 sent. 1(a) GDPR. Obviously, you can withdraw your declarations of consent for the future at any time. If you no longer agree to us providing your data to the service providers mentioned in our privacy policy, you can opt- out in the cookie settings on our website or within your profile settings.
|
||||
|
||||
Please note: it is possible that you can still see advertisements from Workout Test on third-party platforms even if you do not choose this functionality, but these advertisements are at random and won’t be personalized.
|
||||
<h3><strong>Google Analytics </strong></h3>
|
||||
We use the Google Analytics service from Google Ireland Limited (Gordon House, Barrow Street, Dublin 4, Ireland) to analyze our website visitors. Google uses cookies to track the use of the online product or service by users and the information is generally transferred to a Google server in the USA and stored there.
|
||||
|
||||
Google will use this information on our behalf to evaluate the use of our online products and services by users, to compile reports on the activities within these online products and services and to provide us with further services associated with the use of these online products and services and the use of the internet. Pseudonymous user profiles can be created from the processed data.
|
||||
|
||||
We use Google Analytics only with IP anonymization enabled. This means that Google will truncate the IP address of users within Member States of the European Union or in other states that are party to the Agreement
|
||||
|
||||
on the European Economic Area. Only in exceptional cases will the full IP address be transmitted to a Google server in the USA and truncated there. The IP address transmitted by the user’s browser is not merged with other Google data. Users can prevent cookies from being stored by adjusting the settings to their browser software accordingly. We have made data protection friendly default settings.
|
||||
|
||||
The legal basis for the use of this service is Art. 6 paragraph 1 sentence 1 letter f GDPR. Users can prevent the collection of data generated by cookies by downloading and installing the browser plug-in that is available here. As a guarantee pursuant of Art. 44ff of the General Data Protection Regulation (GDPR), Google has signed the EU standard contractual clauses.
|
||||
|
||||
If you do not wish to be tracked by Google Analytics in the future, you can opt out at any time by writing an email to service@workouttest.com.
|
||||
<h3></h3>
|
||||
<h3><strong>Flurry</strong></h3>
|
||||
We use Flurry (360 3rd Street, Suite 750, San Francisco, CA 94107, US) within our app in order to analyze your app usage behaviour. When you visit our website or app the information listed below is collected and analyzed by Flurry, which uses an identifier (ID) that allows analysis of your use of our services.
|
||||
|
||||
On behalf of us, Flurry will use this information to evaluate your use of the app and to compile reports on the use of the website. This information may be used by us, if activated by you, to send you specific information (so-called push notifications or in-app messages) about Workout Test services or specific advertising. If you do not want to receive any in-app messages, please contact service@workouttest.com. For further information about push-notifications, see here.
|
||||
|
||||
If Flurry transfers personal data to the USA, it does so on the basis of an agreement with the EU standard contractual clauses. The legal basis is Art. 6 para. 1 lit. f) DSGVO. If you do not wish to be tracked by Flurry in the future, you can opt-out at any time by writing an email to service@workouttest.com.
|
||||
|
||||
|
||||
<h3><strong>Google Marketing Services </strong></h3>
|
||||
On our apps we use the marketing and re-marketing services of Google Ireland Limited (Gordon House, Barrow Street, Dublin 4, Ireland) that allow us to display advertisements in a more targeted manner in order to present advertisements of interest to users. Through (re- )marketing ads and products are displayed to users relating to an interest established by activity on other apps within the Google Network. For these purposes, a code is used by Google when our app is accessed and what are referred to as (re-)marketing tags are incorporated into the app. With their help, an individual cookie, i.e. a small file, is stored on the user’s device (comparable technologies may also be used instead of cookies). Cookies can be set by various domains. This file records which apps users have visited, which content they are interested in and which offers have been used. In addition, technical information about the browser and operating system, referring apps, the length of the visit as well as any additional data about the use of the online products and services are stored. The IP address of users is also recorded, although we would like inform you that within the framework of Google Analytics, IP addresses within Member States of the European Union or in other contracting states to the Agreement on the European Economic Area are truncated.
|
||||
|
||||
All user data will only be processed as pseudonymous data. Google does not store any names or email addresses. All displayed ads are therefore not displayed specifically for a person, but for the owner of the cookie. This information is collected by Google and transmitted to and stored by servers in the USA.
|
||||
|
||||
One of the Google marketing services we use is the online advertising program Google AdWords. In the case of Google AdWords, each AdWords customer receives a
|
||||
|
||||
different conversion cookie. Cookies can therefore not be tracked through the apps of AdWords customers. The information collected by the cookie is used to generate conversion statistics for AdWords customers who have opted for conversion tracking. AdWords customers see the total number of users who clicked on their ad and were redirected to a page with a conversion tracking tag. However, they will not receive any information that personally identifies users.
|
||||
|
||||
We may include third-party advertisements based on the Google Marketing Service called DoubleClick. DoubleClick uses cookies to enable Google and its partner apps to place ads based on users’ visits to this app or other apps on the Internet.
|
||||
|
||||
Google services make use of Google’s Tag Manager. For more information about Google’s use of data for marketing purposes, please see the summary page, Google’s privacy policy is available here.
|
||||
|
||||
The legal basis for the use of this service is Article Art. 6 paragraph 1 sentence 1 letter f GDPR. If you wish to object to interest-based advertising by Google marketing services, you can do so using the settings and opt-out options provided by Google. As a guarantee pursuant of Art. 44ff of the General Data Protection Regulation (GDPR), Google has signed the EU standard contractual clauses.
|
||||
|
||||
There is another Google service we use called <em>Google Audience</em>, that allows us to show targeted messages to users within the Google Network (such as Gmail, YouTube, Google Feed, etc.). For this, we may provide Google with a customer list that includes the email address or other data such as the device ID you provide to us when you register. This allows Google to create a profile about your usage patterns in our app and on our website in order to display advertisements in a more targeted manner in order to present Workout Test advertisements of interest to users on other apps and websites within the Google Network. Please find more information about the Google Personalized Advertising Service here: https://support.google.com/adspolicy/answer/143465? hl=en
|
||||
|
||||
The legal basis for our processing is Art. 6 Para. 1 Sentence 1 Letter a GDPR. We only actively provide Google with customer lists that include your email address or other personal data with your consent. Obviously, you can withdraw your declarations of consent for the future at any time. If you no longer agree to us providing your data to Google, you can opt-out in our app-settings. It is possible, that you can then still see advertisements from Workout Test even if you withdrew your consent or did not consent in the first place, but these advertisements on third party platforms are at random
|
||||
|
||||
|
||||
<h3><strong>Facebook Marketing Services </strong></h3>
|
||||
We use the “visitor action pixels” from Facebook Inc. (Menlo Park, California) on our website so that user behavior can be tracked after users have been redirected to the provider’s website by clicking on a Facebook ad. This enables us to measure the effectiveness of Facebook ads for statistical and market research purposes. The data collected in this way is anonymous to us, i.e. we do not see the personal data of individual users. However, this data is stored and processed by Facebook, which is why we are informing you, based on our knowledge of the situation. Facebook may link this information to your Facebook account and also use it for its own promotional purposes, in accordance with Facebook’s Data Usage Policy. You can allow Facebook and its partners to place ads on and off Facebook. A cookie may also be stored on your computer for these purposes. You can object to the collection of your data by Facebook pixel, or to the use of your data for the purpose of displaying Facebook ads.
|
||||
|
||||
We also use Facebook’s Software Development Kit (SDK) within our apps, in order to link various Facebook services with our apps. For example, this enables users to be able to use the Facebook SDK to share content from our apps within their Facebook timeline or to send messages to other Facebook users. Further information about the Facebook SDK within iOS can be found here:
|
||||
|
||||
https://developers.facebook.com/docs/ios. For Android, please refer to: https://developers.facebook.com/docs/android.
|
||||
|
||||
We use the Facebook App Events service though the Facebook SDK to track how many people our advertising campaigns reach, and the use of the Facebook SDK. Facebook merely provides us with an aggregated analysis of user behavior with our app. We have no influence beyond that on the information that will be processed through App Events by Facebook. In our app settings, you can opt out of using App Events for these purposes.
|
||||
|
||||
As a guarantee pursuant of Art. 44ff of the General Data Protection Regulation (GDPR), Facebook has signed the EU standard contractual clauses. The legal basis for this processing is Art. 6 paragraph 1 sentence 1 letter f GDPR.
|
||||
|
||||
If you do not wish to be tracked by Facebook in the future, you can opt out at any time by writing an email to service@workouttest.com.
|
||||
<h3><strong>Facebook Custom Audience </strong></h3>
|
||||
The product Custom Audiences from Facebook (Facebook Custom Audiences 1601 S. California Avenue, Palo Alto, CA, 94304) is also used on the website as part of the usage-based online advertising. Basically, a non- reversible and non-personal checksum (hash value) is generated from your usage data, which can be transmitted to Facebook for analysis and marketing purposes. This is done either by placing a pixel-code from Facebook on our website or by capturing your usage behavior in our app using the Facebook SDK (see section above for a detailed description of these two Facebook services). Alternatively, we may provide Facebook with a customer list that includes the email address you provided to us when you registered. It collects information about your activities on the website (e.g. surfing habits, sub-pages visited, etc.). This allows Facebook to create a profile about your usage patterns in our app and on our website. Your IP address is stored and used for the geographical control of advertising.
|
||||
|
||||
As a guarantee pursuant of Art. 44ff of the General Data Protection Regulation (GDPR), Facebook has signed the EU standard contractual clauses. The legal basis for our processing is Art. 6 Para. 1 Sentence 1 Letter f GDPR.
|
||||
|
||||
For registrations from June 2020: The legal basis for our processing is Art. 6 Para. 1 Sentence 1 Letter a GDPR. We only actively provide Facebook with customer lists that include your email address or other personal data with your consent. Obviously, you can withdraw your declarations of consent for the future at any time.
|
||||
|
||||
If you no longer agree to us providing your data to Facebook, you can opt-out in our app-settings.
|
||||
|
||||
In this context, please note that Facebook may also use
|
||||
|
||||
the data we provide about your usage behavior and your e-mail address for its own purposes. Here you have the opportunity to object to targeting on Facebook. Alternatively, you can contact us directly by email at service@workouttest.com.
|
||||
|
||||
Further information about the purpose and scope of the data collection and the further processing and use of the data, as well as the privacy settings can be found in the Facebook data protection guidelines.
|
||||
<h3></h3>
|
||||
<h3><strong>Firebase by Google </strong></h3>
|
||||
We use the Firebase service from Google Ireland Limited (Gordon House, Barrow Street, Dublin 4, Ireland) in order to derive application behavioral analytics. We use that information to see how users interact with our website and app.
|
||||
|
||||
Firebase is part of the Google Cloud Platform and offers numerous services for developers. Some Firebase services process personal data. In most cases, the personal data is limited to so-called "instance IDs", which are provided with a time stamp. These "Instance IDs" assigned by Firebase are unique and thus allow the linking of different events or processes. This data does not represent personally identifiable information for us, nor do we make any efforts to personalize it subsequently. We process these aggregated data to analyze and optimize usage behavior, for example by evaluating crash reports.
|
||||
|
||||
Currently, we use the following Firebase services:
|
||||
|
||||
Google Analytics for Firebase: Google Analytics uses the data to provide analytics and attribution information. The precise information collected can vary by the device and environment. Google Analytics retains ID-associated data for 60 days, and retains aggregate reporting and campaign data without automatic expiration, unless the Firebase customer changes their retention preference in their Analytics settings or deletes their project. For Analytics for Firebase, Google uses not only the "Instance ID" described above, but also the advertising ID of the end device. You can restrict the use of the advertising ID in the device settings of your mobile device. For Android:
|
||||
|
||||
Settings > Google > Ads > Reset Ad ID For iOS: Settings > Privacy > Advertising > No ad tracking
|
||||
|
||||
Firebase Remote Config: Remote Config uses Instance IDs to select configuration values to return to end-user devices. Firebase retains Instance IDs until the Firebase customer makes an API call to delete the ID. After the call, data is removed from live and backup systems within 180 days.
|
||||
|
||||
Firebase Dynamic Links: Dynamic Links uses device specs on iOS to open newly-installed apps to a specific page or context. Dynamic Links only stores device specs temporarily, to provide the service.
|
||||
|
||||
Firebase Cloud Messaging: Firebase Cloud Messaging is used to transmit push messages or so-called in-app messages (messages that are only displayed within the respective app). A pseudonymized push reference is assigned to the mobile device, which serves as a target for the push messages or in-app messages. The push messages can be deactivated and reactivated at any time in the settings of the mobile device. Firebase Cloud Messaging uses Instance IDs to determine which devices to deliver messages to. Firebase retains Instance IDs until the Firebase customer makes an API call to delete the ID. After the call, data is removed from live and backup systems within 180 days.
|
||||
|
||||
Firebase will use this information on our behalf for the
|
||||
|
||||
above mentioned reasons.
|
||||
|
||||
The legal basis for the use of this service is Art. 6 paragraph 1 sentence 1 letter f GDPR. As a guarantee pursuant of Art. 44ff of the General Data Protection Regulation (GDPR), Google has signed the EU standard contractual clauses.
|
||||
|
||||
|
||||
<h3><strong>YouTube (extended data protection mode) </strong></h3>
|
||||
We also use services from YouTube, LLC 901 Cherry Ave., 94066 San Bruno, CA, USA, a company of Google Inc., Amphitheatre Parkway, Mountain View, CA 94043, USA on our website. In so doing we use the option of “extended data protection mode” provided by YouTube. When you access a page containing an embedded YouTube video, a connection to the YouTube servers is established and the contents are displayed on the Internet page through a notification to your browser. Pursuant to YouTube specifications, in the “extended data protection mode” your data is sent to the YouTube servers only when you view the video. If you are simultaneously logged in to YouTube, this information is assigned to your YouTube member account. You may prevent this by logging out of your member account before visiting our website.
|
||||
|
||||
For more information on data protection in connection with YouTube, please refer to this link.
|
||||
<h2></h2>
|
||||
<h2><strong>Quality assurance </strong></h2>
|
||||
When using our website and apps, data is collected and stored which is used to generate information using pseudonymous usage profiles for purposes of web analysis. These usage profiles serve to analyze visitor behaviour and are evaluated to improve and design our services based on demand. In addition, we measure and analyze technical performance data (e.g. response and load times) and application data (hardware and software used) in order to improve the performance of our products. Cookies are used to do so. These are text files saved on your computer that allow us to analyze how you use our website. The pseudonymous usage profiles are not associated with personal data on the bearer of the pseudonym without the concerned party's express consent. You can object to future data collection and storage for the purpose of web analysis at any time by deactivating cookies in your browser settings. You can find the individual privacy notices for the providers here:
|
||||
|
||||
Flurry, S<span style="font-size: 14px;">entry</span>
|
||||
|
||||
|
||||
|
||||
|
||||
<h3><strong>Social Plugins </strong></h3>
|
||||
This website uses social plugins from providers:
|
||||
|
||||
Facebook (Facebook Inc., 1601 S. California Ave, Palo Alto, CA 94304, USA)
|
||||
Twitter (Twitter Inc., 795 Folsom St., Suite 600, San Francisco, CA 94107, USA)
|
||||
|
||||
These plugins normally collect your data and transmit it to the server of the respective provider.
|
||||
|
||||
After being activated, these plugins also record personal data, such as your IP address, and send this to the server of the respective provider where it is stored. Active social plugins also create a cookie with a unique identifier when accessing the respective website. This allows the provider to create profiles on your usage behavior. This also happens when you are not a member of the social network of the respective provider. If you are a member of the provider's social network, and if you are logged in to the social network while visiting this website, your data and visit information can be associated with your profile on the social network. We cannot influence the exact scope of the data collected on you by the respective provider. Please refer to the privacy policies of the respective social network providers for more detailed information on the scope, manner, and purpose of data processing, and on your rights and setting options to protect your privacy. These can be found in the table above. They are also available at the following addresses:
|
||||
|
||||
Facebook, Twitter
|
||||
<h3><strong>Social media fan pages </strong></h3>
|
||||
Workout Test maintains so-called fan pages with social media providers like Instagram, Facebook (both: Facebook Inc. Menlo Park, California) and Twitter (Twitter Inc., 795 Folsom St., Suite 600, San Francisco, CA 94107, USA) in order to communicate with customers, interested parties, and users who are active there, and to inform them about our products, services, and events. In doing so, the users’ data can be processed outside of the EU. The above-mentioned US providers have signed the EU standard contractual clauses and thus guarantee the observance of European data protection laws.
|
||||
|
||||
In the opinion of the European Court of Justice (ECJ), we are responsible, together with Facebook, for the processing of your personal data. You can find the decision of the ECJ dated June 5, 2018 here.
|
||||
|
||||
A Joint Controller Agreement exists with Facebook Inc. pursuant to Art. 26 GDPR. Facebook Ireland pledges to assume the main responsibility in the context of the General Data Protection Regulation (GDPR) for the processing of Insights data and to fulfill all applicable obligations in the context of the GDPR with reference to the processing of Insights data (including, but not limited to Articles 12 and 13 GDPR, Articles 15 to 22 GDPR, and Articles 32 to 34 GDPR). Facebook Ireland will also make available the essential information of this Page Insights Addendum to the affected parties. Please contact Facebook to assume your rights as affected parties. The Data Policy of Facebook can be found here.
|
||||
|
||||
When using the Facebook fan page, the following data will be collected from you for the purpose of user communication and target group advertising:
|
||||
<ul>
|
||||
<li>user interactions (posts, likes, etc.)</li>
|
||||
<li>Facebook cookies</li>
|
||||
<li>demographic data (e.g., based on information regarding age, place of residence, language, or gender)</li>
|
||||
<li>statistical data on user interactions in aggregated form, that is, without the possibility to relate the information to any particular persons (e.g., page activities, page impressions, page previews, likes, recommendations, articles, videos, page subscriptions, incl. source, times of day)</li>
|
||||
</ul>
|
||||
The usage of personal data for advertising purposes is of particular importance for Facebook. We use the statistics function to find out more about visitors to our fan page. The use of the function enables us to adapt our content to the respective target group. In this way we also use, for example, the demographic information about the users’ age and location, whereby it is not at all possible for us to relate this information to persons.
|
||||
|
||||
In order to provide the social media service in the form of our Facebook fan page and to use the Insight function, Facebook generally saves cookies on the end device of the user. These include session cookies, which are deleted when the browser is closed, and persistent cookies that remain on the end device until they expire or are deleted by the user.
|
||||
|
||||
We use the Facebook Insights function for statistical
|
||||
|
||||
evaluation purposes. In this connection, we receive anonymized data concerning the users of our Facebook fan page. As a result, it is not possible for us to trace them back to your person. For more information, you can refer to the cookie guideline of Facebook.
|
||||
|
||||
The personal data of users are processed on the basis of our justified interest in effectively providing information to users and maintaining communication with the users, as well as for the purposes of statistical evaluation pursuant to Art. 6(I) (f) GDPR.
|
||||
|
||||
|
||||
<h3><strong>Transfer of data to third parties </strong></h3>
|
||||
We only pass your personal data on to third parties if:
|
||||
<ul>
|
||||
<li>you have given your explicit consent to this,</li>
|
||||
<li>forwarding data is necessary for the assertion, exercise or defense of legal claims and there is no reason to assume you have an overriding legitimate interest in your data not being passed on,</li>
|
||||
<li>in the event that we have a legal obligation to forward data, and</li>
|
||||
<li>this is legally permissible and required for the performance of the contractual relationship with you.</li>
|
||||
</ul>
|
||||
In the case of data transfer outside the European Union, the high European level of data protection essentially does not exist. It may be the case with a transfer that an EU Commission adequacy decision in accordance with Article 45 (1) (3) GDPR is not currently in place. This means the EU Commission has not yet decided that the level of data protection in the respective country corresponds to the level of protection in the European Union based on the GDPR. Consequently, we have put the appropriate guarantees referred to above in place. Potential risks, which cannot be ruled out completely in connection with data transfer, are in particular:
|
||||
<ul>
|
||||
<li>your personal data could be processed over and above the intended purpose.</li>
|
||||
<li>Moreover, there is a possibility that you may not be able to exercise your rights in relation to data protection, for example your right of access, to rectification, erasure or data portability, on a consistent basis and enforce these.</li>
|
||||
<li>It may also be highly likely that data is processed incorrectly and in quantitative and qualitative terms, the protection of personal data fails to meet the requirements of the GDPR in full.</li>
|
||||
</ul>
|
||||
|
||||
<h2><strong>Your rights </strong></h2>
|
||||
<h3><strong>Information on the rights of data subjects </strong></h3>
|
||||
Each data subject has the right of access in accordance with Article 15 GDPR, the right to rectification in accordance with Article 16 GDPR, the right to erasure in accordance with Article 17 GDPR, the right to restriction of processing in accordance with Article 18 GDPR, the right to object in Article 21 GDPR and the right to data portability in Article 20 GDPR. The limitations according to Articles 34 and 35 BDSG apply to the right of access and to the right to erasure.
|
||||
|
||||
|
||||
<h3></h3>
|
||||
<h3><strong>Information on right the to object in the case of balance of interests </strong></h3>
|
||||
If we process your personal data based on a balance of interests, you can object to such processing. If you exercise this right to object, please state the reasons why we should not process your data as we have described. If your objection is justified, we will review the situation and either stop or adjust data processing or explain our compelling legitimate reasons for processing to you.
|
||||
|
||||
|
||||
|
||||
<strong>Information on withdrawal of consent </strong>
|
||||
|
||||
You can withdraw your consent with us to process personal data at any time. This also applies to withdrawals of a declaration of consent that were given to us before the General Data Protection Regulation came into effect, i.e. before May 25, 2018. Please note that this withdrawal will only apply prospectively. This does not affect processing that took place prior to a withdrawal.
|
||||
|
||||
|
||||
<h3><strong>Information on right the to object in the case of balance of interests </strong></h3>
|
||||
If we process your personal data based on a balance of interests, you can object to such processing. If you exercise this right to object, please state the reasons why we should not process your data as we have described. If your objection is justified, we will review the situation and either stop or adjust data processing or explain our compelling legitimate reasons for processing to you.
|
||||
</html>
|
||||
""";
|
||||
|
||||
class _DialogPremiumState extends State<DialogGDPR> with Trans {
|
||||
bool isStart = true;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
setContext(context);
|
||||
return Dialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(31),
|
||||
),
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.transparent,
|
||||
child: contentBox(context),
|
||||
);
|
||||
}
|
||||
|
||||
contentBox(context) {
|
||||
return Stack(children: <Widget>[
|
||||
SingleChildScrollView(
|
||||
padding: EdgeInsets.only(top: 15),
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 24, right: 20, bottom: 30),
|
||||
margin: EdgeInsets.only(top: 30),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
boxShadow: [BoxShadow(color: Colors.black, offset: Offset(0, 10), blurRadius: 10)],
|
||||
image: DecorationImage(
|
||||
image: AssetImage('asset/image/WT_light_background.jpg'),
|
||||
fit: BoxFit.cover,
|
||||
alignment: Alignment.center,
|
||||
),
|
||||
),
|
||||
child: Html(data: htmlData,
|
||||
//Optional parameters:
|
||||
style: {
|
||||
"html": Style(
|
||||
backgroundColor: Colors.transparent,
|
||||
),
|
||||
}),
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: FloatingActionButton(
|
||||
backgroundColor: Colors.orange,
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text("OK"),
|
||||
)),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: CircleAvatar(
|
||||
backgroundColor: Colors.transparent,
|
||||
radius: 28,
|
||||
child: Text(
|
||||
"X",
|
||||
style: GoogleFonts.archivoBlack(fontSize: 32, color: Colors.grey[400]),
|
||||
),
|
||||
)),
|
||||
]);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
}
|
28
lib/widgets/dialog_web_browser.dart
Normal file
28
lib/widgets/dialog_web_browser.dart
Normal file
@ -0,0 +1,28 @@
|
||||
import 'package:aitrainer_app/util/trans.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:web_browser/web_browser.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class DialogWebBrowser extends StatelessWidget with Trans {
|
||||
final String url;
|
||||
final bool javascriptEnabled;
|
||||
DialogWebBrowser({Key? key, required this.url, required this.javascriptEnabled}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
setContext(context);
|
||||
return Dialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(31),
|
||||
),
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.transparent,
|
||||
child: SafeArea(
|
||||
child: WebBrowser(
|
||||
initialUrl: url,
|
||||
javascriptEnabled: javascriptEnabled,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,13 +1,19 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:aitrainer_app/bloc/tutorial/tutorial_bloc.dart';
|
||||
import 'package:aitrainer_app/library/custom_icon_icons.dart';
|
||||
import 'package:aitrainer_app/util/trans.dart';
|
||||
import 'package:aitrainer_app/widgets/time_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:keyboard_actions/keyboard_actions.dart';
|
||||
import 'package:keyboard_actions/keyboard_actions_config.dart';
|
||||
import 'package:stop_watch_timer/stop_watch_timer.dart';
|
||||
import 'package:wakelock/wakelock.dart';
|
||||
|
||||
import 'dialog_common.dart';
|
||||
import 'dialog_html.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
@ -26,6 +32,7 @@ class ExerciseSave extends StatefulWidget {
|
||||
final int? repeats;
|
||||
final int? set;
|
||||
final int? exerciseNr;
|
||||
final int? originalQuantity;
|
||||
|
||||
ExerciseSave(
|
||||
{required this.onQuantityChanged,
|
||||
@ -41,7 +48,8 @@ class ExerciseSave extends StatefulWidget {
|
||||
this.weight,
|
||||
this.repeats,
|
||||
this.set,
|
||||
this.exerciseNr});
|
||||
this.exerciseNr,
|
||||
this.originalQuantity});
|
||||
@override
|
||||
_ExerciseSaveState createState() => _ExerciseSaveState();
|
||||
}
|
||||
@ -65,7 +73,7 @@ class _ExerciseSaveState extends State<ExerciseSave> with Trans {
|
||||
initState() {
|
||||
super.initState();
|
||||
_controller1.text = widget.weight == null
|
||||
? "30"
|
||||
? "0"
|
||||
: widget.weight! % widget.weight!.round() == 0
|
||||
? widget.weight!.toStringAsFixed(0)
|
||||
: widget.weight!.toStringAsFixed(1);
|
||||
@ -81,8 +89,35 @@ class _ExerciseSaveState extends State<ExerciseSave> with Trans {
|
||||
}
|
||||
});
|
||||
if (widget.unit == "second") {
|
||||
stopWatchTimer.rawTime.listen((value) => widget.onQuantityChanged((value / 1000)));
|
||||
stopWatchTimer.rawTime.listen((value) => {
|
||||
if (value > 0)
|
||||
{
|
||||
widget.onQuantityChanged((value / 1000)),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
SchedulerBinding.instance!.addPostFrameCallback((_) {
|
||||
final TutorialBloc bloc = BlocProvider.of<TutorialBloc>(context);
|
||||
if (bloc.actualCheck == "directTest") {
|
||||
Timer(
|
||||
Duration(milliseconds: 2000),
|
||||
() => {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return DialogCommon(
|
||||
title: t("Attention"),
|
||||
descriptions: t(widget.exerciseTask),
|
||||
text: "OK",
|
||||
onTap: () => Navigator.of(context).pop(),
|
||||
onCancel: () => Navigator.of(context).pop(),
|
||||
);
|
||||
})
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@ -137,6 +172,22 @@ class _ExerciseSaveState extends State<ExerciseSave> with Trans {
|
||||
);
|
||||
}
|
||||
|
||||
String getTimeGoal(int? originalQuantity) {
|
||||
if (originalQuantity == null) {
|
||||
return "";
|
||||
} else if (originalQuantity == -1) {
|
||||
return t("Goal") + ": Max";
|
||||
} else {
|
||||
int mins = (originalQuantity / 60).floor();
|
||||
int secs = originalQuantity % 60;
|
||||
String seconds = "";
|
||||
if (secs > 0) {
|
||||
seconds = secs.toStringAsFixed(0) + " " + t("secs");
|
||||
}
|
||||
return t("Goal") + ": " + mins.toStringAsFixed(0) + " " + t("mins") + " " + seconds;
|
||||
}
|
||||
}
|
||||
|
||||
Widget getExerciseWidget() {
|
||||
return KeyboardActions(
|
||||
config: _buildConfig(context),
|
||||
@ -171,18 +222,9 @@ class _ExerciseSaveState extends State<ExerciseSave> with Trans {
|
||||
SizedBox(
|
||||
height: 15,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10, left: 55, right: 55),
|
||||
child: Text(
|
||||
widget.exerciseDescription,
|
||||
style: GoogleFonts.inter(fontSize: 12, color: Colors.yellow[300]),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.fade,
|
||||
softWrap: true,
|
||||
)),
|
||||
InkWell(
|
||||
child: Text(
|
||||
t("More »"),
|
||||
t("Exercise descripton") + " »",
|
||||
style: GoogleFonts.inter(fontSize: 12, color: Colors.blue[200]),
|
||||
),
|
||||
onTap: () => {
|
||||
@ -223,6 +265,29 @@ class _ExerciseSaveState extends State<ExerciseSave> with Trans {
|
||||
Divider(
|
||||
color: Colors.transparent,
|
||||
),
|
||||
widget.unit == "second"
|
||||
? Text(
|
||||
getTimeGoal(widget.originalQuantity),
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 24,
|
||||
color: Colors.yellow[400],
|
||||
fontWeight: FontWeight.bold,
|
||||
shadows: <Shadow>[
|
||||
Shadow(
|
||||
offset: Offset(2.0, 2.0),
|
||||
blurRadius: 6.0,
|
||||
color: Colors.black54,
|
||||
),
|
||||
Shadow(
|
||||
offset: Offset(-3.0, 3.0),
|
||||
blurRadius: 12.0,
|
||||
color: Colors.black54,
|
||||
),
|
||||
],
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
)
|
||||
: Offstage(),
|
||||
columnQuantityUnit(),
|
||||
Divider(
|
||||
color: Colors.transparent,
|
||||
@ -298,7 +363,6 @@ class _ExerciseSaveState extends State<ExerciseSave> with Trans {
|
||||
return Column(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10, left: 55, right: 55),
|
||||
//padding: const EdgeInsets.only(bottom: 0),
|
||||
child: StreamBuilder<int>(
|
||||
stream: stopWatchTimer.rawTime,
|
||||
initialData: stopWatchTimer.rawTime.value,
|
||||
@ -316,7 +380,6 @@ class _ExerciseSaveState extends State<ExerciseSave> with Trans {
|
||||
]);
|
||||
})),
|
||||
Padding(
|
||||
//padding: const EdgeInsets.all(2),
|
||||
padding: const EdgeInsets.only(top: 10, left: 25, right: 25),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
@ -372,7 +435,11 @@ class _ExerciseSaveState extends State<ExerciseSave> with Trans {
|
||||
Divider(),
|
||||
Divider(),
|
||||
Text(t("Or type the time manually:"), style: GoogleFonts.inter(color: Colors.white)),
|
||||
TimePickerWidget(onChange: (value) => widget.onQuantityChanged((value)))
|
||||
TimePickerWidget(
|
||||
onChange: (value) => {
|
||||
widget.onQuantityChanged(value),
|
||||
},
|
||||
)
|
||||
]);
|
||||
}
|
||||
Widget row = Container(
|
||||
|
@ -1,5 +1,3 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:aitrainer_app/bloc/session/session_bloc.dart';
|
||||
import 'package:aitrainer_app/bloc/settings/settings_bloc.dart';
|
||||
import 'package:aitrainer_app/model/cache.dart';
|
||||
|
@ -25,8 +25,6 @@ import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
import 'dialog_html.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class MenuPageWidget extends StatefulWidget {
|
||||
int? parent;
|
||||
@ -288,9 +286,9 @@ class _MenuPageWidgetState extends State<MenuPageWidget> with Trans, Logging {
|
||||
backgroundColor: Colors.orange,
|
||||
content: Text(AppLocalizations.of(context)!.translate('Please log in'), style: TextStyle(color: Colors.white))));
|
||||
} else {
|
||||
Track().track(TrackingEvent.search, eventValue: value.exerciseType!.name);
|
||||
Track().track(TrackingEvent.search, eventValue: value!.exerciseType!.name);
|
||||
menuBloc.ability = ExerciseAbility.oneRepMax;
|
||||
Navigator.of(context).popAndPushNamed('exerciseNewPage', arguments: value.exerciseType);
|
||||
Navigator.of(context).pushNamed('exerciseNewPage', arguments: value.exerciseType);
|
||||
}
|
||||
},
|
||||
),
|
||||
|
@ -26,7 +26,7 @@ class SearchBarStream {
|
||||
// ignore: must_be_immutable
|
||||
class MenuSearchBar extends StatelessWidget {
|
||||
final List<WorkoutMenuTree> listItems;
|
||||
final Function(WorkoutMenuTree) onFind;
|
||||
final Function(WorkoutMenuTree?) onFind;
|
||||
bool showIcon;
|
||||
MenuSearchBar({required this.listItems, required this.onFind, this.showIcon = true});
|
||||
|
||||
@ -43,7 +43,7 @@ class MenuSearchBar extends StatelessWidget {
|
||||
// ignore: must_be_immutable
|
||||
class AnimatedSearch extends StatefulWidget {
|
||||
final List<WorkoutMenuTree> listItems;
|
||||
final Function(WorkoutMenuTree) onFind;
|
||||
final Function(WorkoutMenuTree?) onFind;
|
||||
bool showIcon = true;
|
||||
|
||||
AnimatedSearch({required this.listItems, required this.onFind, required this.showIcon});
|
||||
@ -118,7 +118,7 @@ class _AnimatedSearch extends State<AnimatedSearch> {
|
||||
// ignore: must_be_immutable
|
||||
class Search extends StatelessWidget with Trans {
|
||||
final List<WorkoutMenuTree> listItems;
|
||||
final Function(WorkoutMenuTree) onFind;
|
||||
final Function(WorkoutMenuTree?) onFind;
|
||||
|
||||
Search({required this.listItems, required this.onFind});
|
||||
|
||||
@ -172,7 +172,7 @@ class Search extends StatelessWidget with Trans {
|
||||
child: ListTile(
|
||||
selected: isSelected,
|
||||
title: Text(
|
||||
item.name,
|
||||
item!.name,
|
||||
style: GoogleFonts.inter(color: Colors.yellow[300], fontSize: 16),
|
||||
),
|
||||
subtitle: Text(
|
||||
|
@ -1,6 +1,9 @@
|
||||
import 'package:aitrainer_app/bloc/tutorial/tutorial_bloc.dart';
|
||||
import 'package:aitrainer_app/model/cache.dart';
|
||||
import 'package:aitrainer_app/model/exercise_type.dart';
|
||||
import 'package:aitrainer_app/service/logging.dart';
|
||||
import 'package:aitrainer_app/util/trans.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
@ -28,6 +31,12 @@ class TutorialWidget with Trans, Logging {
|
||||
print("Action is null");
|
||||
return;
|
||||
}
|
||||
if (bloc.actualCheck == "directTest") {
|
||||
print("DirectTest");
|
||||
tooltip!.close();
|
||||
directTest(bloc);
|
||||
return;
|
||||
}
|
||||
setContext(context);
|
||||
if (tooltip != null && tooltip!.isOpen) {
|
||||
tooltip!.rebuild();
|
||||
@ -161,4 +170,66 @@ class TutorialWidget with Trans, Logging {
|
||||
|
||||
tooltip!.show(context);
|
||||
}
|
||||
|
||||
void directTest(TutorialBloc bloc) {
|
||||
ExerciseType? exerciseType;
|
||||
showCupertinoDialog(
|
||||
useRootNavigator: true,
|
||||
context: context,
|
||||
builder: (_) => CupertinoAlertDialog(
|
||||
title: Html(data: bloc.actualText!,
|
||||
//Optional parameters:
|
||||
style: {
|
||||
"p": Style(
|
||||
color: Colors.indigo,
|
||||
fontSize: FontSize(16),
|
||||
padding: const EdgeInsets.all(4),
|
||||
),
|
||||
}),
|
||||
content: Column(children: [
|
||||
Divider(),
|
||||
]),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(t("Chest Press")),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
exerciseType = Cache().getExerciseTypeById(37);
|
||||
if (exerciseType != null) {
|
||||
Navigator.of(context).popAndPushNamed('exerciseNewPage', arguments: exerciseType);
|
||||
}
|
||||
}),
|
||||
TextButton(
|
||||
child: Text(t("Olympic Squat")),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
exerciseType = Cache().getExerciseTypeById(145);
|
||||
if (exerciseType != null) {
|
||||
Navigator.of(context).popAndPushNamed('exerciseNewPage', arguments: exerciseType);
|
||||
}
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text(t("Pushups")),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
exerciseType = Cache().getExerciseTypeById(33);
|
||||
if (exerciseType != null) {
|
||||
Navigator.of(context).popAndPushNamed('exerciseNewPage', arguments: exerciseType);
|
||||
}
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text(t("Plank")),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
exerciseType = Cache().getExerciseTypeById(53);
|
||||
if (exerciseType != null) {
|
||||
Navigator.of(context).popAndPushNamed('exerciseNewPage', arguments: exerciseType);
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
));
|
||||
}
|
||||
}
|
||||
|
112
lib/widgets/weight_control.dart
Normal file
112
lib/widgets/weight_control.dart
Normal file
@ -0,0 +1,112 @@
|
||||
import 'package:aitrainer_app/util/trans.dart';
|
||||
import 'package:aitrainer_app/widgets/number_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
class WeightControl extends StatefulWidget {
|
||||
final Function(double) onTap;
|
||||
final double initialValue;
|
||||
const WeightControl({required this.onTap, required this.initialValue});
|
||||
@override
|
||||
_UnitQuantityControlState createState() => _UnitQuantityControlState();
|
||||
}
|
||||
|
||||
class _UnitQuantityControlState extends State<WeightControl> with Trans {
|
||||
double? changedValue;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
setContext(context);
|
||||
return Dialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(31),
|
||||
),
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.transparent,
|
||||
child: contentBox(context),
|
||||
);
|
||||
}
|
||||
|
||||
contentBox(context) {
|
||||
return Stack(alignment: AlignmentDirectional.topStart, children: <Widget>[
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 24, right: 20, bottom: 30),
|
||||
margin: EdgeInsets.only(top: 30),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
boxShadow: [BoxShadow(color: Colors.black, offset: Offset(0, 10), blurRadius: 10)],
|
||||
image: DecorationImage(
|
||||
image: AssetImage('asset/image/WT_results_background.jpg'),
|
||||
fit: BoxFit.cover,
|
||||
alignment: Alignment.center,
|
||||
),
|
||||
),
|
||||
child: Column(mainAxisSize: MainAxisSize.min, children: [
|
||||
Text(
|
||||
t("Change the weight to"),
|
||||
textAlign: TextAlign.center,
|
||||
style: GoogleFonts.archivoBlack(
|
||||
fontSize: 24,
|
||||
color: Colors.yellow[100],
|
||||
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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
NumberPickerWidget(
|
||||
minValue: (widget.initialValue - 30).round(),
|
||||
maxValue: (widget.initialValue + 30).round(),
|
||||
initalValue: widget.initialValue.round(),
|
||||
unit: t("kg"),
|
||||
color: Colors.yellow[50]!,
|
||||
onChange: (value) => {
|
||||
setState(() {
|
||||
changedValue = value;
|
||||
})
|
||||
}),
|
||||
Align(
|
||||
alignment: Alignment.center,
|
||||
child: GestureDetector(
|
||||
onTap: () => {
|
||||
if (changedValue == null)
|
||||
{
|
||||
changedValue = widget.initialValue,
|
||||
},
|
||||
Navigator.of(context).pop(),
|
||||
widget.onTap(changedValue!),
|
||||
},
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Image.asset('asset/icon/gomb_orange_c.png', width: 100, height: 45),
|
||||
Text(
|
||||
t("OK"),
|
||||
style: TextStyle(fontSize: 16, color: Colors.white),
|
||||
),
|
||||
],
|
||||
))),
|
||||
])),
|
||||
GestureDetector(
|
||||
onTap: () => Navigator.of(context).pop(),
|
||||
child: CircleAvatar(
|
||||
backgroundColor: Colors.transparent,
|
||||
radius: 28,
|
||||
child: Text(
|
||||
"X",
|
||||
style: GoogleFonts.archivoBlack(fontSize: 32, color: Colors.white54),
|
||||
),
|
||||
)),
|
||||
]);
|
||||
}
|
||||
}
|
58
pubspec.lock
58
pubspec.lock
@ -239,6 +239,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
csp:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: csp
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.1"
|
||||
csslib:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -679,6 +686,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.4.0"
|
||||
kind:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: kind
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.5.2"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -903,6 +917,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.2.1"
|
||||
protobuf:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: protobuf
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
provider:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -980,6 +1001,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "5.1.0-beta.1"
|
||||
share:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: share
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.4"
|
||||
shared_preferences:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@ -1237,6 +1265,20 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
universal_html:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: universal_html
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.8"
|
||||
universal_io:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: universal_io
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.4"
|
||||
upgrader:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -1245,7 +1287,7 @@ packages:
|
||||
source: hosted
|
||||
version: "3.3.0"
|
||||
url_launcher:
|
||||
dependency: "direct main"
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher
|
||||
url: "https://pub.dartlang.org"
|
||||
@ -1370,6 +1412,20 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
web_browser:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: web_browser
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.5.0"
|
||||
web_node:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_node
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.1"
|
||||
web_socket_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -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.19+90
|
||||
version: 1.1.20+91
|
||||
|
||||
environment:
|
||||
sdk: ">=2.12.0 <3.0.0"
|
||||
@ -55,9 +55,9 @@ dependencies:
|
||||
carousel_slider: ^4.0.0-nullsafety.0
|
||||
convex_bottom_bar: ^3.0.0
|
||||
flutter_app_badger: ^1.2.0
|
||||
url_launcher: ^6.0.3
|
||||
extended_tabs: ^2.2.0
|
||||
upgrader: ^3.3.0
|
||||
web_browser: ^0.5.0
|
||||
|
||||
firebase_core: ^1.2.0
|
||||
firebase_analytics: ^8.1.0
|
||||
@ -159,6 +159,7 @@ flutter:
|
||||
- asset/image/button_apple.png
|
||||
- asset/image/button_google.png
|
||||
- asset/image/continue.png
|
||||
- asset/image/drop_set.png
|
||||
- asset/image/lock.png
|
||||
- asset/image/add_test.png
|
||||
- asset/image/testemfejl400x400.jpg
|
||||
@ -185,7 +186,8 @@ flutter:
|
||||
- asset/image/kupa.png
|
||||
- asset/image/test_picto_m.png
|
||||
- asset/image/test_picto_w.png
|
||||
|
||||
- asset/image/pict_1rm.png
|
||||
- asset/image/pict_history.png
|
||||
- asset/image/pict_calorie.png
|
||||
- asset/image/pict_development_by_bodypart_percent.png
|
||||
- asset/image/pict_distance_m.png
|
||||
|
Loading…
Reference in New Issue
Block a user