WT 1.1.18+4 A/B Test sales page

This commit is contained in:
bossanyit 2021-06-05 15:39:12 +02:00
parent d5deaf48a9
commit 0ca3b71c03
48 changed files with 734 additions and 1223 deletions

View File

@ -454,5 +454,13 @@
"Do you want to override it with": "Do you want to override it with",
"Calculated Weight": "Calculated Weight",
"The weight is based on your previuos tests - if they exist.": "The weight is based on your previuos tests - if they exist.",
"If it does not exist, your very first exercise will be a test.": "If it does not exist, your very first exercise will be a test."
"If it does not exist, your very first exercise will be a test.": "If it does not exist, your very first exercise will be a test.",
"Tap on the button below the reach all premium content!": "Tap on the button below the reach all premium content!",
"Enjoy also this premium feature": "Enjoy also this premium feature",
"to activate all available training programs.": "to activate all available training programs.",
"because only that way can we generated the training plan for you.": "because only that way can we generate the personalized training plan for you.",
"This is a premium function": "This is a premium function",
"because only that way can we show you your exercises, results and evaluations.": "because only that way can we show you your exercises, results and evaluations.",
"because only that way can we show you the personalized development diagrams and analysises": "because only that way can we show you the personalized development diagrams and analysises",
"because only in that way can you begin to execute a training plan": "because only in that way can you begin to execute a training plan"
}

View File

@ -279,7 +279,7 @@
"feature is reachable after you finished": "funkció elérhető számodra, miután teljesítetted",
"100% test circles": "100%-os teszt-köröd",
"Keep testing": "Folytasd a tesztelést",
"Enjoy also this premium fetaure to show all old evaluation data of your successful exercises.": "Élvezd ezt a prémium funkciót is, amely megjeleníti a korábbi gyakorlatok teljes kiértékelését",
"Enjoy also this premium feature to show all old evaluation data of your successful exercises.": "Élvezd ezt a prémium funkciót is, amely megjeleníti a korábbi gyakorlatok teljes kiértékelését",
"Please define your Exercise Plan": "Kérlek készíts edzéstervet!",
"Go to: 'Training Plan' - 'Edit My Custom Plan'": "Menj a 'Edzéstervem' - 'Egyéni edzésterv' menübe",
"Jump there »": "Vigyél oda »",
@ -452,5 +452,13 @@
"Do you want to override it with": "Ezt az edzéstervet akarod ezentúl használni?",
"Calculated Weight": "Kalkulált súly",
"The weight is based on your previuos tests - if they exist.": "A súlyt kikalkuláltuk az előző teszted alapján - ha létezik.",
"If it does not exist, your very first exercise will be a test.": "Ha nem létezik 3 hétnél korábbi teszt, akkor a legelső gyakorlatod egy teszt lesz."
"If it does not exist, your very first exercise will be a test.": "Ha nem létezik 3 hétnél korábbi teszt, akkor a legelső gyakorlatod egy teszt lesz.",
"Tap on the button below the reach all premium content!": "Kattints a gombra, hogy hozzáférhess a prémium funkciókhoz!",
"Enjoy also this premium feature": "Élvezd ezt a prémium funkciót is, ",
"to activate all available training programs.": "amellyel aktiválhatod az összes haladó tréning programot.",
"because only that way can we generated the training plan for you.": "mert csak így tudjuk neked a személyre szabott edzéstervet generálni.",
"This is a premium function": "Ez egy prémium funkció",
"because only that way can we show you your exercises, results and evaluations.": "mert csak így tudjuk neked megmutatni a korábbi gyakorlataidat, eredményeket és kiértékeléseket.",
"because only that way can we show you the personalized development diagrams and analysises": "mert csak így tudjuk neked megmutatni a személyre szabott diagramokat és analíziseket.",
"because only in that way can you begin to execute a training plan": "mert csak így tudod elkezdeni az edzésterved végrehajtását"
}

View File

@ -388,7 +388,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 3;
CURRENT_PROJECT_VERSION = 4;
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 = 3;
CURRENT_PROJECT_VERSION = 4;
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 = 3;
CURRENT_PROJECT_VERSION = 4;
DEVELOPMENT_TEAM = SFJJBDCU6Z;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (

View File

@ -1,59 +0,0 @@
import 'dart:async';
import 'package:aitrainer_app/model/exercise_type.dart';
import 'package:aitrainer_app/model/workout_menu_tree.dart';
import 'package:aitrainer_app/repository/exercise_plan_repository.dart';
import 'package:aitrainer_app/repository/workout_tree_repository.dart';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart';
part 'exercise_execute_plan_event.dart';
part 'exercise_execute_plan_state.dart';
class ExerciseExecutePlanBloc extends Bloc<ExerciseExecutePlanEvent, ExerciseExecutePlanState> {
final WorkoutTreeRepository menuTreeRepository;
final ExercisePlanRepository exercisePlanRepository = ExercisePlanRepository();
int? customerId;
int selectedNumber = 0;
@override
ExerciseExecutePlanBloc({required this.menuTreeRepository}) : super(ExerciseByPlanStateInitial());
Future<void> getData() async {
exercisePlanRepository.setCustomerId(customerId!);
await exercisePlanRepository.getLastExercisePlan();
await exercisePlanRepository.getExercisePlanDetails();
menuTreeRepository.sortedTree.clear();
menuTreeRepository.sortByMuscleType();
menuTreeRepository.sortedTree.forEach((key, value) {
List<WorkoutMenuTree> listWorkoutTree = value;
listWorkoutTree.forEach((workoutTree) {
workoutTree.selected = false;
if (exercisePlanRepository.getExercisePlanDetailSize() > 0) {
if (exercisePlanRepository.getExercisePlanDetailByExerciseId(workoutTree.exerciseTypeId) != null) {
workoutTree.selected = true;
this.selectedNumber++;
}
}
});
});
}
@override
Stream<ExerciseExecutePlanState> mapEventToState(ExerciseExecutePlanEvent event) async* {
try {
if (event is ExerciseByPlanLoad) {
yield ExerciseByPlanLoading();
await this.getData();
yield ExerciseByPlanReady();
} else if (event is AddExerciseByPlanEvent) {
yield ExerciseByPlanLoading();
yield ExerciseByPlanReady();
}
} on Exception catch (e) {
yield ExerciseByPlanError(message: e.toString());
}
}
}

View File

@ -1,21 +0,0 @@
part of 'exercise_execute_plan_bloc.dart';
@immutable
abstract class ExerciseExecutePlanEvent extends Equatable {
const ExerciseExecutePlanEvent();
@override
List<Object> get props => [];
}
class AddExerciseByPlanEvent extends ExerciseExecutePlanEvent {
final ExerciseType exerciseType;
const AddExerciseByPlanEvent({required this.exerciseType});
@override
List<Object> get props => [exerciseType];
}
class ExerciseByPlanLoad extends ExerciseExecutePlanEvent {
const ExerciseByPlanLoad();
}

View File

@ -1,31 +0,0 @@
part of 'exercise_execute_plan_bloc.dart';
@immutable
abstract class ExerciseExecutePlanState extends Equatable {
const ExerciseExecutePlanState();
@override
List<Object> get props => [];
}
class ExerciseByPlanStateInitial extends ExerciseExecutePlanState {
const ExerciseByPlanStateInitial();
}
class ExerciseByPlanLoading extends ExerciseExecutePlanState {
const ExerciseByPlanLoading();
}
// updated screen
class ExerciseByPlanReady extends ExerciseExecutePlanState {
const ExerciseByPlanReady();
}
// error splash screen
class ExerciseByPlanError extends ExerciseExecutePlanState {
final String message;
const ExerciseByPlanError({required this.message});
@override
List<Object> get props => [message];
}

View File

@ -1,99 +0,0 @@
import 'dart:async';
import 'package:aitrainer_app/bloc/exercise_execute_plan/exercise_execute_plan_bloc.dart';
import 'package:aitrainer_app/model/cache.dart';
import 'package:aitrainer_app/model/customer.dart';
import 'package:aitrainer_app/model/workout_menu_tree.dart';
import 'package:aitrainer_app/repository/exercise_plan_repository.dart';
import 'package:aitrainer_app/repository/exercise_repository.dart';
import 'package:aitrainer_app/util/enums.dart';
import 'package:aitrainer_app/util/track.dart';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart';
part 'exercise_execute_plan_add_event.dart';
part 'exercise_execute_plan_add_state.dart';
class ExerciseExecutePlanAddBloc extends Bloc<ExerciseExecutePlanAddEvent, ExerciseExecutePlanAddState> {
final ExerciseRepository exerciseRepository;
final ExercisePlanRepository exercisePlanRepository;
final WorkoutMenuTree workoutTree;
final ExerciseExecutePlanBloc planBloc;
final int customerId;
late Customer customer;
int step = 1;
int countSteps = 1;
double? quantity;
double? unitQuantity;
double scrollOffset = 0;
@override
ExerciseExecutePlanAddBloc(
{required this.exerciseRepository,
required this.exercisePlanRepository,
required this.customerId,
required this.workoutTree,
required this.planBloc})
: super(ExerciseExecutePlanAddInitial());
void init() {
exerciseRepository.exerciseType = workoutTree.exerciseType;
if (Cache().userLoggedIn!.customerId == customerId) {
customer = Cache().userLoggedIn!;
} else if (Cache().getTrainee()!.customerId == customerId) {
customer = Cache().getTrainee()!;
}
exercisePlanRepository.setActualPlanDetailByExerciseType(workoutTree.exerciseType!);
exerciseRepository.customer = customer;
countSteps = exercisePlanRepository.getActualPlanDetail()!.serie!;
if (exercisePlanRepository.getActualPlanDetail()!.weightEquation == null) {
unitQuantity = 0.0;
} else {
unitQuantity = double.parse(exercisePlanRepository.getActualPlanDetail()!.weightEquation!);
}
quantity = exercisePlanRepository.getActualPlanDetail()!.repeats!.toDouble();
exerciseRepository.setQuantity(quantity!);
exerciseRepository.setUnitQuantity(unitQuantity!);
}
@override
Stream<ExerciseExecutePlanAddState> mapEventToState(ExerciseExecutePlanAddEvent event) async* {
try {
if (event is ExerciseExecutePlanAddLoad) {
yield ExerciseExecutePlanAddLoading();
init();
Track().track(TrackingEvent.my_exercise_plan_execute_open);
yield ExerciseExecutePlanAddReady();
} else if (event is ExerciseExecutePlanAddChangeQuantity) {
yield ExerciseExecutePlanAddLoading();
quantity = event.quantity;
exerciseRepository.setQuantity(quantity!);
yield ExerciseExecutePlanAddReady();
} else if (event is ExerciseExecutePlanAddChangeUnitQuantity) {
yield ExerciseExecutePlanAddLoading();
unitQuantity = event.quantity;
exerciseRepository.setUnitQuantity(unitQuantity!);
yield ExerciseExecutePlanAddReady();
} else if (event is ExerciseExecutePlanAddSubmit) {
yield ExerciseExecutePlanAddLoading();
exerciseRepository.exercise!.exercisePlanDetailId = exercisePlanRepository.getActualPlanDetail()!.exercisePlanDetailId;
exerciseRepository.exercise!.unit = workoutTree.exerciseType!.unit;
workoutTree.executed = true;
await exerciseRepository.addExercise();
exerciseRepository.initExercise();
Track().track(TrackingEvent.my_exercise_plan_execute_save);
step++;
scrollOffset = step * 200.0;
planBloc.add(ExerciseByPlanLoad());
yield ExerciseExecutePlanAddReady();
}
} on Exception catch (e) {
yield ExerciseExecutePlanAddError(message: e.toString());
}
}
}

View File

@ -1,33 +0,0 @@
part of 'exercise_execute_plan_add_bloc.dart';
@immutable
abstract class ExerciseExecutePlanAddEvent extends Equatable {
const ExerciseExecutePlanAddEvent();
@override
List<Object> get props => [];
}
class ExerciseExecutePlanAddLoad extends ExerciseExecutePlanAddEvent {
const ExerciseExecutePlanAddLoad();
}
class ExerciseExecutePlanAddChangeQuantity extends ExerciseExecutePlanAddEvent {
final double quantity;
const ExerciseExecutePlanAddChangeQuantity({required this.quantity});
@override
List<Object> get props => [quantity];
}
class ExerciseExecutePlanAddChangeUnitQuantity extends ExerciseExecutePlanAddEvent {
final double quantity;
const ExerciseExecutePlanAddChangeUnitQuantity({required this.quantity});
@override
List<Object> get props => [quantity];
}
class ExerciseExecutePlanAddSubmit extends ExerciseExecutePlanAddEvent {
const ExerciseExecutePlanAddSubmit();
}

View File

@ -1,31 +0,0 @@
part of 'exercise_execute_plan_add_bloc.dart';
@immutable
abstract class ExerciseExecutePlanAddState extends Equatable {
const ExerciseExecutePlanAddState();
@override
List<Object> get props => [];
}
class ExerciseExecutePlanAddInitial extends ExerciseExecutePlanAddState {
const ExerciseExecutePlanAddInitial();
}
class ExerciseExecutePlanAddLoading extends ExerciseExecutePlanAddState {
const ExerciseExecutePlanAddLoading();
}
// updated screen
class ExerciseExecutePlanAddReady extends ExerciseExecutePlanAddState {
const ExerciseExecutePlanAddReady();
}
// error splash screen
class ExerciseExecutePlanAddError extends ExerciseExecutePlanAddState {
final String message;
const ExerciseExecutePlanAddError({required this.message});
@override
List<Object> get props => [message];
}

View File

@ -3,6 +3,7 @@ import 'dart:async';
import 'package:aitrainer_app/bloc/account/account_bloc.dart';
import 'package:aitrainer_app/model/cache.dart';
import 'package:aitrainer_app/repository/customer_repository.dart';
import 'package:aitrainer_app/repository/split_test_respository.dart';
import 'package:aitrainer_app/repository/user_repository.dart';
import 'package:aitrainer_app/util/common.dart';
import 'package:aitrainer_app/util/enums.dart';
@ -19,14 +20,27 @@ class LoginBloc extends Bloc<LoginEvent, LoginState> with Trans {
final AccountBloc accountBloc;
final UserRepository userRepository;
final CustomerRepository customerRepository = CustomerRepository();
final SplitTestRepository splitTestRepository = SplitTestRepository();
final BuildContext context;
final bool isRegistration;
bool dataPolicyAllowed = false;
bool emailSubscription = false;
bool obscure = true;
Color testColor = Colors.green[800]!;
bool emailCheckbox = true;
LoginBloc({required this.accountBloc, required this.userRepository, required this.context, required this.isRegistration})
: super(LoginInitial());
: super(LoginInitial()) {
String colorString = splitTestRepository.getSplitTestValue("registration_skip");
if (colorString == "red") {
testColor = Colors.red[800]!;
}
String emailCheckboxString = splitTestRepository.getSplitTestValue("email_checkbox");
if (emailCheckboxString == "0") {
emailCheckbox = false;
}
}
@override
Stream<LoginState> mapEventToState(

View File

@ -66,7 +66,7 @@ class MenuBloc extends Bloc<MenuEvent, MenuState> with Trans, Logging {
workoutItem = event.item;
if (workoutItem != null) {
setAbility(workoutItem!.nameEnglish);
setAbility(workoutItem!.internalName);
}
final LinkedHashMap<String, WorkoutMenuTree> branch = menuTreeRepository.getBranch(event.parent);
@ -80,7 +80,7 @@ class MenuBloc extends Bloc<MenuEvent, MenuState> with Trans, Logging {
LinkedHashMap<String, WorkoutMenuTree> branch;
if (workoutItem != null) {
setAbility(workoutItem!.nameEnglish);
setAbility(workoutItem!.internalName);
branch = menuTreeRepository.getBranch(workoutItem!.parent);
await getImages(branch);
}
@ -92,7 +92,7 @@ class MenuBloc extends Bloc<MenuEvent, MenuState> with Trans, Logging {
workoutItem = menuTreeRepository.getParentItem(parent);
if (workoutItem != null) {
setAbility(workoutItem!.nameEnglish);
setAbility(workoutItem!.internalName);
}
final LinkedHashMap<String, WorkoutMenuTree> branch = menuTreeRepository.getBranch(workoutItem!.parent);
await getImages(branch);
@ -119,22 +119,19 @@ class MenuBloc extends Bloc<MenuEvent, MenuState> with Trans, Logging {
void setAbility(String name) {
switch (name) {
case "Muscle Build / Shape Toning":
case "one_rep_max":
ability = ExerciseAbility.oneRepMax;
break;
case "Endurance":
ability = ExerciseAbility.endurance;
break;
case "Cardio":
case "cardio":
ability = ExerciseAbility.running;
break;
case "Test Center":
case "test_center":
ability = ExerciseAbility.mini_test_set;
break;
case "Training Plans":
case "training_plans":
ability = ExerciseAbility.training;
break;
case "My Body":
case "my_body":
ability = ExerciseAbility.none;
break;
}

View File

@ -1,12 +1,11 @@
import 'dart:async';
import 'dart:io';
import 'dart:math' as math;
import 'package:aitrainer_app/model/cache.dart';
import 'package:aitrainer_app/model/product.dart';
import 'package:aitrainer_app/model/product_test.dart';
import 'package:aitrainer_app/model/purchase.dart';
import 'package:aitrainer_app/repository/description_repository.dart';
import 'package:aitrainer_app/repository/split_test_respository.dart';
import 'package:aitrainer_app/service/logging.dart';
import 'package:aitrainer_app/service/purchase_service.dart';
import 'package:aitrainer_app/util/enums.dart';
@ -14,37 +13,43 @@ import 'package:aitrainer_app/util/purchases.dart';
import 'package:aitrainer_app/util/track.dart';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:firebase_remote_config/firebase_remote_config.dart';
import 'package:purchases_flutter/offering_wrapper.dart';
part 'sales_event.dart';
part 'sales_state.dart';
class SalesBloc extends Bloc<SalesEvent, SalesState> with Logging {
List<ProductTest>? tests = [];
List<Product> product2Display = [];
List<String> productText2Display = ["WorkoutTest annual", "WorkoutTest montly"];
final SplitTestRepository splitTestRepository = SplitTestRepository();
int productSet = -1;
final DescriptionRepository descriptionRepository = DescriptionRepository();
SalesBloc() : super(SalesInitial());
String? salesText;
String? premiumFunctions = "";
String salesButtonText = "<h1>Workout Test Monthly</h1><p>localizedPrice</p><p><small>cancel any time</small></p>";
Product? offeredProduct;
Product? getProductByName(String name) {
Product? product;
if (product2Display.isNotEmpty) {
product2Display.forEach((element) {
if (element.type == name) {
product = element;
salesButtonText = salesButtonText.replaceFirst(RegExp(r'localizedPrice'), product!.localizedPrice!);
print("Localized Price ${product!.localizedPrice!} - Text: $salesButtonText");
}
});
void init() async {
if (Cache().userLoggedIn == null) {
throw Exception("Please log in");
}
return product;
salesText = splitTestRepository.getSplitTestValue("sales_page_text_a");
if (salesText == null || salesText!.isEmpty) {
salesText = descriptionRepository.getDescriptionByName("sales_page_text_a");
}
print("sales Text: $salesText");
getProductsTexts();
premiumFunctions = descriptionRepository.getDescriptionByName("premium_functions");
if (premiumFunctions == null || premiumFunctions!.isEmpty) {
premiumFunctions = "";
}
await RevenueCatPurchases().getOfferings();
this.getProductSet();
Track().track(TrackingEvent.sales_page);
}
@override
@ -53,33 +58,11 @@ class SalesBloc extends Bloc<SalesEvent, SalesState> with Logging {
) async* {
try {
if (event is SalesLoad) {
log(" -- start SalesLoad");
yield SalesLoading();
log("Load Sales");
if (Cache().userLoggedIn == null) {
throw Exception("Please log in");
}
String descriptionName = "sales_page_text";
RemoteConfig? remoteConfig = Cache().remoteConfig;
if (remoteConfig != null) {
remoteConfig.fetchAndActivate();
Map config = remoteConfig.getAll();
RemoteConfigValue? value = config['sales_page_text_a'];
if (value != null) {
log("RemoteConfig sales_page_text value: ${value.asString()}");
if (value.asString() == "1") {
descriptionName = "sales_page_text_a";
}
}
}
await RevenueCatPurchases().getOfferings();
this.getProductSet();
salesText = descriptionRepository.getDescriptionByName(descriptionName);
log(salesText!);
salesButtonText = descriptionRepository.getDescriptionByName("sales_button_monthly");
offeredProduct = getProductByName("wt_sub_2_3");
Track().track(TrackingEvent.sales_page);
init();
yield SalesReady();
log(" -- finish SalesLoad");
} else if (event is SalesPurchase) {
if (Cache().hasPurchased) {
throw Exception("You have already a successfull subscription");
@ -104,28 +87,34 @@ class SalesBloc extends Bloc<SalesEvent, SalesState> with Logging {
} else {
yield SalesError(message: "No selected product");
}
} else if (event is SalesChangeSubscription) {
yield SalesLoading();
print("offered product .. $offeredProduct");
if (offeredProduct != null) {
if (offeredProduct!.type == "wt_sub_2_3") {
print("go yearly");
salesButtonText = descriptionRepository.getDescriptionByName("sales_button_yearly");
offeredProduct = getProductByName("wt_sub_2_1");
} else {
print("go monthly");
salesButtonText = descriptionRepository.getDescriptionByName("sales_button_monthly");
offeredProduct = getProductByName("wt_sub_2_3");
}
}
yield SalesReady();
}
} on Exception catch (ex) {
yield SalesError(message: ex.toString());
}
}
void getProductsTexts() {
Product product;
if (product2Display.isNotEmpty) {
String salesButtonText;
product2Display.forEach((element) {
product = element;
if (product.sort == 3) {
salesButtonText = descriptionRepository.getDescriptionByName("sales_button_monthly");
productText2Display[1] = salesButtonText.replaceFirst(RegExp(r'localizedPrice'), product.localizedPrice!);
} else if (product.sort == 1) {
salesButtonText = descriptionRepository.getDescriptionByName("sales_button_yearly");
productText2Display[0] = salesButtonText.replaceFirst(RegExp(r'localizedPrice'), product.localizedPrice!);
}
});
}
print("product Text $productText2Display");
splitTestRepository.getSplitTestValue("product_set_2");
return;
}
Product? getSelectedProduct(int productId) {
Product? prod;
for (var product in this.product2Display) {
@ -161,38 +150,37 @@ class SalesBloc extends Bloc<SalesEvent, SalesState> with Logging {
}
void getProductSet() {
int productId = 0;
//this.tests = Cache().productTests;
List<Product>? products = Cache().products;
if (products == null) {
return;
}
/* if (tests != null && tests!.isEmpty) {
var rand = math.Random.secure();
productSet = rand.nextInt(5) + 1;
} else {
trace("Previous ProductTest: " + tests![0].toJson().toString());
productId = tests![0].productId;
for (var elem in products) {
final Product product = elem;
if (product.productId == productId) {
productSet = product.productSet;
break;
}
}
} */
//ProductTest productTest = ProductTest();
String productSetString = splitTestRepository.getSplitTestValue("product_set_2");
log("ProductSetString: $productSetString");
try {
productSet = int.parse(productSetString);
} on Exception catch (e) {
log("Define the right productset!");
productSet = 2;
log("ProductSet: " + productSet.toString());
}
log("ProductSet: $productSet");
for (var elem in products) {
Product product = elem;
if (product.productSet == productSet) {
productId = product.productId;
final String platformProductId = Platform.isAndroid ? product.productIdAndroid! : product.productIdIos!;
String? platformProductId;
if (product.productIdAndroid == null || product.productIdIos == null) {
log("Define the product ID for the different Platforms!!");
} else {
platformProductId = Platform.isAndroid ? product.productIdAndroid! : product.productIdIos!;
}
if (platformProductId == null) {
log("Not defined platform product id!!");
platformProductId = "";
}
product.localizedPrice = getLocalizedPrice(platformProductId, product);
log("product with localized price: $product");
product2Display.add(product);
@ -203,10 +191,6 @@ class SalesBloc extends Bloc<SalesEvent, SalesState> with Logging {
return a.sort < b.sort ? -1 : 1;
});
//productTest.productId = productId;
//productTest.customerId = Cache().userLoggedIn!.customerId!;
//productTest.dateView = DateTime.now();
//ProductTestApi().saveProductTest(productTest);
//Cache().productTests.add(productTest);
this.getProductsTexts();
}
}

View File

@ -92,11 +92,16 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
event.detail.state = ExercisePlanDetailState.inProgress;
}
// recalculate the weight to the original planned repeats
if (event.detail.isTest && event.detail.exercises.length == 1) {
trainingPlanRepository.recalculateDetail(_myPlan!.trainingPlanId!, event.detail);
}
exercise.trainingPlanDetailsId = _myPlan!.trainingPlanId;
// save Exercise
await ExerciseApi().addExercise(exercise);
Cache().addExercise(exercise);
Exercise savedExercise = await ExerciseApi().addExercise(exercise);
Cache().addExercise(savedExercise);
Cache().myTrainingPlan = _myPlan;
await Cache().saveMyTrainingPlan();
@ -108,7 +113,6 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
}
} else if (event is TrainingPlanSkipExercise) {
yield TrainingPlanLoading();
print("Skipping ${event.detail.exerciseTypeId}");
event.detail.state = ExercisePlanDetailState.skipped;
Cache().myTrainingPlan = _myPlan;
await Cache().saveMyTrainingPlan();
@ -378,7 +382,8 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
return 0;
}
if (_myPlan == null || _myPlan!.details.isEmpty) {
throw Exception("No defined Training Plan");
// throw Exception("No defined Training Plan");
return 0;
}
if (dayNames.isEmpty || dayNames.length == 1) {
@ -402,7 +407,7 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
}
activeDayIndex++;
}
print("Active Day Index: $activeDayIndex");
if (activeDayIndex >= dayNames.length) {
activeDayIndex = 0;
this.add(TrainingPlanGoToRestart());
@ -481,4 +486,17 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
return value.toStringAsFixed(0);
}
bool existsAddedExerciseTypeInTree(String name) {
bool exists = false;
final List<WorkoutMenuTree>? listWorkoutTree = menuBloc.menuTreeRepository.sortedTree[name];
if (listWorkoutTree != null) {
listWorkoutTree.forEach((element) {
if (element.exerciseType!.trainingPlanState.equalsTo(ExerciseTypeTrainingPlanState.added)) {
exists = true;
}
});
}
return exists;
}
}

View File

@ -18,8 +18,6 @@ import 'package:aitrainer_app/view/customer_modify_page.dart';
import 'package:aitrainer_app/view/customer_welcome_page.dart';
import 'package:aitrainer_app/view/evaluation_page.dart';
import 'package:aitrainer_app/view/exercise_control_page.dart';
import 'package:aitrainer_app/view/exercise_execute_page.dart';
import 'package:aitrainer_app/view/exercise_execute_plan_add_page.dart';
import 'package:aitrainer_app/view/exercise_log_page.dart';
import 'package:aitrainer_app/view/exercise_plan_custom_page.dart';
import 'package:aitrainer_app/view/exercise_plan_custom_detail_add_page.dart';
@ -198,8 +196,8 @@ Future<void> initThirdParty() async {
if (!isInDebugMode) {
await Flurry.initialize(androidKey: "JNYCTCWBT34FM3J8TV36", iosKey: "3QBG7BSMGPDH24S8TRQP", enableLog: true);
FlutterUxcam.optIntoSchematicRecordings();
PushNotificationsManager().init();
}
PushNotificationsManager().init();
}
class WorkoutTestApp extends StatelessWidget {
@ -258,8 +256,6 @@ class WorkoutTestApp extends StatelessWidget {
'exerciseLogPage': (context) => ExerciseLogPage(),
'exercisePlanCustomPage': (context) => ExercisePlanCustomPage(),
'exercisePlanDetailAdd': (context) => ExercisePlanDetailAddPage(),
'exerciseExecutePlanPage': (context) => ExerciseExecutePage(),
'exerciseExecuteAddPage': (context) => ExerciseExecutePlanAddPage(),
'mydevelopmentMusclePage': (context) => MyDevelopmentMusclePage(),
'mydevelopmentBodyPage': (context) => MyDevelopmentBodyPage(),
'mydevelopmentSizesPage': (context) => SizesDevelopmentPage(),

View File

@ -14,9 +14,9 @@ import 'package:aitrainer_app/model/faq.dart';
import 'package:aitrainer_app/model/model_change.dart';
import 'package:aitrainer_app/model/product.dart' as wt_product;
import 'package:aitrainer_app/model/product.dart';
import 'package:aitrainer_app/model/product_test.dart';
import 'package:aitrainer_app/model/property.dart';
import 'package:aitrainer_app/model/purchase.dart';
import 'package:aitrainer_app/model/split_test.dart';
import 'package:aitrainer_app/model/sport.dart';
import 'package:aitrainer_app/model/training_plan.dart';
import 'package:aitrainer_app/model/tutorial.dart';
@ -136,7 +136,8 @@ class Cache with Logging {
List<Sport>? _sports;
List<wt_product.Product>? _products;
List<Purchase> _purchases = [];
List<ProductTest>? _productTests;
List<SplitTest> _splitTests = [];
List<ExercisePlanTemplate> _exercisePlanTemplates = [];
ExercisePlan? activeExercisePlan;
@ -242,7 +243,7 @@ class Cache with Logging {
Map<String, dynamic> map;
try {
map = JsonDecoder().convert(savedTrainingPlanJson);
print("Training plan: $savedTrainingPlanJson");
//print("Training plan: $savedTrainingPlanJson");
this.myTrainingPlan = CustomerTrainingPlan.fromJsonWithDetails(map);
} on Exception catch (e) {
print(e.toString());
@ -662,11 +663,6 @@ class Cache with Logging {
List<Purchase> get purchases => _purchases;
setPurchases(List<Purchase> value) => _purchases = value;
// ignore: unnecessary_getters_setters
List<ProductTest>? get productTests => _productTests;
// ignore: unnecessary_getters_setters
set productTests(List<ProductTest>? value) => _productTests = value;
Future<void> initCustomer(int customerId) async {
log(" *** initCustomer");
await PackageApi().getCustomerPackage(customerId);
@ -738,4 +734,7 @@ class Cache with Logging {
List<CustomerTrainingPlan>? getCustomerTrainingPlans() => this._customerTrainingPlans;
setCustomerTrainingPlans(value) => this._customerTrainingPlans = value;
List<SplitTest> getSplitTests() => this._splitTests;
setSplitTests(value) => this._splitTests = value;
}

View File

@ -57,10 +57,6 @@ class CustomerTrainingPlan {
jsonDetails =
jsonDetails.replaceAllMapped(RegExp(r'([0-9]{4}\-[0-9]{2}\-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2})'), (Match m) => "\"${m[0]}\"");
// jsonDetails = jsonDetails.replaceAll(r'\"null\"', 'null');
// jsonDetails = jsonDetails.replaceAll(r'\"false\"', 'false');
// jsonDetails = jsonDetails.replaceAll(r'\"true\"', 'true');
print("detail: $jsonDetails");
Iterable iterable = jsonDecode(jsonDetails);

View File

@ -1,5 +1,3 @@
import 'dart:convert';
import 'package:aitrainer_app/model/cache.dart';
import 'package:aitrainer_app/model/exercise.dart';
import 'package:aitrainer_app/model/exercise_plan_detail.dart';
@ -9,6 +7,9 @@ class CustomerTrainingPlanDetails {
/// customerTrainingPlanDetails
int? customerTrainingPlanDetailsId;
/// trainingPlanDetailsId
int? trainingPlanDetailsId;
/// exerciseTypeId
int? exerciseTypeId;
@ -32,6 +33,8 @@ class CustomerTrainingPlanDetails {
List<Exercise> exercises = [];
bool isTest = false;
CustomerTrainingPlanDetails();
CustomerTrainingPlanDetails.fromJson(Map json) {
@ -49,6 +52,7 @@ class CustomerTrainingPlanDetails {
this.customerTrainingPlanDetailsId = json['customerTrainingPlanDetailsId'] == "null" || json['customerTrainingPlanDetailsId'] == null
? 0
: json['customerTrainingPlanDetailsId'];
this.trainingPlanDetailsId = json['trainingPlanDetailsId'];
this.exerciseTypeId = json['exerciseTypeId'];
this.set = json['set'];
this.repeats = json['repeats'] == "null" ? -1 : json['repeats'];
@ -79,6 +83,7 @@ class CustomerTrainingPlanDetails {
} else {
this.state = ExercisePlanDetailState.start;
}
this.isTest = json['isTest'] == "true" ? true : false;
this.exerciseType = Cache().getExerciseTypeById(exerciseTypeId!);
}
@ -98,7 +103,8 @@ class CustomerTrainingPlanDetails {
Map<String, dynamic> toJsonWithExercises() {
final Map<String, dynamic> jsonMap = {
//"customerTrainingPlanDetailsId": this.customerTrainingPlanDetailsId,
"customerTrainingPlanDetailsId": this.customerTrainingPlanDetailsId,
"trainingPlanDetailsId": this.trainingPlanDetailsId,
"exerciseTypeId": this.exerciseTypeId,
"set": this.set,
"repeats": this.repeats,
@ -107,6 +113,7 @@ class CustomerTrainingPlanDetails {
"parallel": this.parallel,
'exercises': exercises.isEmpty ? [].toString() : exercises.map((exercise) => exercise.toJson()).toList().toString(),
'state': this.state.toStr(),
"isTest": this.isTest,
};
if (this.day != null && this.day!.isNotEmpty) {
jsonMap["day"] = this.day;

View File

@ -1,39 +0,0 @@
import 'package:intl/intl.dart';
class ProductTest {
late int? productTestId;
late int customerId;
late int productId;
late DateTime dateView;
late bool purchaseClick;
ProductTest();
ProductTest.fromJson(Map json) {
this.productTestId = json['productTestId'];
this.customerId = json['customerId'];
this.productId = json['productId'];
this.dateView = DateTime.parse(json['dateView']);
this.purchaseClick = json['purchaseClick'];
}
Map<String, dynamic> toJson() {
if (productTestId != null) {
return {
"productTestId": productTestId,
"customerId": customerId,
"productId": productId,
"dateView": DateFormat('yyyy-MM-dd HH:mm:ss').format(this.dateView),
"purchaseClick": purchaseClick
};
} else {
return {
"customerId": customerId,
"productId": productId,
"dateView": DateFormat('yyyy-MM-dd HH:mm:ss').format(this.dateView),
"purchaseClick": purchaseClick
};
}
}
}

36
lib/model/split_test.dart Normal file
View File

@ -0,0 +1,36 @@
class SplitTest {
late int testId;
late String name;
late String remoteConfigKey;
late String remoteConfigValue;
late String testValue;
String? source;
late bool active;
DateTime? validTo;
SplitTest.fromJson(Map json) {
this.testId = json['testId'];
this.name = json['name'];
this.remoteConfigKey = json['remoteConfigKey'];
this.remoteConfigValue = json['remoteConfigValue'];
this.testValue = json['testValue'];
this.source = json['source'];
this.active = json['active'];
this.validTo = json['validTo'] == null ? null : DateTime.parse(json['validTo']);
}
@override
String toString() {
Map<String, dynamic> json = {
'productId': this.testId,
'name': this.name,
'remoteConfigKey': this.remoteConfigKey,
'remoteConfigValue': this.remoteConfigValue,
'testValue': this.testValue,
'source': this.source,
'active': this.active,
'validTo': validTo,
};
return json.toString();
}
}

View File

@ -10,6 +10,7 @@ class PushNotificationsManager with Logging {
static final PushNotificationsManager _instance = PushNotificationsManager._();
Future<void> init() async {
log(" --- Firebase Messagein init..");
const AndroidNotificationChannel channel = AndroidNotificationChannel(
'high_importance_channel', // id
'High Importance Notifications', // title
@ -28,6 +29,6 @@ class PushNotificationsManager with Logging {
sound: true,
);
String? token = await FirebaseMessaging.instance.getToken();
print("FirebaseMessaging tokne $token");
log("FirebaseMessaging token $token");
}
}

View File

@ -3,17 +3,14 @@ import 'dart:collection';
import 'package:aitrainer_app/model/cache.dart';
import 'package:aitrainer_app/model/customer.dart';
import 'package:aitrainer_app/model/customer_property.dart';
import 'package:aitrainer_app/model/product_test.dart';
import 'package:aitrainer_app/model/property.dart';
import 'package:aitrainer_app/model/purchase.dart';
import 'package:aitrainer_app/model/sport.dart';
import 'package:aitrainer_app/repository/property_repository.dart';
import 'package:aitrainer_app/service/customer_service.dart';
import 'package:aitrainer_app/service/logging.dart';
import 'package:aitrainer_app/service/product_test_service.dart';
import 'package:aitrainer_app/service/purchase_service.dart';
import 'package:aitrainer_app/util/enums.dart';
import 'package:aitrainer_app/util/not_found_exception.dart';
class GenderItem {
GenderItem(this.dbValue, this.name);
@ -354,25 +351,6 @@ class CustomerRepository with Logging {
await PurchaseApi().savePurchase(purchase);
}
Future<List<ProductTest>> getProductTests() async {
List<ProductTest> tests = [];
try {
int customerId = Cache().userLoggedIn!.customerId!;
tests = await ProductTestApi().getProductTestByCustomer(customerId);
} on NotFoundException catch (_) {
log("Product Tests not found");
Cache().productTests = tests;
} on Exception catch (ex) {
log(ex.toString());
Cache().productTests = tests;
}
return tests;
}
Future<void> addProductTest(ProductTest productTest) async {
await ProductTestApi().saveProductTest(productTest);
}
void setMediaDimensions(double width, double height) {
this.mediaHeight = height;
this.mediaWidth = width;

View File

@ -1,5 +1,8 @@
import 'package:aitrainer_app/model/cache.dart';
import 'package:aitrainer_app/model/description.dart';
import 'package:aitrainer_app/util/app_language.dart';
import 'package:flutter/material.dart';
class DescriptionRepository {
List<Description>? descriptions;
@ -12,10 +15,13 @@ class DescriptionRepository {
String descriptionText = "";
if (descriptions != null) {
this.descriptions!.forEach((element) {
print("Desc ${element.name} - $name");
if (element.name == name) {
if (AppLanguage().appLocal == Locale('en')) {
descriptionText = element.description;
} else {
descriptionText = element.descriptionTranslation != null ? element.descriptionTranslation! : element.description;
}
}
});
}
return descriptionText;

View File

@ -0,0 +1,63 @@
import 'package:aitrainer_app/model/cache.dart';
import 'package:aitrainer_app/repository/description_repository.dart';
import 'package:aitrainer_app/service/logging.dart';
import 'package:firebase_remote_config/firebase_remote_config.dart';
class SplitTestRepository with Logging {
final RemoteConfig? _remoteConfig = Cache().remoteConfig;
final DescriptionRepository descriptionRepository = DescriptionRepository();
String getSplitTestValue(String remoteConfigKey) {
String testValue = "";
if (_remoteConfig != null) {
_remoteConfig!.fetchAndActivate();
Map configs = _remoteConfig!.getAll();
RemoteConfigValue? value = configs[remoteConfigKey];
if (value != null) {
log("A/B Test RemoteConfig $remoteConfigKey value: ${value.asString()}");
final String remoteConfigValue = value.asString();
testValue = this.getSplitTestValueByRemoteConfig(remoteConfigKey, remoteConfigValue);
} else {
log("RemoteConfig value $remoteConfigKey is null!!");
}
} else {
log(" !! remoteConfig isnull");
}
return testValue;
}
String getSource(String remoteConfigKey) {
String source = "";
return source;
}
String getSplitTestValueByRemoteConfig(String key, String value) {
String testValue = "";
if (Cache().getSplitTests().isEmpty) {
log("Splittests empty");
return testValue;
}
Cache().getSplitTests().forEach((element) {
if (element.remoteConfigKey == key && element.remoteConfigValue == value && element.active) {
testValue = element.testValue;
log("A/B Test testValue: $testValue");
if (element.source != null && element.source!.isNotEmpty) {
final List<String> sourceElements = element.source!.split(".");
if (sourceElements.length > 1) {
final String modelName = sourceElements[0];
if (modelName == "description") {
testValue = descriptionRepository.getDescriptionByName(element.testValue);
}
}
}
}
});
return testValue;
}
}

View File

@ -65,8 +65,11 @@ class TrainingPlanRepository {
}
// 3 calculate weights
int index = 0;
trainingPlan.details!.forEach((elem) {
CustomerTrainingPlanDetails detail = CustomerTrainingPlanDetails();
detail.customerTrainingPlanDetailsId = ++index;
detail.trainingPlanDetailsId = elem.trainingPlanDetailId;
detail.exerciseTypeId = elem.exerciseTypeId;
detail.repeats = elem.repeats;
detail.set = elem.set;
@ -111,6 +114,7 @@ class TrainingPlanRepository {
double weight = -1;
if (Cache().getExercises() == null) {
detail.weight = weight;
detail.isTest = true;
return detail;
}
@ -124,6 +128,7 @@ class TrainingPlanRepository {
if (lastExercise1RM == null || lastExercise1RM!.unitQuantity == null) {
detail.weight = weight;
detail.isTest = true;
return detail;
}
@ -138,4 +143,34 @@ class TrainingPlanRepository {
detail.weight = weight;
return detail;
}
CustomerTrainingPlanDetails recalculateDetail(int trainingPlanId, CustomerTrainingPlanDetails detail) {
CustomerTrainingPlanDetails recalculatedDetail = detail;
// 1. get original repeats
// 1a get original plan
TrainingPlan? plan = getTrainingPlanById(trainingPlanId);
if (plan == null) {
return recalculatedDetail;
}
// 1.b get the original detail's repeat
int originalRepeats = detail.repeats!;
plan.details!.forEach((element) {
if (element.trainingPlanDetailId == detail.trainingPlanDetailsId) {
print("element $element");
originalRepeats = element.repeats;
}
});
// 2 get recalculated repeats
recalculatedDetail.weight =
Common.calculateWeigthByChangedQuantity(detail.weight!, detail.repeats!.toDouble(), originalRepeats.toDouble());
print("recalculated repeats for $originalRepeats: ${recalculatedDetail.weight}");
recalculatedDetail.repeats = originalRepeats;
return recalculatedDetail;
}
}

View File

@ -33,6 +33,9 @@ class ExerciseApi with Logging {
}
Future<void> deleteExercise(Exercise exercise) async {
if (exercise.exerciseId == null) {
return;
}
int exerciseId = exercise.exerciseId!;
log(" ===== delete exercise: " + exerciseId.toString());
await _client.post("exercises/" + exerciseId.toString(), "");

View File

@ -291,21 +291,25 @@ class FirebaseApi with logging.Logging {
Future<void> setupRemoteConfig() async {
initializeFlutterFire();
final RemoteConfig remoteConfig = RemoteConfig.instance;
RemoteConfig? remoteConfig;
try {
remoteConfig = RemoteConfig.instance;
await remoteConfig.setConfigSettings(RemoteConfigSettings(
fetchTimeout: const Duration(seconds: 10),
minimumFetchInterval: const Duration(seconds: 1),
fetchTimeout: const Duration(seconds: 60),
minimumFetchInterval: const Duration(hours: 6),
));
await remoteConfig.setDefaults(<String, dynamic>{
'sales_page_text': '0',
'sales_page_bkg': 'dark',
'sales_page_offer': 'monthly',
'please_log_in': '',
});
RemoteConfigValue(null, ValueSource.valueStatic);
Cache().setRemoteConfig(remoteConfig);
Map config = remoteConfig.getAll();
print("RemoteConfig sales_page_text Value : ${config['sales_page_text'].asString()}");
} on Exception catch (e) {
print('Unable to fetch remote config. Cached or default values will be used: $e');
if (remoteConfig != null) {
await remoteConfig.setDefaults(<String, dynamic>{
'sales_page_text_a': '',
'product_set_2': '',
});
Cache().setRemoteConfig(remoteConfig);
}
}
}
}

View File

@ -15,9 +15,9 @@ import 'package:aitrainer_app/model/exercise_tree_parents.dart';
import 'package:aitrainer_app/model/exercise_type.dart';
import 'package:aitrainer_app/model/faq.dart';
import 'package:aitrainer_app/model/product.dart';
import 'package:aitrainer_app/model/product_test.dart';
import 'package:aitrainer_app/model/property.dart';
import 'package:aitrainer_app/model/purchase.dart';
import 'package:aitrainer_app/model/split_test.dart';
import 'package:aitrainer_app/model/training_plan.dart';
import 'package:aitrainer_app/model/tutorial.dart';
import 'package:aitrainer_app/service/api.dart';
@ -92,6 +92,11 @@ class PackageApi {
final List<TrainingPlan>? plans = json.map((plan) => TrainingPlan.fromJson(plan)).toList();
Cache().setTrainingPlans(plans);
} else if (headRecord[0] == "SplitTests") {
final Iterable json = jsonDecode(headRecord[1]);
final List<SplitTest>? tests = json.map((test) => SplitTest.fromJson(test)).toList();
//print("A/B tests: $tests");
Cache().setSplitTests(tests);
}
});
@ -156,10 +161,6 @@ class PackageApi {
final Iterable json = jsonDecode(headRecord[1]);
final List<Exercise> exercises = json.map((exerciseType) => Exercise.fromJson(exerciseType)).toList();
Cache().setExercises(exercises);
} else if (headRecord[0] == "ProductTest") {
final Iterable json = jsonDecode(headRecord[1]);
final List<ProductTest> productTests = json.map((productTest) => ProductTest.fromJson(productTest)).toList();
Cache().productTests = productTests;
} else if (headRecord[0] == "Purchase") {
final Iterable json = jsonDecode(headRecord[1]);
final List<Purchase> purchases = json.map((purchase) => Purchase.fromJson(purchase)).toList();

View File

@ -1,23 +0,0 @@
import 'package:aitrainer_app/model/cache.dart';
import 'package:aitrainer_app/model/product_test.dart';
import 'package:aitrainer_app/service/logging.dart';
import 'dart:convert';
import 'api.dart';
class ProductTestApi with Logging {
final APIClient _client = new APIClient();
Future<List<ProductTest>> getProductTestByCustomer(int customerId) async {
final body = await _client.get("product_test/customer/" + customerId.toString(), "");
final Iterable json = jsonDecode(body);
final List<ProductTest> productTests = json.map((productTest) => ProductTest.fromJson(productTest)).toList();
Cache().productTests = productTests;
return productTests;
}
Future<void> saveProductTest(ProductTest productTest) async {
String body = JsonEncoder().convert(productTest.toJson());
log(" ===== saving productTest:" + body);
await _client.post("product_test/", body);
}
}

View File

@ -139,6 +139,9 @@ mixin Common {
}
static normalizeDecimal(String value) {
if (value.isEmpty) {
return 0;
}
value = value.replaceFirst(",", ".");
value = value.replaceAll(RegExp(r'[^0-9.]'), "");
return value;
@ -193,7 +196,6 @@ mixin Common {
static int calculateQuantityByChangedWeight(double initialRM, double weight, double repeat) {
final double rmWendler = weight * repeat * 0.0333 + weight;
final double rmOconner = weight * (1 + repeat / 40);
//print("Weight: $weight oneRepQuantity: $repeat, $rmWendler, Oconner: $rmOconner");
final double repeatWendler = (rmWendler - weight) / 0.0333 / weight;
final double repeatOconner = (rmOconner / weight - 1) * 40;
@ -201,4 +203,17 @@ mixin Common {
//print("Initial 1RM: $initialRM Weight: $weight repeatWendler: $repeatWendler repeat Oconner: $repeatOconner. NEW REPEAT: $newRepeat");
return newRepeat;
}
static double calculateWeigthByChangedQuantity(double weight, double repeat, double changedRepeats) {
final double rmWendler = weight * repeat * 0.0333 + weight;
final double rmOconner = weight * (1 + repeat / 40);
final double initialRM = (rmWendler + rmOconner) / 2;
final double weightWendler = rmWendler / (changedRepeats * 0.0333 + 1);
final double weightOconner = rmOconner / (1 + changedRepeats / 40);
final double newWeight = ((weightWendler + weightOconner) / 2);
print(
"Initial 1RM: $initialRM repeat: $repeat changedRepeat: $changedRepeats Weight: $weight weightWendler: $weightWendler weight Oconner: $weightOconner. NEW WEIGHT: $newWeight");
return newWeight;
}
}

View File

@ -1,227 +0,0 @@
import 'dart:collection';
import 'package:aitrainer_app/bloc/exercise_execute_plan/exercise_execute_plan_bloc.dart';
import 'package:aitrainer_app/model/cache.dart';
import 'package:aitrainer_app/model/workout_menu_tree.dart';
import 'package:aitrainer_app/library/tree_view.dart';
import 'package:aitrainer_app/util/trans.dart';
import 'package:aitrainer_app/widgets/app_bar.dart';
import 'package:aitrainer_app/widgets/bottom_nav.dart';
import 'package:aitrainer_app/widgets/treeview_parent_widget.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:google_fonts/google_fonts.dart';
class ExerciseExecutePage extends StatefulWidget {
@override
_ExerciseExecutePage createState() => _ExerciseExecutePage();
}
class _ExerciseExecutePage extends State<ExerciseExecutePage> with Trans {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
// ignore: close_sinks
late ExerciseExecutePlanBloc bloc;
@override
void initState() {
super.initState();
/// We require the initializers to run after the loading screen is rendered
SchedulerBinding.instance!.addPostFrameCallback((_) {
BlocProvider.of<ExerciseExecutePlanBloc>(context).add(ExerciseByPlanLoad());
});
}
@override
Widget build(BuildContext context) {
LinkedHashMap arguments = ModalRoute.of(context)!.settings.arguments as LinkedHashMap;
final int customerId = arguments['customerId'];
bloc = BlocProvider.of<ExerciseExecutePlanBloc>(context);
bloc.customerId = customerId;
setContext(context);
return Scaffold(
key: _scaffoldKey,
appBar: AppBarNav(depth: 1),
body: Container(
padding: EdgeInsets.all(20),
decoration: BoxDecoration(
image: DecorationImage(
image: customerId == Cache().userLoggedIn!.customerId
? AssetImage('asset/image/WT_black_background.jpg')
: AssetImage('asset/image/WT_light_background.jpg'),
fit: BoxFit.cover,
alignment: Alignment.center,
),
),
child: BlocConsumer<ExerciseExecutePlanBloc, ExerciseExecutePlanState>(listener: (context, state) {
if (state is ExerciseByPlanError) {
//LoadingDialog.hide(context);
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(
state.message,
),
backgroundColor: Colors.orange,
));
} else if (state is ExerciseByPlanLoading) {
//LoadingDialog.show(context);
}
}, builder: (context, state) {
if (state is ExerciseByPlanStateInitial || state is ExerciseByPlanLoading) {
return Container();
} else if (state is ExerciseByPlanReady) {
//LoadingDialog.hide(context);
return exerciseWidget(bloc);
} else {
return exerciseWidget(bloc);
}
})),
bottomNavigationBar: BottomNavigator(bottomNavIndex: 2),
);
}
Widget exerciseWidget(ExerciseExecutePlanBloc bloc) {
return TreeView(
startExpanded: false,
children: nodeExercisePlan(bloc),
);
}
List<Widget> nodeExercisePlan(ExerciseExecutePlanBloc bloc) {
List<Widget> exerciseTypes = [];
Card explanation = Card(
color: Colors.white38,
child: Container(
padding: EdgeInsets.only(left: 10, right: 5, top: 12, bottom: 8),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Row(
children: [
Icon(
Icons.info,
color: Colors.orangeAccent,
),
Text(" "),
Flexible(
child: Text(
t("Execute your active Exercise Plan!"),
style: GoogleFonts.archivoBlack(
fontSize: 20,
),
maxLines: 2,
),
),
],
),
Divider(
color: Colors.transparent,
),
Text(
t("Select the muscle type and tap on the exercise. One the next page enter the weight and repeat."),
style: TextStyle(fontSize: 12, fontWeight: FontWeight.normal),
),
],
)));
exerciseTypes.add(explanation);
if (bloc.selectedNumber == 0) {
exerciseTypes.add(Container(
child: Center(
child: Text(
t("Please define your Exercise Plan"),
style: GoogleFonts.inter(color: Colors.white),
))));
exerciseTypes.add(Container(
child: Center(
child: Text(
t("Go to: 'Training Plan' - 'Edit My Custom Plan'"),
style: GoogleFonts.inter(color: Colors.white),
))));
exerciseTypes.add(Container(
child: Center(
child: InkWell(
onTap: () {
final LinkedHashMap args = LinkedHashMap();
args['customerId'] = Cache().userLoggedIn!.customerId;
Navigator.of(context).pop();
Navigator.of(context).pushNamed('exercisePlanCustomPage', arguments: args);
},
child: Text(
t("Jump there »"),
style: GoogleFonts.inter(color: Colors.blue[200], decorationStyle: TextDecorationStyle.solid),
)))));
} else {
bloc.menuTreeRepository.sortedTree.forEach((name, list) {
exerciseTypes.add(Container(
margin: const EdgeInsets.only(left: 4.0),
child: TreeViewChild(
startExpanded: true,
parent: TreeviewParentWidget(text: name),
children: _getChildList(list, bloc),
)));
});
}
return exerciseTypes;
}
List<Widget> _getChildList(List<WorkoutMenuTree> listWorkoutTree, ExerciseExecutePlanBloc bloc) {
List<Widget> list = [];
listWorkoutTree.forEach((element) {
if (element.selected) {
list.add(TreeViewChild(
startExpanded: false,
parent: Card(
margin: EdgeInsets.only(left: 10, top: 5),
color: Colors.white54,
child: Container(
padding: const EdgeInsets.only(left: 5, top: 0, right: 5, bottom: 0),
child: Row(mainAxisAlignment: MainAxisAlignment.start, children: [
IconButton(
icon: element.executed
? Icon(Icons.check_box, color: Colors.green[200])
: Icon(
Icons.indeterminate_check_box,
color: Colors.blue.shade800,
),
onPressed: () => {addExerciseByPlanEvent(bloc, element)},
),
SizedBox(width: 20),
Flexible(
fit: FlexFit.tight,
child: InkWell(
child: Text(
element.name,
textAlign: TextAlign.start,
style: GoogleFonts.inter(fontSize: 17, color: Colors.black),
),
onTap: () => {addExerciseByPlanEvent(bloc, element)},
),
),
IconButton(
padding: EdgeInsets.all(0),
icon: Icon(
Icons.info,
color: Colors.black12,
),
onPressed: () {},
),
]),
)),
children: []));
}
});
return list;
}
void addExerciseByPlanEvent(ExerciseExecutePlanBloc bloc, WorkoutMenuTree workoutTree) {
LinkedHashMap args = LinkedHashMap();
args['blocExerciseByPlan'] = bloc;
args['customerId'] = bloc.customerId;
args['workoutTree'] = workoutTree;
Navigator.of(context).pushNamed("exerciseExecuteAddPage", arguments: args);
}
}

View File

@ -1,284 +0,0 @@
import 'dart:collection';
import 'package:aitrainer_app/bloc/exercise_execute_plan/exercise_execute_plan_bloc.dart';
import 'package:aitrainer_app/bloc/exercise_execute_plan_add/exercise_execute_plan_add_bloc.dart';
import 'package:aitrainer_app/library/custom_icon_icons.dart';
import 'package:aitrainer_app/util/app_language.dart';
import 'package:aitrainer_app/model/workout_menu_tree.dart';
import 'package:aitrainer_app/repository/exercise_repository.dart';
import 'package:aitrainer_app/util/trans.dart';
import 'package:aitrainer_app/widgets/app_bar.dart';
import 'package:aitrainer_app/widgets/number_picker.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:modal_progress_hud_nsn/modal_progress_hud_nsn.dart';
class ExerciseExecutePlanAddPage extends StatefulWidget {
_ExerciseExecuteAddPage createState() => _ExerciseExecuteAddPage();
}
class _ExerciseExecuteAddPage extends State<ExerciseExecutePlanAddPage> with Trans {
final ScrollController _controller = ScrollController();
double offset = 0;
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
LinkedHashMap arguments = ModalRoute.of(context)!.settings.arguments as LinkedHashMap;
// ignore: close_sinks
final ExerciseExecutePlanBloc planBloc = arguments['blocExerciseByPlan'];
final int customerId = arguments['customerId'];
final WorkoutMenuTree workoutTree = arguments['workoutTree'];
final ExerciseRepository exerciseRepository = ExerciseRepository();
setContext(context);
return BlocProvider(
create: (context) => ExerciseExecutePlanAddBloc(
exerciseRepository: exerciseRepository,
exercisePlanRepository: planBloc.exercisePlanRepository,
customerId: customerId,
workoutTree: workoutTree,
planBloc: planBloc)
..add(ExerciseExecutePlanAddLoad()),
child: BlocConsumer<ExerciseExecutePlanAddBloc, ExerciseExecutePlanAddState>(listener: (context, state) {
if (state is ExerciseExecutePlanAddError) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(backgroundColor: Colors.orange, content: Text(state.message, style: TextStyle(color: Colors.white))));
}
}, builder: (context, state) {
// ignore: close_sinks
final exerciseBloc = BlocProvider.of<ExerciseExecutePlanAddBloc>(context);
if (state is ExerciseExecutePlanAddReady && _controller.hasClients) {
_controller.animateTo(exerciseBloc.scrollOffset, duration: Duration(milliseconds: 300), curve: Curves.easeIn);
}
return ModalProgressHUD(
child: getControlForm(exerciseBloc),
inAsyncCall: state is ExerciseExecutePlanAddLoading,
opacity: 0.5,
color: Colors.black54,
progressIndicator: CircularProgressIndicator(),
);
}));
}
Widget getControlForm(ExerciseExecutePlanAddBloc exerciseBloc) {
if (exerciseBloc.exerciseRepository.exerciseType == null || exerciseBloc.quantity == null) {
return Offstage();
}
String exerciseName = AppLanguage().appLocal == Locale("en")
? exerciseBloc.exerciseRepository.exerciseType!.name
: exerciseBloc.exerciseRepository.exerciseType!.nameTranslation;
return Form(
child: Scaffold(
resizeToAvoidBottomInset: true,
appBar: AppBarNav(depth: 1),
body: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('asset/image/WT_black_background.jpg'),
fit: BoxFit.fill,
alignment: Alignment.center,
),
),
child: Container(
padding: const EdgeInsets.only(top: 25, left: 25, right: 25),
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
physics: BouncingScrollPhysics(),
controller: _controller,
child: Column(mainAxisAlignment: MainAxisAlignment.spaceAround, children: <Widget>[
Text(
t("Save Exercise"),
style: GoogleFonts.inter(fontSize: 16, color: Colors.orange[50]),
),
Text(
exerciseName,
style: GoogleFonts.archivoBlack(
fontSize: 24,
color: Colors.orange[700],
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,
overflow: TextOverflow.fade,
maxLines: 3,
softWrap: true,
),
Divider(
color: Colors.transparent,
),
Divider(),
Column(
children: repeatExercises(exerciseBloc),
),
Divider(),
]),
))),
),
);
}
List<Column> repeatExercises(ExerciseExecutePlanAddBloc exerciseBloc) {
List<Column> listColumns = [];
for (int i = 0; i < exerciseBloc.countSteps; i++) {
Column col = Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Divider(
color: Colors.transparent,
),
RichText(
text: TextSpan(
style: GoogleFonts.inter(
fontSize: 16,
fontWeight: FontWeight.normal,
color: Colors.yellow[300],
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,
),
],
),
children: [
TextSpan(text: t("Execute the") + " "),
TextSpan(
text: (i + 1).toString() + ". ",
style: GoogleFonts.inter(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.yellow[600],
),
),
TextSpan(text: t("set!"))
]),
),
Divider(
color: Colors.transparent,
),
Row(mainAxisAlignment: MainAxisAlignment.start, children: [
exerciseBloc.exerciseRepository.exerciseType!.unitQuantityUnit == null
? Offstage()
: NumberPickerWidget(
minValue: 0,
maxValue: 1000,
fontSize: 16,
initalValue: exerciseBloc.unitQuantity!.toInt(),
unit: t(exerciseBloc.exerciseRepository.exerciseType!.unitQuantityUnit!),
color: Colors.yellow[50]!,
onChange: (value) => {exerciseBloc.add(ExerciseExecutePlanAddChangeUnitQuantity(quantity: value.toDouble()))}),
NumberPickerWidget(
minValue: 0,
maxValue: 200,
fontSize: 16,
initalValue: exerciseBloc.quantity!.toInt(),
unit: t(exerciseBloc.exerciseRepository.exerciseType!.unit), //t("repeat"),
color: Colors.yellow[50]!,
onChange: (value) => {exerciseBloc.add(ExerciseExecutePlanAddChangeQuantity(quantity: value.toDouble()))}),
]),
TextButton(
style: TextButton.styleFrom(
padding: EdgeInsets.all(0),
primary: Colors.white,
onSurface: Colors.blueAccent,
),
onPressed: () => {
if (exerciseBloc.step == i + 1) {exerciseBloc.add(ExerciseExecutePlanAddSubmit())},
if (i + 1 == exerciseBloc.countSteps) {Navigator.of(context).pop()}
},
child: exerciseBloc.step == i + 1
? 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),
),
],
)
: Stack(
alignment: Alignment.center,
children: getButton(i + 1, exerciseBloc),
)),
/* Row(children: [
NumberPicker.horizontal(
highlightSelectedValue: (i + 1) == exerciseBloc.step,
initialValue: exerciseBloc.quantity.toInt(),
minValue: 0,
maxValue: 200,
step: 1,
textStyle: TextStyle(fontWeight: FontWeight.bold),
textStyleHighlighted: TextStyle(fontSize: 24, color: Colors.deepOrange, fontWeight: FontWeight.bold),
onChanged: (value) => {exerciseBloc.add(ExerciseExecutePlanAddChangeQuantity(quantity: value.toDouble()))},
listViewHeight: 80,
//decoration: _decoration,
),
Text(t("repeat")),
]), */
/* RaisedButton(
padding: EdgeInsets.all(0),
textColor: Colors.white,
color: exerciseBloc.step == i + 1 ? Colors.blue : Colors.black26,
focusColor: Colors.blueAccent,
onPressed: () => {
if (exerciseBloc.step == i + 1) {exerciseBloc.add(ExerciseExecutePlanAddSubmit())},
if (i + 1 == exerciseBloc.countSteps) {Navigator.of(context).pop()}
},
child: Text(
t("Save"),
style: TextStyle(fontSize: 12),
)), */
Divider(),
],
);
listColumns.add(col);
}
return listColumns;
}
List<Widget> getButton(int step, ExerciseExecutePlanAddBloc exerciseBloc) {
List<Widget> widgets = [];
if (step < exerciseBloc.step) {
widgets.add(Icon(
CustomIcon.check_circle,
color: Color(0xffb4f500),
size: 36,
));
} else {
widgets.add(Icon(
CustomIcon.question,
color: Colors.grey[700],
size: 36,
));
}
return widgets;
}
}

View File

@ -220,7 +220,7 @@ class ExerciseLogPage extends StatelessWidget with Trans, Common {
return DialogPremium(
unlocked: Cache().hasPurchased,
unlockRound: 1,
unlockedText: t("Enjoy also this premium fetaure to show all old evaluation data of your successful exercises."),
unlockedText: t("Enjoy also this premium feature to show all old evaluation data of your successful exercises."),
function: "My Exercise Logs",
onTap: () => {Navigator.of(context).pop()},
onCancel: () => {Navigator.of(context).pop()},

View File

@ -5,6 +5,7 @@ import 'package:aitrainer_app/repository/customer_repository.dart';
import 'package:aitrainer_app/repository/exercise_repository.dart';
import 'package:aitrainer_app/util/enums.dart';
import 'package:aitrainer_app/util/track.dart';
import 'package:aitrainer_app/widgets/dialog_common.dart';
import 'package:aitrainer_app/widgets/dialog_premium.dart';
import 'package:badges/badges.dart';
import 'package:google_fonts/google_fonts.dart';
@ -93,29 +94,27 @@ class _MyDevelopmentPage extends State<MyDevelopmentPage> with Trans {
Navigator.of(context).pushNamed('mydevelopmentBodyPage', arguments: args)
}
else
{}
},
isLocked: true,
),
/* ImageButton(
width: imageWidth,
textAlignment: Alignment.topLeft,
text: t("My Sizes Development"),
style: GoogleFonts.robotoMono(
textStyle: TextStyle(
fontSize: 14, color: Colors.white, fontWeight: FontWeight.bold, backgroundColor: Colors.black54.withOpacity(0.4)),
),
image: "asset/image/testemfejl400x400.jpg",
left: 5,
onTap: () => {
if (Cache().userLoggedIn != null)
{
args['customerId'] = Cache().userLoggedIn.customerId,
Navigator.of(context).pushNamed('mydevelopmentSizesPage', arguments: args)
showDialog(
context: context,
builder: (BuildContext context) {
return DialogCommon(
warning: true,
title: t("Warning"),
descriptions: t("Please log in"),
description2:
t("because only that way can we show you the personalized development diagrams and analysises"),
text: "OK",
onTap: () => Navigator.of(context).popAndPushNamed("login"),
onCancel: () => {
Navigator.of(context).pop(),
},
);
})
}
},
isLocked: true,
), */
),
Badge(
padding: EdgeInsets.all(8),
position: BadgePosition.topEnd(top: -5, end: -3),
@ -221,6 +220,22 @@ class _MyDevelopmentPage extends State<MyDevelopmentPage> with Trans {
args['customerRepository'] = customerRepository;
args['customerId'] = Cache().userLoggedIn!.customerId;
Navigator.of(context).pushNamed('exerciseLogPage', arguments: args);
} else {
showDialog(
context: context,
builder: (BuildContext context) {
return DialogCommon(
warning: true,
title: t("Warning"),
descriptions: t("Please log in"),
description2: t("because only that way can we show you your exercises, results and evaluations."),
text: "OK",
onTap: () => Navigator.of(context).popAndPushNamed("login"),
onCancel: () => {
Navigator.of(context).pop(),
},
);
});
}
}
}

View File

@ -104,7 +104,7 @@ class RegistrationPage extends StatelessWidget with Trans {
child: Text(
t("Skip"),
textAlign: TextAlign.right,
style: GoogleFonts.inter(color: Colors.black, decoration: TextDecoration.underline),
style: GoogleFonts.inter(color: loginBloc.testColor, decoration: TextDecoration.underline),
)),
SizedBox(
height: 120,
@ -203,7 +203,7 @@ class RegistrationPage extends StatelessWidget with Trans {
color: Colors.transparent,
),
getDataProtection(loginBloc),
getEmailSubscription(loginBloc),
loginBloc.emailCheckbox ? getEmailSubscription(loginBloc) : Offstage(),
Divider(
color: Colors.transparent,
),
@ -220,7 +220,6 @@ class RegistrationPage extends StatelessWidget with Trans {
),
],
),
//Image.asset('asset/icon/gomb_zold_b-1.png', width: 100, height: 100),
onPressed: () => {loginBloc.add(RegistrationSubmit())}),
]),
Divider(

View File

@ -62,6 +62,8 @@ class SalesPage extends StatelessWidget with Trans, Logging {
final salesText = bloc.salesText != null ? bloc.salesText! : "";
final String html = salesText;
log("start SalesPageBuild");
return Container(
decoration: BoxDecoration(
image: DecorationImage(
@ -80,12 +82,124 @@ class SalesPage extends StatelessWidget with Trans, Logging {
"p": Style(
color: Colors.white,
fontSize: FontSize(16),
padding: const EdgeInsets.only(left: 20, right: 8, bottom: 4),
padding: const EdgeInsets.only(left: 10, right: 8, bottom: 4),
textShadow: <Shadow>[
Shadow(
offset: Offset(3.0, 3.0),
blurRadius: 12.0,
color: Colors.black54,
),
Shadow(
offset: Offset(-3.0, 3.0),
blurRadius: 6.0,
color: Colors.black54,
),
],
),
"strong": Style(
color: Colors.yellow[600],
color: Colors.orange[600],
fontSize: FontSize(16),
),
"h3": Style(
color: Colors.orange[600],
fontSize: FontSize(16),
textAlign: TextAlign.center,
padding: const EdgeInsets.all(12),
),
"li": Style(
color: Colors.white,
fontSize: FontSize(16),
padding: const EdgeInsets.only(left: 10, bottom: 10, right: 8),
//before: "*",
textShadow: <Shadow>[
Shadow(
offset: Offset(3.0, 3.0),
blurRadius: 12.0,
color: Colors.black54,
),
Shadow(
offset: Offset(-3.0, 3.0),
blurRadius: 6.0,
color: Colors.black54,
),
],
//display: Display.LIST_ITEM,
),
"h2": Style(
color: Colors.orange[600],
fontWeight: FontWeight.bold,
fontSize: FontSize(24),
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),
),
"h1": Style(
color: Colors.orange[400],
fontWeight: FontWeight.bold,
fontSize: FontSize.larger,
alignment: Alignment.center,
padding: const EdgeInsets.all(4),
),
},
), // final Color bgrColor = Color(0xffb4f500);
//final Color bgrColorEnd = Colors.blue;
Container(
padding: EdgeInsets.only(left: 55, right: 55),
child: Text(
t("Tap on the button below the reach all premium content!"),
textAlign: TextAlign.center,
style: GoogleFonts.inter(color: Colors.white, fontSize: 13),
)),
Divider(),
Row(
children: [0, 1].map((idx) {
return Expanded(
flex: 1,
child: Container(
margin: EdgeInsets.symmetric(horizontal: 10),
child: AnimatedButton(
duration: 600,
darkShadow: true,
blurRadius: 12,
animationCurve: Curves.easeIn,
height: 150,
width: 160,
onTap: () => bloc.add(SalesPurchase(productId: bloc.product2Display[idx].productId)),
isMultiColor: true,
colors: [
//Colors.blue,
//Color(0xffb4f500),
//Color(0xffb4f500),
Colors.white,
Colors.yellow[50]!,
Colors.yellow[300]!,
],
child: Html(
data: bloc.productText2Display[idx],
//Optional parameters:
style: {
"p": Style(
color: Colors.blue,
fontSize: FontSize(14),
padding: const EdgeInsets.only(left: 5, right: 8, bottom: 5),
textAlign: TextAlign.center,
),
"strong": Style(
color: Colors.red[800],
fontWeight: FontWeight.bold,
fontSize: FontSize(14),
),
"h3": Style(
color: Colors.yellow[600],
fontSize: FontSize(16),
@ -95,121 +209,83 @@ class SalesPage extends StatelessWidget with Trans, Logging {
"li": Style(
color: Colors.white,
fontSize: FontSize(14),
padding: const EdgeInsets.only(left: 20, bottom: 10, right: 8),
padding: const EdgeInsets.only(left: 5, bottom: 10, right: 5),
//before: "*",
display: Display.LIST_ITEM),
"h2": Style(
color: Colors.yellow[600],
color: Colors.blue[600],
fontWeight: FontWeight.bold,
fontSize: FontSize(24),
fontSize: FontSize(16),
textAlign: TextAlign.center,
//padding: const EdgeInsets.all(4),
/* textShadow: <Shadow>[
Shadow(
offset: Offset(3.0, 3.0),
blurRadius: 4.0,
color: Colors.black54,
),
], */
),
"h1": Style(
color: Colors.yellow[400],
color: Colors.blue[400],
fontWeight: FontWeight.bold,
fontSize: FontSize.larger,
alignment: Alignment.center,
padding: const EdgeInsets.all(4),
),
},
), // final Color bgrColor = Color(0xffb4f500);
//final Color bgrColorEnd = Colors.blue;
AnimatedButton(
child: Html(
data: bloc.salesButtonText,
), // final Color bgr
)),
);
}).toList(),
),
getTrialDescription(),
Html(
data: bloc.premiumFunctions,
//Optional parameters:
style: {
"p": Style(
color: Colors.black,
fontSize: FontSize(16),
padding: const EdgeInsets.all(4),
textAlign: TextAlign.center,
textShadow: [
color: Colors.white,
fontSize: FontSize(14),
padding: const EdgeInsets.only(left: 10, right: 8, bottom: 4),
),
"strong": Style(
color: Colors.orange[600],
fontSize: FontSize(14),
textShadow: <Shadow>[
Shadow(
offset: Offset(2.0, 2.0),
blurRadius: 6.0,
blurRadius: 4.0,
color: Colors.black54,
)
),
],
),
"li": Style(
color: Colors.white,
fontSize: FontSize(14),
padding: const EdgeInsets.only(left: 10, bottom: 10),
before: "*",
padding: const EdgeInsets.only(left: 10, bottom: 3, right: 10),
),
"h2": Style(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: FontSize.larger,
textAlign: TextAlign.center,
textShadow: [
Shadow(
offset: Offset(2.0, 2.0),
blurRadius: 12.0,
color: Colors.black54,
)
],
//padding: const EdgeInsets.all(4),
),
"h1": Style(
color: Colors.yellow[600],
fontWeight: FontWeight.bold,
fontSize: FontSize.xLarge,
alignment: Alignment.center,
padding: const EdgeInsets.all(4),
fontSize: FontSize(16),
textAlign: TextAlign.center,
textShadow: [
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),
),
},
),
duration: 600,
darkShadow: true,
blurRadius: 12,
animationCurve: Curves.easeIn,
height: 120,
width: 320,
onTap: () => bloc.add(SalesPurchase(productId: bloc.offeredProduct!.productId)),
//color: Color(0xffb4f500),
isMultiColor: true,
colors: [
Colors.blue,
Color(0xffb4f500),
Color(0xffb4f500),
],
),
getTrialDescription(),
//Divider(),
AnimatedButton(
child: Html(
data: "<p>" + t("View other alternatives") + "</p>",
//Optional parameters:
style: {
"p": Style(
color: Colors.black,
fontSize: FontSize(14),
padding: const EdgeInsets.all(4),
textAlign: TextAlign.center,
),
},
),
onTap: () => bloc.add(SalesChangeSubscription()),
width: 320,
blurRadius: 6,
isMultiColor: true,
colors: [
Colors.white,
Colors.yellow[300]!,
],
),
SizedBox(
height: 30,

View File

@ -11,6 +11,7 @@ import 'package:aitrainer_app/util/app_language.dart';
import 'package:aitrainer_app/util/trans.dart';
import 'package:aitrainer_app/widgets/app_bar.dart';
import 'package:aitrainer_app/widgets/dialog_common.dart';
import 'package:aitrainer_app/widgets/dialog_premium.dart';
import 'package:aitrainer_app/widgets/menu_image.dart';
import 'package:aitrainer_app/widgets/treeview_parent_widget.dart';
import 'package:flutter/cupertino.dart';
@ -192,6 +193,8 @@ class TrainingPlanActivatePage extends StatelessWidget with Trans {
List<Widget> _getChildList(TrainingPlan plan, TrainingPlanBloc bloc) {
List<Widget> list = [];
bool restricted = (!plan.free && !Cache().hasPurchased);
list.add(Card(
margin: EdgeInsets.only(left: 10, top: 5),
color: Colors.white60,
@ -236,6 +239,58 @@ class TrainingPlanActivatePage extends StatelessWidget with Trans {
),
child: Text(t("Start")),
onPressed: () {
if (Cache().userLoggedIn == null) {
showDialog(
context: context,
builder: (BuildContext context) {
return DialogCommon(
warning: true,
title: t("Warning"),
descriptions: t("Please log in"),
description2: t("because only that way can we generated the training plan for you."),
text: "OK",
onTap: () => Navigator.of(context).popAndPushNamed("login"),
onCancel: () => {
Navigator.of(context).pop(),
},
);
});
} else {
if (restricted) {
showDialog(
context: context,
builder: (BuildContext context) {
return DialogPremium(
unlocked: Cache().hasPurchased,
unlockRound: 1,
unlockedText: t("Enjoy also this premium feature") + " " + t("to activate all available training programs."),
function: "Training Programs",
onTap: () => {Navigator.of(context).pop()},
onCancel: () => {Navigator.of(context).pop()},
);
});
} else {
activate(plan, bloc);
}
}
},
),
restricted
? Container(
padding: EdgeInsets.only(bottom: 8),
child: Text(
t("This is a premium function"),
style: GoogleFonts.inter(color: Colors.blue[700]),
),
)
: Offstage(),
]),
)));
return list;
}
void activate(TrainingPlan plan, TrainingPlanBloc bloc) {
if (Cache().myTrainingPlan != null) {
showCupertinoDialog(
useRootNavigator: true,
@ -270,12 +325,6 @@ class TrainingPlanActivatePage extends StatelessWidget with Trans {
} else {
bloc.add(TrainingPlanActivate(trainingPlanId: plan.trainingPlanId));
}
},
)
]),
)));
return list;
}
Widget getPlanDetails(TrainingPlan plan, TrainingPlanBloc bloc) {

View File

@ -123,7 +123,7 @@ class _ExercisePlanCustomPage extends State<TrainingPlanCustomPage> with Trans {
exerciseTypes.add(Container(
margin: const EdgeInsets.only(left: 4.0),
child: TreeViewChild(
startExpanded: false,
startExpanded: bloc.existsAddedExerciseTypeInTree(name),
parent: TreeviewParentWidget(text: name),
children: getTiles(list, bloc),
)));

View File

@ -1,7 +1,6 @@
import 'package:aitrainer_app/bloc/training_plan/training_plan_bloc.dart';
import 'package:aitrainer_app/util/app_language.dart';
import 'package:aitrainer_app/util/trans.dart';
import 'package:aitrainer_app/widgets/app_bar.dart';
import 'package:aitrainer_app/widgets/app_bar_min.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter/cupertino.dart';
@ -111,6 +110,9 @@ class _ExercisePlanDetailAddPage extends State<TrainingPlanCustomAddPage> with T
}
Widget getForm(TrainingPlanBloc bloc) {
if (bloc.getMyDetail() == null) {
return Offstage();
}
String exerciseName = "";
exerciseName = bloc.getExerciseName(AppLanguage().appLocal);
@ -124,7 +126,10 @@ class _ExercisePlanDetailAddPage extends State<TrainingPlanCustomAddPage> with T
return Form(
child: Scaffold(
resizeToAvoidBottomInset: true,
appBar: AppBarMin(back: true),
appBar: AppBarMin(
back: true,
onTap: () => Navigator.of(context).popAndPushNamed("myTrainingPlanCustom"),
),
body: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
@ -139,7 +144,7 @@ class _ExercisePlanDetailAddPage extends State<TrainingPlanCustomAddPage> with T
config: _buildConfig(context),
child: Container(
child: SingleChildScrollView(
padding: const EdgeInsets.only(top: 25, left: 95, right: 95),
padding: EdgeInsets.only(top: 25, left: 95, right: 95),
scrollDirection: Axis.vertical,
child: Column(mainAxisAlignment: MainAxisAlignment.spaceAround, children: <Widget>[
Text(t('Save The Exercise To The Training Plan'),

View File

@ -111,19 +111,37 @@ class MyTrainingPlans extends StatelessWidget with Trans, Logging {
left: 5,
textColor: color,
onTap: () {
if (Cache().userLoggedIn != null) {
// if (Cache().userLoggedIn != null) {
if (route == "myTrainingPlanActivate") {
HashMap<String, dynamic> args = HashMap();
args['parentName'] = parentName;
Navigator.of(context).pushNamed(route, arguments: args);
} else if (route == "myTrainingPlanExecute") {
if (Cache().userLoggedIn != null) {
final TrainingPlanBloc bloc = BlocProvider.of<TrainingPlanBloc>(context);
bloc.setMyPlan(Cache().myTrainingPlan);
Navigator.of(context).pushNamed(route);
} else {
showDialog(
context: context,
builder: (BuildContext context) {
return DialogCommon(
warning: true,
title: t("Warning"),
descriptions: t("Please log in"),
description2: t("because only in that way can you begin to execute a training plan"),
text: "OK",
onTap: () => Navigator.of(context).pushNamed("login"),
onCancel: () => {
Navigator.of(context).pop(),
},
);
});
}
} else {
Navigator.of(context).pushNamed(route);
}
}
// }
},
isLocked: false,
);

View File

@ -156,7 +156,9 @@ class _AppBarNav extends State<AppBarNav> with SingleTickerProviderStateMixin, C
}
int sizeExerciseList = Cache().getExercises() == null ? 0 : Cache().getExercises()!.length;
if (sizeExerciseList == 0) {
String text = AppLocalizations.of(context)!.translate("Make your first test");
String text = Cache().userLoggedIn == null
? AppLocalizations.of(context)!.translate("Please log in")
: AppLocalizations.of(context)!.translate("Make your first test");
double fontSize = text.length > 24 ? 13 : 16;
return Stack(alignment: Alignment.topLeft, children: [
GestureDetector(

View File

@ -9,7 +9,8 @@ import 'package:google_fonts/google_fonts.dart';
// ignore: must_be_immutable
class AppBarMin extends StatefulWidget implements PreferredSizeWidget {
bool back = false;
AppBarMin({this.back = false});
VoidCallback? onTap;
AppBarMin({this.back = false, this.onTap});
@override
_AppBarNav createState() => _AppBarNav();
@ -46,9 +47,13 @@ class _AppBarNav extends State<AppBarMin> with Common {
),
leading: IconButton(
icon: Icon(Icons.arrow_back, color: widget.back ? Colors.white : Colors.black),
onPressed: () => {
timerBloc.add(TimerEnd(duration: 0)),
if (widget.back) {Navigator.of(context).pop()}
onPressed: () {
timerBloc.add(TimerEnd(duration: 0));
if (widget.onTap != null) {
widget.onTap!();
} else if (widget.back) {
Navigator.of(context).pop();
}
},
));
}

View File

@ -313,9 +313,12 @@ class _BMIState extends State<BMI> with Trans {
textInputAction: TextInputAction.done,
style: GoogleFonts.archivoBlack(fontSize: 20, color: Colors.yellow[300]),
onChanged: (value) => {
if (value.isNotEmpty)
{
value = value.replaceFirst(",", "."),
value = value.replaceAll(RegExp(r'[^0-9.]'), ""),
widget.exerciseBloc.add(ExerciseNewHeightChange(value: double.parse(value))),
}
}),
);
} else {
@ -397,9 +400,12 @@ class _BMIState extends State<BMI> with Trans {
textInputAction: TextInputAction.done,
style: GoogleFonts.archivoBlack(fontSize: 20, color: Colors.yellow[300]),
onChanged: (value) => {
if (value.isNotEmpty)
{
value = value.replaceFirst(",", "."),
value = value.replaceAll(RegExp(r'[^0-9.]'), ""),
widget.exerciseBloc.add(ExerciseNewWeightChange(value: double.parse(value))),
}
},
),
),

View File

@ -217,9 +217,12 @@ class _BMRState extends State<BMR> with Trans {
textInputAction: TextInputAction.done,
style: GoogleFonts.archivoBlack(fontSize: 20, color: Colors.yellow[300]),
onChanged: (value) => {
if (value.isNotEmpty)
{
value = value.replaceFirst(",", "."),
value = value.replaceAll(RegExp(r'[^0-9.]'), ""),
widget.exerciseBloc.add(ExerciseNewHeightChange(value: double.parse(value))),
}
}),
);
} else {
@ -248,8 +251,11 @@ class _BMRState extends State<BMR> with Trans {
textInputAction: TextInputAction.done,
style: GoogleFonts.archivoBlack(fontSize: 20, color: Colors.yellow[300]),
onChanged: (value) => {
if (value.isNotEmpty)
{
value = value.replaceAll(RegExp(r'[^0-9.]'), ""),
widget.exerciseBloc.add(ExerciseNewBirthyearChange(value: int.parse(value)))
}
}),
);
}
@ -373,9 +379,12 @@ class _BMRState extends State<BMR> with Trans {
textInputAction: TextInputAction.done,
style: GoogleFonts.archivoBlack(fontSize: 20, color: Colors.yellow[300]),
onChanged: (value) => {
if (value.isNotEmpty)
{
value = value.replaceFirst(",", "."),
value = value.replaceAll(RegExp(r'[^0-9.]'), ""),
widget.exerciseBloc.add(ExerciseNewWeightChange(value: double.parse(value))),
}
},
),
),

View File

@ -162,7 +162,7 @@ class _DialogPremiumState extends State<DialogPremium> with Trans {
Align(
alignment: Alignment.center,
child: GestureDetector(
onTap: () => widget.unlocked ? Navigator.of(context).pop() : Navigator.of(context).pushNamed("salesPage"),
onTap: () => widget.unlocked ? Navigator.of(context).pop() : Navigator.of(context).popAndPushNamed("salesPage"),
child: Stack(
alignment: Alignment.center,
children: [

View File

@ -281,10 +281,12 @@ class _ExerciseSaveState extends State<ExerciseSave> with Trans {
keyboardType: TextInputType.numberWithOptions(decimal: true),
textInputAction: TextInputAction.done,
style: GoogleFonts.archivoBlack(fontSize: 80, color: Colors.yellow[300]),
onChanged: (value) => {
value = value.replaceFirst(",", "."),
value = value.replaceAll(RegExp(r'[^0-9.]'), ""),
widget.onUnitQuantityChanged!(double.parse(value)),
onChanged: (value) {
if (value.isNotEmpty) {
value = value.replaceFirst(",", ".");
value = value.replaceAll(RegExp(r'[^0-9.]'), "");
widget.onUnitQuantityChanged!(double.parse(value));
}
}),
]));
}
@ -395,9 +397,11 @@ class _ExerciseSaveState extends State<ExerciseSave> with Trans {
textInputAction: TextInputAction.next,
style: GoogleFonts.archivoBlack(fontSize: 80, color: Colors.orange[200]),
onChanged: (value) {
if (value.isNotEmpty) {
value = value.replaceFirst(",", ".");
value = value.replaceAll(RegExp(r'[^0-9.]'), "");
widget.onQuantityChanged(double.parse(value));
}
},
),
]));

View File

@ -157,9 +157,12 @@ class _InputDialogState<Event> extends State<InputDialog<Event>> with Trans {
textInputAction: TextInputAction.done,
style: GoogleFonts.archivoBlack(fontSize: 20, color: Colors.yellow[300]),
onChanged: (value) => {
if (value.isNotEmpty)
{
value = value.replaceFirst(",", "."),
value = value.replaceAll(RegExp(r'[^0-9.]'), ""),
this.inputValue = double.parse(value),
}
},
),
),

View File

@ -81,9 +81,7 @@ class _VictoryState extends State<Victory> {
void initState() {
animation.start();
animation.addStatusListener((status) {
if (status == AnimationStatus.completed) {
setState(() {});
}
if (status == AnimationStatus.completed) {}
});
super.initState();

View File

@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.1.18+82
version: 1.1.18+83
environment:
sdk: ">=2.12.0 <3.0.0"