WT 1.1.20 Training Plan improvements, InApp Messageing

This commit is contained in:
bossanyit
2021-06-25 17:30:14 +02:00
parent a595ffb358
commit 211307e63e
36 changed files with 693 additions and 300 deletions
+8 -8
View File
@@ -87,9 +87,9 @@ class LoginBloc extends Bloc<LoginEvent, LoginState> with Trans {
yield LoginSuccess();
} else if (event is RegistrationSubmit) {
yield LoginLoading();
if (!this.dataPolicyAllowed) {
/* if (!this.dataPolicyAllowed) {
throw Exception("Please accept our data policy");
}
} */
final String? validationError = validate();
if (validationError != null) {
yield LoginError(message: validationError);
@@ -104,9 +104,9 @@ class LoginBloc extends Bloc<LoginEvent, LoginState> with Trans {
}
} else if (event is RegistrationFB) {
yield LoginLoading();
if (!this.dataPolicyAllowed) {
/* if (!this.dataPolicyAllowed) {
throw Exception("Please accept our data policy");
}
} */
Cache().setLoginType(LoginType.fb);
await userRepository.addUserFB();
accountBloc.add(AccountLogInFinished(customer: Cache().userLoggedIn!));
@@ -116,9 +116,9 @@ class LoginBloc extends Bloc<LoginEvent, LoginState> with Trans {
yield LoginSuccess();
} else if (event is RegistrationGoogle) {
yield LoginLoading();
if (!this.dataPolicyAllowed) {
/* if (!this.dataPolicyAllowed) {
throw Exception("Please accept our data policy");
}
} */
Cache().setLoginType(LoginType.google);
await userRepository.addUserGoogle();
accountBloc.add(AccountLogInFinished(customer: Cache().userLoggedIn!));
@@ -128,9 +128,9 @@ class LoginBloc extends Bloc<LoginEvent, LoginState> with Trans {
yield LoginSuccess();
} else if (event is RegistrationApple) {
yield LoginLoading();
if (!this.dataPolicyAllowed) {
/* if (!this.dataPolicyAllowed) {
throw Exception("Please accept our data policy");
}
} */
Cache().setLoginType(LoginType.apple);
await userRepository.addUserApple();
accountBloc.add(AccountLogInFinished(customer: Cache().userLoggedIn!));
+54 -17
View File
@@ -58,15 +58,16 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
await Cache().saveMyTrainingPlan();
yield TrainingPlanFinished();
} else if (event is TrainingPlanWeightChange) {
yield TrainingPlanLoading();
yield TrainingPlanExerciseLoading();
event.detail.weight = event.weight;
yield TrainingPlanExerciseReady();
yield TrainingPlanReady();
} else if (event is TrainingPlanRepeatsChange) {
yield TrainingPlanLoading();
yield TrainingPlanExerciseLoading();
event.detail.repeats = event.repeats;
yield TrainingPlanExerciseReady();
yield TrainingPlanReady();
} else if (event is TrainingPlanSetChange) {
yield TrainingPlanLoading();
@@ -91,10 +92,13 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
} else if (event.detail.exercises.length >= 0) {
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);
// 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);
}
}
}
exercise.trainingPlanDetailsId = _myPlan!.trainingPlanId;
@@ -230,28 +234,61 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
dayNames.clear();
_myPlan!.days.clear();
String dayName = ".";
String previousDay = ".";
_myPlan!.details.forEach((element) {
if (element.day != null && element.day != dayName) {
dayNames.add(element.day!);
dayName = element.day!;
if (previousDay != ".") {
this.addExtraExerciseType("Stretching", previousDay);
}
}
if (_myPlan!.days[dayName] == null) {
if (dayName == ".") {
dayName = "";
}
_myPlan!.days[dayName] = [];
this.addExtraExerciseType("Warming Up", dayName);
previousDay = dayName;
}
_myPlan!.days[dayName]!.add(element);
});
if (dayNames.length == 0) {
dayNames.add("");
_myPlan!.days[""] = [];
_myPlan!.days[""]!.addAll(_myPlan!.details);
dayName = "";
dayNames.add(dayName);
_myPlan!.days[dayName] = [];
_myPlan!.days[dayName]!.addAll(_myPlan!.details);
}
getActiveDayIndex();
}
void addExtraExerciseType(String name, String dayName) {
if (Cache().getExerciseTypes() == null) {
return;
}
for (var exerciseType in Cache().getExerciseTypes()!) {
if (exerciseType.name == name) {
CustomerTrainingPlanDetails detail = CustomerTrainingPlanDetails();
detail.customerTrainingPlanDetailsId = 0;
detail.trainingPlanDetailsId = 0;
detail.exerciseTypeId = exerciseType.exerciseTypeId;
detail.repeats = 1;
detail.set = 1;
detail.day = "";
detail.parallel = false;
detail.restingTime = 0;
detail.exerciseType = exerciseType;
if (_myPlan!.days[dayName] == null) {
_myPlan!.days[dayName] = [];
}
_myPlan!.days[dayName]!.add(detail);
break;
}
}
}
CustomerTrainingPlanDetails? getTrainingPlanDetail(int trainingPlanDetailsId) {
CustomerTrainingPlanDetails? detail;
if (_myPlan == null || _myPlan!.details.isEmpty) {
@@ -297,13 +334,14 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
int minStep = 99;
for (final detail in this._myPlan!.details) {
if (!detail.state.equalsTo(ExercisePlanDetailState.finished)) {
if (detail.exercises.isEmpty && !detail.state.equalsTo(ExercisePlanDetailState.skipped)) {
final day = dayNames[this.activeDayIndex];
if (detail.exercises.isEmpty && !detail.state.equalsTo(ExercisePlanDetailState.skipped) && day == detail.day) {
next = detail;
minStep = 1;
break;
} else {
final int step = detail.exercises.length;
if (step < minStep && !detail.state.equalsTo(ExercisePlanDetailState.skipped)) {
if (step < minStep && !detail.state.equalsTo(ExercisePlanDetailState.skipped) && day == detail.day) {
next = detail;
minStep = step;
if (detail.parallel != true) {
@@ -313,7 +351,7 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
}
}
}
print("Next detail $next");
//print("Next detail $next");
return next;
}
@@ -379,20 +417,19 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
}
int getActiveDayIndex() {
activeDayIndex = 0;
if (restarting) {
return 0;
}
if (_myPlan == null || _myPlan!.details.isEmpty) {
// throw Exception("No defined Training Plan");
return 0;
}
if (dayNames.isEmpty || dayNames.length == 1) {
activeDayIndex = 0;
return 0;
}
activeDayIndex = 0;
for (var day in dayNames) {
if (_myPlan!.days[day] == null) {
throw Exception("Wrong activated day: $day does not exist");
@@ -413,7 +450,7 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
activeDayIndex = 0;
this.add(TrainingPlanGoToRestart());
}
print("ActiveDayIndex $activeDayIndex");
return activeDayIndex;
}
@@ -15,10 +15,18 @@ class TrainingPlanLoading extends TrainingPlanState {
const TrainingPlanLoading();
}
class TrainingPlanExerciseLoading extends TrainingPlanState {
const TrainingPlanExerciseLoading();
}
class TrainingPlanReady extends TrainingPlanState {
const TrainingPlanReady();
}
class TrainingPlanExerciseReady extends TrainingPlanState {
const TrainingPlanExerciseReady();
}
class TrainingPlanFinished extends TrainingPlanState {
const TrainingPlanFinished();
}
+3 -4
View File
@@ -3,7 +3,6 @@ import 'dart:io';
import 'package:aitrainer_app/bloc/test_set_execute/test_set_execute_bloc.dart';
import 'package:aitrainer_app/bloc/training_plan/training_plan_bloc.dart';
import 'package:aitrainer_app/bloc/tutorial/tutorial_bloc.dart';
import 'package:aitrainer_app/push_notifications.dart';
import 'package:aitrainer_app/repository/customer_repository.dart';
import 'package:aitrainer_app/repository/training_plan_repository.dart';
import 'package:aitrainer_app/repository/workout_tree_repository.dart';
@@ -44,7 +43,7 @@ import 'package:aitrainer_app/widgets/home.dart';
import 'package:aitrainer_app/library/facebook_app_events/facebook_app_events.dart';
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:firebase_analytics/observer.dart';
import 'package:flurry/flurry.dart';
import 'package:flurry_data/flurry_data.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
@@ -110,6 +109,7 @@ 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.
@@ -192,10 +192,9 @@ Future<Null> main() async {
Future<void> initThirdParty() async {
if (!isInDebugMode) {
await Flurry.initialize(androidKey: "JNYCTCWBT34FM3J8TV36", iosKey: "3QBG7BSMGPDH24S8TRQP", enableLog: true);
await FlurryData.initialize(androidKey: "JNYCTCWBT34FM3J8TV36", iosKey: "3QBG7BSMGPDH24S8TRQP", enableLog: true);
FlutterUxcam.optIntoSchematicRecordings();
}
PushNotificationsManager().init();
}
class WorkoutTestApp extends StatelessWidget {
+8 -2
View File
@@ -19,6 +19,7 @@ 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/training_plan_day.dart';
import 'package:aitrainer_app/model/tutorial.dart';
import 'package:aitrainer_app/model/workout_menu_tree.dart';
import 'package:aitrainer_app/repository/customer_repository.dart';
@@ -30,7 +31,7 @@ import 'package:aitrainer_app/util/enums.dart';
import 'package:aitrainer_app/util/env.dart';
import 'package:aitrainer_app/util/track.dart';
import 'package:firebase_remote_config/firebase_remote_config.dart';
import 'package:flurry/flurry.dart';
import 'package:flurry_data/flurry_data.dart';
import 'package:flutter_facebook_auth/flutter_facebook_auth.dart';
import 'package:flutter_uxcam/flutter_uxcam.dart';
import 'package:package_info/package_info.dart';
@@ -137,6 +138,7 @@ class Cache with Logging {
List<wt_product.Product>? _products;
List<Purchase> _purchases = [];
List<SplitTest> _splitTests = [];
List<TrainingPlanDay> _trainingPlanDays = [];
List<ExercisePlanTemplate> _exercisePlanTemplates = [];
@@ -184,6 +186,7 @@ class Cache with Logging {
String testEnv = EnvironmentConfig.test_env;
this.testEnvironment = testEnv;
if (testEnv == "1") {
print("testEnv $testEnv");
baseUrl = baseUrlTest;
liveServer = false;
}
@@ -668,7 +671,7 @@ class Cache with Logging {
await PackageApi().getCustomerPackage(customerId);
if (!isInDebugMode) {
Flurry.setUserId(customerId.toString());
FlurryData.setUserId(customerId.toString());
//Smartlook.setUserIdentifier(customerId.toString());
FlutterUxcam.setUserProperty("username", customerId.toString());
FlutterUxcam.setUserIdentity(customerId.toString());
@@ -737,4 +740,7 @@ class Cache with Logging {
List<SplitTest> getSplitTests() => this._splitTests;
setSplitTests(value) => this._splitTests = value;
List<TrainingPlanDay> getTrainingPlanDays() => this._trainingPlanDays;
setTrainingPlanDays(value) => this._trainingPlanDays = value;
}
+4 -4
View File
@@ -4,7 +4,7 @@ class CustomerProperty {
int? customerPropertyId;
late int propertyId;
late int customerId;
late DateTime dateAdd;
DateTime? dateAdd;
late double propertyValue;
bool newData = false;
@@ -14,7 +14,7 @@ class CustomerProperty {
this.customerPropertyId = json['customerPropertyId'];
this.propertyId = json['propertyId'];
this.customerId = json['customerId'];
this.dateAdd = json['propertyName'];
this.dateAdd = json['dataAdd'] ?? DateTime.now();
this.propertyValue = json['propertyValue'];
}
@@ -24,14 +24,14 @@ class CustomerProperty {
"customerPropertyId": this.customerPropertyId,
"propertyId": this.propertyId,
"customerId": this.customerId,
"dateAdd": DateFormat('yyyy-MM-dd HH:mm:ss').format(this.dateAdd),
"dateAdd": DateFormat('yyyy-MM-dd HH:mm:ss').format(this.dateAdd!),
"propertyValue": this.propertyValue
};
} else {
return {
"propertyId": this.propertyId,
"customerId": this.customerId,
"dateAdd": DateFormat('yyyy-MM-dd HH:mm:ss').format(this.dateAdd),
"dateAdd": DateFormat('yyyy-MM-dd HH:mm:ss').format(this.dateAdd!),
"propertyValue": this.propertyValue
};
}
+26 -8
View File
@@ -2,6 +2,7 @@ import 'package:aitrainer_app/model/cache.dart';
import 'package:aitrainer_app/model/exercise.dart';
import 'package:aitrainer_app/model/exercise_plan_detail.dart';
import 'package:aitrainer_app/model/exercise_type.dart';
import 'package:aitrainer_app/repository/training_plan_day_repository.dart';
class CustomerTrainingPlanDetails {
/// customerTrainingPlanDetails
@@ -26,6 +27,8 @@ class CustomerTrainingPlanDetails {
bool? parallel;
String? day;
int? dayId;
/// exerciseType
ExerciseType? exerciseType;
@@ -63,10 +66,10 @@ class CustomerTrainingPlanDetails {
: json['parallel'] == "true"
? true
: null;
this.day = json['day'].toString();
if (this.day == null || this.day == "null") {
this.day = "";
}
this.dayId = json['dayId'] == "null" ? null : json['dayId'];
TrainingPlanDayRepository trainingPlanDayRepository = TrainingPlanDayRepository();
this.day = trainingPlanDayRepository.getNameById(this.dayId);
try {
Iterable iterable = json['exercises'];
this.exercises = iterable.map((exercise) => Exercise.fromJson(exercise)).toList();
@@ -114,15 +117,30 @@ class CustomerTrainingPlanDetails {
'exercises': exercises.isEmpty ? [].toString() : exercises.map((exercise) => exercise.toJson()).toList().toString(),
'state': this.state.toStr(),
"isTest": this.isTest,
"dayId": this.dayId,
};
if (this.day != null && this.day!.isNotEmpty) {
jsonMap["day"] = this.day;
}
//print("Detail $jsonMap");
//print("Detail toJson $jsonMap");
return jsonMap;
}
@override
String toString() => this.toJsonWithExercises().toString();
void copy(CustomerTrainingPlanDetails from) {
this.customerTrainingPlanDetailsId = from.customerTrainingPlanDetailsId;
this.trainingPlanDetailsId = from.trainingPlanDetailsId;
this.exerciseTypeId = from.exerciseTypeId;
this.exerciseType = from.exerciseType;
this.set = from.set;
this.repeats = from.repeats;
this.weight = from.weight;
this.restingTime = from.restingTime;
this.parallel = from.parallel;
this.exercises = from.exercises;
this.state = from.state;
this.isTest = from.isTest;
this.day = from.day;
this.dayId = from.dayId;
}
}
+3 -3
View File
@@ -1,10 +1,10 @@
import 'package:aitrainer_app/model/evaluation_attribute.dart';
class Evaluation {
late int evaluationId;
int? evaluationId;
late String name;
late int exerciseTypeId;
late String unit;
int? exerciseTypeId;
String? unit;
late List attributes;
Evaluation.fromJson(Map json) {
+1 -1
View File
@@ -1,6 +1,6 @@
class EvaluationAttribute {
late int evaluationAttrId;
late int evaluationId;
int? evaluationId;
late String name;
late String sex;
late int ageMin;
+6 -1
View File
@@ -17,7 +17,7 @@ class ExerciseType {
late String unit;
/// unitQuantity
late String unitQuantity;
String? unitQuantity;
/// unitQuantityUnit
String? unitQuantityUnit;
@@ -28,6 +28,8 @@ class ExerciseType {
/// base
late bool base;
late bool buddyWarning;
/// imageUrl
String imageUrl = "";
@@ -65,6 +67,7 @@ class ExerciseType {
this.unitQuantityUnit = json['unitQuantityUnit'];
this.active = json['active'];
this.base = json['base'];
this.buddyWarning = json['buddyWarning'];
if (json['images'].length > 0) {
this.imageUrl = json['images'][0]['url'];
}
@@ -105,6 +108,8 @@ class ExerciseType {
"unitQuantity": unitQuantity,
"unitQuantityUnit": unitQuantityUnit,
"active": active,
"base": base,
"buddyWarning": buddyWarning,
"devices": this.devices.toString(),
"nameTranslation": this.nameTranslation,
"parents": this.parents.toString()
+4 -4
View File
@@ -4,10 +4,10 @@ import 'package:aitrainer_app/model/training_plan_detail.dart';
class TrainingPlan {
late int trainingPlanId;
late String type;
String? type;
late String name;
late String internalName;
late String description;
String? internalName;
String? description;
late bool free;
late bool active;
int? treeId;
@@ -28,7 +28,7 @@ class TrainingPlan {
this.treeId = json['treeId'];
nameTranslations['en'] = name;
descriptionTranslations['en'] = description;
descriptionTranslations['en'] = description ?? "";
if (json['translations'] != null && json['translations'].length > 0) {
json['translations'].forEach((translation) {
nameTranslations[translation['languageCode']] = translation['nameTranslation'];
+29
View File
@@ -0,0 +1,29 @@
import 'dart:collection';
class TrainingPlanDay {
late int dayId;
late String name;
HashMap<String, String> nameTranslations = HashMap();
TrainingPlanDay.fromJson(Map json) {
this.dayId = json['dayId'];
this.name = json['name'];
nameTranslations['en'] = name;
if (json['translations'] != null && json['translations'].length > 0) {
json['translations'].forEach((translation) {
nameTranslations[translation['languageCode']] = translation['nameTranslation'];
});
}
}
Map<String, dynamic> toJson() => {
"dayId": this.dayId,
"name": this.name,
"nameTranslation": this.nameTranslations.toString(),
};
@override
String toString() => this.toJson().toString();
}
+9 -7
View File
@@ -1,14 +1,15 @@
class TrainingPlanDetail {
late int trainingPlanDetailId;
late int trainingPlanId;
int? trainingPlanId;
late int exerciseTypeId;
late int sort;
late int set;
late int repeats;
late double weight;
late int restingTime;
late bool parallel;
late String day;
int? repeats;
double? weight;
int? restingTime;
bool? parallel;
int? dayId;
String? day;
TrainingPlanDetail.fromJson(Map<String, dynamic> json) {
this.trainingPlanDetailId = json['trainingPlanDetailId'];
@@ -20,7 +21,7 @@ class TrainingPlanDetail {
this.weight = json['weight'];
this.restingTime = json['restingTime'];
this.parallel = json['parallel'];
this.day = json['day'];
this.dayId = json['dayId'];
}
Map<String, dynamic> toJson() => {
@@ -32,6 +33,7 @@ class TrainingPlanDetail {
"weight": this.weight,
"restingTime": this.restingTime,
"parallel": this.parallel,
"dayId": this.dayId,
"day": this.day,
};
-34
View File
@@ -1,34 +0,0 @@
import 'package:aitrainer_app/service/logging.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
class PushNotificationsManager with Logging {
PushNotificationsManager._();
factory PushNotificationsManager() => _instance;
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
'This channel is used for important notifications.', // description
importance: Importance.max,
);
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);
await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
alert: true, // Required to display a heads up notification
badge: true,
sound: true,
);
String? token = await FirebaseMessaging.instance.getToken();
log("FirebaseMessaging token $token");
}
}
@@ -0,0 +1,35 @@
import 'package:aitrainer_app/model/cache.dart';
import 'package:aitrainer_app/model/training_plan.dart';
import 'package:aitrainer_app/util/app_language.dart';
class TrainingPlanDayRepository {
const TrainingPlanDayRepository();
void assignTrainingPlanDays() {
List<TrainingPlan>? plans = Cache().getTrainingPlans();
if (plans == null) {
return;
}
plans.forEach((plan) {
if (plan.details != null) {
plan.details!.forEach((element) {
element.day = this.getNameById(element.dayId);
});
}
});
}
String? getNameById(int? dayId) {
if (dayId == null) {
return "";
}
String? name;
for (var day in Cache().getTrainingPlanDays()) {
if (day.dayId == dayId) {
name = day.nameTranslations[AppLanguage().appLocal.languageCode];
break;
}
}
return name;
}
}
+30 -5
View File
@@ -5,6 +5,7 @@ import 'package:aitrainer_app/model/exercise.dart';
import 'package:aitrainer_app/model/exercise_plan_detail.dart';
import 'package:aitrainer_app/model/exercise_tree.dart';
import 'package:aitrainer_app/model/training_plan.dart';
import 'package:aitrainer_app/repository/training_plan_day_repository.dart';
import 'package:aitrainer_app/service/training_plan_service.dart';
import 'package:aitrainer_app/util/common.dart';
@@ -66,6 +67,7 @@ class TrainingPlanRepository {
// 3 calculate weights
int index = 0;
trainingPlan.details!.forEach((elem) {
CustomerTrainingPlanDetails detail = CustomerTrainingPlanDetails();
detail.customerTrainingPlanDetailsId = ++index;
@@ -73,7 +75,9 @@ class TrainingPlanRepository {
detail.exerciseTypeId = elem.exerciseTypeId;
detail.repeats = elem.repeats;
detail.set = elem.set;
detail.day = elem.day;
detail.dayId = elem.dayId;
TrainingPlanDayRepository trainingPlanDayRepository = TrainingPlanDayRepository();
detail.day = trainingPlanDayRepository.getNameById(elem.dayId);
detail.parallel = elem.parallel;
detail.restingTime = elem.restingTime;
detail.exerciseType = Cache().getExerciseTypeById(detail.exerciseTypeId!);
@@ -83,6 +87,13 @@ class TrainingPlanRepository {
} else {
detail.weight = 0;
}
} else if (elem.weight == -2) {
final CustomerTrainingPlanDetails calculated = this.isWeightCalculatedByExerciseType(elem.exerciseTypeId, detail, plan);
if (calculated.weight != -1) {
detail.weight = calculated.weight;
} else {
detail.weight = -2;
}
} else {
detail.weight = elem.weight;
}
@@ -98,6 +109,19 @@ class TrainingPlanRepository {
return plan;
}
CustomerTrainingPlanDetails isWeightCalculatedByExerciseType(
int exerciseTypeId, CustomerTrainingPlanDetails detail, CustomerTrainingPlan plan) {
CustomerTrainingPlanDetails calculated = detail;
for (var element in plan.details) {
if (element.exerciseTypeId == exerciseTypeId) {
calculated = element;
break;
}
}
return calculated;
}
TrainingPlan? getTrainingPlanById(int trainingPlanId) {
TrainingPlan? plan;
if (Cache().getTrainingPlans() == null) {
@@ -148,8 +172,9 @@ class TrainingPlanRepository {
return detail;
}
CustomerTrainingPlanDetails recalculateDetail(int trainingPlanId, CustomerTrainingPlanDetails detail) {
CustomerTrainingPlanDetails recalculatedDetail = detail;
CustomerTrainingPlanDetails recalculateDetail(
int trainingPlanId, CustomerTrainingPlanDetails detail, CustomerTrainingPlanDetails nextDetail) {
CustomerTrainingPlanDetails recalculatedDetail = nextDetail;
// 1. get original repeats
@@ -164,14 +189,14 @@ class TrainingPlanRepository {
plan.details!.forEach((element) {
if (element.trainingPlanDetailId == detail.trainingPlanDetailsId) {
print("element $element");
originalRepeats = element.repeats;
originalRepeats = element.repeats ?? 0;
}
});
// 2 get recalculated repeats
recalculatedDetail.weight =
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;
+2 -2
View File
@@ -112,10 +112,10 @@ class WorkoutTreeRepository with Logging {
0,
"");
this.tree[exerciseType.name] = menuItem;
if (isRunning || is1RM) {
if (isRunning || is1RM || exerciseType.name == "Warming Up" || exerciseType.name == "Stretching") {
menuAsExercise.add(menuItem);
}
//log("ExerciseType in Menu item ${exerciseType.toJson()} is1RM: $is1RM");
//log("ExerciseType in Menu item ${exerciseType.toJson()}");
});
} else {
//log("No Parents " + exerciseType.toJson().toString());
+128 -23
View File
@@ -1,15 +1,20 @@
import 'dart:math' as math;
import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:aitrainer_app/model/cache.dart';
import 'package:aitrainer_app/service/logging.dart' as logging;
import 'package:apple_sign_in/apple_sign_in.dart';
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
import 'package:awesome_notifications/awesome_notifications.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:firebase_remote_config/firebase_remote_config.dart';
import 'package:flutter/material.dart';
import 'package:flutter_facebook_auth/flutter_facebook_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
class FirebaseApi with logging.Logging {
bool appleSignInAvailable = false;
//late FirebaseApi _instance;
static final FirebaseAuth auth = FirebaseAuth.instance;
@@ -29,15 +34,69 @@ class FirebaseApi with logging.Logging {
Future<void> initializeFlutterFire() async {
try {
// Wait for Firebase to initialize and set `_initialized` state to true
FirebaseApp app = await Firebase.initializeApp();
await Firebase.initializeApp();
this.appleSignInAvailable = await AppleSignIn.isAvailable();
this.appleSignInAvailable = await SignInWithApple.isAvailable();
AwesomeNotifications().initialize(
// set the icon to null if you want to use the default app icon
null,
[
NotificationChannel(
channelKey: 'basic_channel',
channelName: 'Basic notifications',
channelDescription: 'Notification channel for basic tests',
defaultColor: Color(0xFF9D50DD),
ledColor: Colors.white)
]);
AwesomeNotifications().isNotificationAllowed().then((isAllowed) {
if (!isAllowed) {
// Insert here your friendly dialog box before call the request method
// This is very important to not harm the user experience
AwesomeNotifications().requestPermissionToSendNotifications();
}
});
await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
alert: true, // Required to display a heads up notification
badge: true,
sound: true,
);
String? token = await FirebaseMessaging.instance.getToken();
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
log("FirebaseMessaging token $token");
} catch (e) {
// Set `_error` state to true if Firebase initialization fails
log("Error initializing Firebase");
}
}
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
print('Handling a background message: ${message.messageId}');
if (!StringUtils.isNullOrEmpty(message.notification?.title, considerWhiteSpaceAsEmpty: true) ||
!StringUtils.isNullOrEmpty(message.notification?.body, considerWhiteSpaceAsEmpty: true)) {
print('message also contained a notification: ${message.notification}');
String? imageUrl;
imageUrl ??= message.notification!.android?.imageUrl;
imageUrl ??= message.notification!.apple?.imageUrl;
Map<String, dynamic> notificationAdapter = {
NOTIFICATION_CHANNEL_KEY: 'basic_channel',
NOTIFICATION_ID: message.data[NOTIFICATION_CONTENT]?[NOTIFICATION_ID] ?? message.messageId ?? math.Random().nextInt(2147483647),
NOTIFICATION_TITLE: message.data[NOTIFICATION_CONTENT]?[NOTIFICATION_TITLE] ?? message.notification?.title,
NOTIFICATION_BODY: message.data[NOTIFICATION_CONTENT]?[NOTIFICATION_BODY] ?? message.notification?.body,
NOTIFICATION_LAYOUT: StringUtils.isNullOrEmpty(imageUrl) ? 'Default' : 'BigPicture',
NOTIFICATION_BIG_PICTURE: imageUrl
};
AwesomeNotifications().createNotificationFromJsonData(notificationAdapter);
} else {
AwesomeNotifications().createNotificationFromJsonData(message.data);
}
}
Future<String> signInEmail(String? email, String? password) async {
if (email == null) {
throw Exception("Please type an email address");
@@ -85,20 +144,33 @@ class FirebaseApi with logging.Logging {
return rc;
}
String generateNonce([int length = 32]) {
final charset = '0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._';
final random = math.Random.secure();
return List.generate(length, (_) => charset[random.nextInt(charset.length)]).join();
}
/// Returns the sha256 hash of [input] in hex notation.
String sha256ofString(String input) {
final bytes = utf8.encode(input);
final digest = sha256.convert(bytes);
return digest.toString();
}
Future<Map<String, dynamic>> signInWithApple() async {
Map<String, dynamic> userData = Map();
final AuthorizationResult result = await AppleSignIn.performRequests([
AppleIdRequest(requestedScopes: [Scope.email, Scope.fullName])
/* final apple.AuthorizationResult result = await SignInWithApple.performRequests([
apple.AppleIdRequest(requestedScopes: [apple.Scope.email, apple.Scope.fullName])
]);
switch (result.status) {
case AuthorizationStatus.authorized:
case apple.AuthorizationStatus.authorized:
print('User authorized');
break;
case AuthorizationStatus.error:
case apple.AuthorizationStatus.error:
print('User error');
throw Exception("Apple Sign-In failed");
case AuthorizationStatus.cancelled:
case apple.AuthorizationStatus.cancelled:
print('User cancelled');
throw Exception("Apple Sign-In cancelled");
}
@@ -106,20 +178,36 @@ class FirebaseApi with logging.Logging {
// Create an `OAuthCredential` from the credential returned by Apple.
final oauthCredential = OAuthProvider("apple.com").credential(
idToken: String.fromCharCodes(result.credential.identityToken),
accessToken: String.fromCharCodes(result.credential.authorizationCode));
accessToken: String.fromCharCodes(result.credential.authorizationCode!));
*/
// To prevent replay attacks with the credential returned from Apple, we
// include a nonce in the credential request. When signing in with
// Firebase, the nonce in the id token returned by Apple, is expected to
// match the sha256 hash of `rawNonce`.
final rawNonce = generateNonce();
final nonce = sha256ofString(rawNonce);
// Request credential for the currently signed in Apple account.
final appleCredential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
nonce: nonce,
);
// Create an `OAuthCredential` from the credential returned by Apple.
final oauthCredential = OAuthProvider("apple.com").credential(
idToken: appleCredential.identityToken,
rawNonce: rawNonce,
);
// Sign in the user with Firebase. If the nonce we generated earlier does
// not match the nonce in `appleCredential.identityToken`, sign in will fail.
UserCredential userCredential = await FirebaseAuth.instance.signInWithCredential(oauthCredential);
Cache().firebaseUid = userCredential.user!.uid;
log("userCredential: " + userCredential.toString());
log("Apple Credentials: " +
result.credential.user.toString() +
" state " +
result.credential.state.toString() +
" email " +
userCredential.user!.email!);
log("Apple Credentials: ${appleCredential.userIdentifier} state ${appleCredential.state} email ${userCredential.user!.email!}");
userData['email'] = userCredential.user!.email;
return userData;
@@ -127,26 +215,43 @@ class FirebaseApi with logging.Logging {
Future<Map<String, dynamic>> registerWithApple() async {
Map<String, dynamic> userData = Map();
final AuthorizationResult result = await AppleSignIn.performRequests([
AppleIdRequest(requestedScopes: [Scope.email, Scope.fullName])
/* final apple.AuthorizationResult result = await apple.TheAppleSignIn.performRequests([
apple.AppleIdRequest(requestedScopes: [apple.Scope.email, apple.Scope.fullName])
]);
switch (result.status) {
case AuthorizationStatus.authorized:
case apple.AuthorizationStatus.authorized:
print('Apple User authorized');
break;
case AuthorizationStatus.error:
case apple.AuthorizationStatus.error:
print('Apple User error');
throw Exception("Apple Sign-In failed");
case AuthorizationStatus.cancelled:
case apple.AuthorizationStatus.cancelled:
print('User cancelled');
throw Exception("Apple Sign-In cancelled");
}
*/
final rawNonce = generateNonce();
final nonce = sha256ofString(rawNonce);
// Create an `OAuthCredential` from the credential returned by Apple.
// Request credential for the currently signed in Apple account.
final appleCredential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
nonce: nonce,
);
final oauthCredential = OAuthProvider("apple.com").credential(
idToken: appleCredential.identityToken,
rawNonce: rawNonce,
);
/* // Create an `OAuthCredential` from the credential returned by Apple.
final oauthCredential = OAuthProvider("apple.com").credential(
idToken: String.fromCharCodes(result.credential.identityToken),
accessToken: String.fromCharCodes(result.credential.authorizationCode));
*/
// Sign in the user with Firebase. If the nonce we generated earlier does
// not match the nonce in `appleCredential.identityToken`, sign in will fail.
UserCredential userCredential = await FirebaseAuth.instance.signInWithCredential(oauthCredential);
+9 -1
View File
@@ -19,7 +19,9 @@ 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/training_plan_day.dart';
import 'package:aitrainer_app/model/tutorial.dart';
import 'package:aitrainer_app/repository/training_plan_day_repository.dart';
import 'package:aitrainer_app/service/api.dart';
import 'package:aitrainer_app/service/exercise_type_service.dart';
import 'package:aitrainer_app/util/not_found_exception.dart';
@@ -105,6 +107,10 @@ class PackageApi {
final List<SplitTest>? tests = json.map((test) => SplitTest.fromJson(test)).toList();
//print("A/B tests: $tests");
Cache().setSplitTests(tests);
} else if (headRecord[0] == "TrainingPlanDay") {
final Iterable json = jsonDecode(headRecord[1]);
final List<TrainingPlanDay>? days = json.map((day) => TrainingPlanDay.fromJson(day)).toList();
Cache().setTrainingPlanDays(days);
}
});
@@ -114,9 +120,11 @@ class PackageApi {
ExerciseTree tree = element as ExerciseTree;
tree.imageUrl = await ExerciseTreeApi().buildImage(tree.imageUrl, tree.treeId);
});
//print("tree: $exerciseTree");
Cache().setExerciseTree(exerciseTree);
TrainingPlanDayRepository trainingPlanDayRepository = TrainingPlanDayRepository();
trainingPlanDayRepository.assignTrainingPlanDays();
return;
}
+2 -2
View File
@@ -5,7 +5,7 @@ import 'package:aitrainer_app/service/tracking_service.dart';
import 'package:aitrainer_app/util/enums.dart';
import 'package:aitrainer_app/model/tracking.dart' as model;
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:flurry/flurry.dart';
import 'package:flurry_data/flurry_data.dart';
import 'package:flutter_uxcam/flutter_uxcam.dart';
class Track with Logging {
@@ -20,7 +20,7 @@ class Track with Logging {
void track(TrackingEvent event, {String eventValue = ""}) {
if (!isInDebugMode) {
Flurry.logEvent(event.toString());
FlurryData.logEvent(event.toString());
// Smartlook.setGlobalEventProperty(event.toString(), eventValue, false);
FlutterUxcam.logEventWithProperties(event.enumToString(), {"value": eventValue});
model.Tracking tracking = model.Tracking();
+2 -2
View File
@@ -202,8 +202,8 @@ class RegistrationPage extends StatelessWidget with Trans {
Divider(
color: Colors.transparent,
),
getDataProtection(loginBloc),
loginBloc.emailCheckbox ? getEmailSubscription(loginBloc) : Offstage(),
// getDataProtection(loginBloc),
// loginBloc.emailCheckbox ? getEmailSubscription(loginBloc) : Offstage(),
Divider(
color: Colors.transparent,
),
+57 -17
View File
@@ -254,7 +254,7 @@ class TrainingPlanActivatePage extends StatelessWidget with Trans {
ElevatedButton(
style: ElevatedButton.styleFrom(
onPrimary: Colors.white,
primary: Colors.orange,
primary: restricted ? Colors.grey[600] : Colors.orange,
),
child: Text(t("Start")),
onPressed: () {
@@ -294,15 +294,25 @@ class TrainingPlanActivatePage extends StatelessWidget with Trans {
}
},
),
restricted
/* restricted
? Container(
padding: EdgeInsets.only(bottom: 8),
child: Text(
t("This is a premium function"),
style: GoogleFonts.inter(color: Colors.blue[700]),
style: GoogleFonts.inter(
color: Colors.deepOrange[800],
fontWeight: FontWeight.bold,
shadows: <Shadow>[
Shadow(
offset: Offset(2.0, 2.0),
blurRadius: 4.0,
color: Colors.black54,
),
],
),
),
)
: Offstage(),
: Offstage(), */
]),
)));
@@ -352,7 +362,7 @@ class TrainingPlanActivatePage extends StatelessWidget with Trans {
Widget getPlanDetails(TrainingPlan plan, TrainingPlanBloc bloc) {
return SfDataGrid(
headerRowHeight: 30,
rowHeight: 45,
rowHeight: 60,
source: TrainingPlanDetailSource(
plan: plan,
menuBloc: bloc.menuBloc,
@@ -390,14 +400,33 @@ class TrainingPlanActivatePage extends StatelessWidget with Trans {
Navigator.of(context).pop(),
},
);
}),
},
onDropsetTap: () => {
showDialog(
context: context,
builder: (BuildContext context) {
return DialogCommon(
title: t("Dropset"),
descriptions: t("A drop set is an advanced resistance training technique "),
description2:
t(" in which you focus on completing a set until failure - or the inability to do another repetition."),
text: "OK",
onTap: () => {
Navigator.of(context).pop(),
},
onCancel: () => {
Navigator.of(context).pop(),
},
);
})
}),
headerGridLinesVisibility: GridLinesVisibility.both,
gridLinesVisibility: GridLinesVisibility.both,
columns: [
GridTextColumn(
//columnWidthMode: ColumnWidthMode.lastColumnFill,
maximumWidth: 120,
columnWidthMode: ColumnWidthMode.lastColumnFill,
maximumWidth: 160,
columnName: 'exerciseImage',
label: Container(
color: Colors.green[50],
@@ -472,11 +501,13 @@ class TrainingPlanActivatePage extends StatelessWidget with Trans {
class TrainingPlanDetailSource extends DataGridSource {
final TrainingPlan plan;
final MenuBloc menuBloc;
final VoidCallback onDropsetTap;
final VoidCallback onWeightTap;
final VoidCallback onRepeatTap;
TrainingPlanDetailSource({
required this.plan,
required this.menuBloc,
required this.onDropsetTap,
required this.onWeightTap,
required this.onRepeatTap,
}) {
@@ -540,7 +571,7 @@ class TrainingPlanDetailSource extends DataGridSource {
]),
))
])
: dataGridCell.columnName == "weight" && dataGridCell.value == -1
: dataGridCell.columnName == "weight" && (dataGridCell.value == -1 || dataGridCell.value == -2)
? GestureDetector(
onTap: () {
onWeightTap();
@@ -549,21 +580,30 @@ class TrainingPlanDetailSource extends DataGridSource {
CustomIcon.question_circle,
color: Colors.indigo[300],
))
: dataGridCell.columnName == "reps" && dataGridCell.value == -1
: dataGridCell.columnName == "weight" && dataGridCell.value == -3
? GestureDetector(
onTap: () {
onRepeatTap();
onDropsetTap();
},
child: Icon(
CustomIcon.question_circle,
color: Colors.indigo[600],
color: Colors.orange[400],
))
: Text(dataGridCell.value.toString(),
style: GoogleFonts.inter(
fontSize: 14,
color: Colors.indigo,
fontWeight: FontWeight.bold,
)));
: dataGridCell.columnName == "reps" && dataGridCell.value == -1
? GestureDetector(
onTap: () {
onRepeatTap();
},
child: Icon(
CustomIcon.question_circle,
color: Colors.indigo[600],
))
: Text(dataGridCell.value.toString(),
style: GoogleFonts.inter(
fontSize: 14,
color: Colors.indigo,
fontWeight: FontWeight.bold,
)));
}).toList());
}
}
+192 -79
View File
@@ -8,8 +8,10 @@ import 'package:aitrainer_app/util/app_localization.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_html.dart';
import 'package:aitrainer_app/widgets/menu_image.dart';
import 'package:aitrainer_app/widgets/victory_widget.dart';
import 'package:badges/badges.dart';
import 'package:extended_tabs/extended_tabs.dart';
import 'package:ezanimation/ezanimation.dart';
import 'package:flutter/cupertino.dart';
@@ -125,6 +127,7 @@ class _ExerciseTabs extends State<ExerciseTabs> with TickerProviderStateMixin {
@override
void initState() {
super.initState();
print("init TAB ${widget.bloc.dayNames.length} index ${widget.bloc.activeDayIndex}");
tabController = TabController(length: widget.bloc.dayNames.length, vsync: this);
tabController.animateTo(widget.bloc.activeDayIndex, duration: Duration(milliseconds: 300));
}
@@ -143,6 +146,13 @@ class _ExerciseTabs extends State<ExerciseTabs> with TickerProviderStateMixin {
Widget getTabs(TrainingPlanBloc bloc) {
return Column(children: [
ExtendedTabBar(
indicator: BoxDecoration(
color: Colors.black87,
border: Border(
bottom: BorderSide(width: 4.0, color: Colors.blue),
top: BorderSide(width: 4.0, color: Colors.blue),
)),
labelPadding: EdgeInsets.only(left: 5, right: 5),
tabs: getTabNames(),
controller: tabController,
onTap: (index) => bloc.activeDayIndex = index,
@@ -168,52 +178,57 @@ class _ExerciseTabs extends State<ExerciseTabs> with TickerProviderStateMixin {
List<Tab> getTabNames() {
List<Tab> tabs = [];
widget.bloc.dayNames.forEach((element) {
final Widget widget = RichText(
text: TextSpan(
style: GoogleFonts.inter(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.white,
),
children: [
TextSpan(
text: AppLocalizations.of(context)!.translate("Training Day") + ": \n",
style: GoogleFonts.inter(
fontSize: 14,
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,
),
],
)),
TextSpan(
text: element,
style: GoogleFonts.inter(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.yellow[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,
),
],
)),
]));
final Widget widget = Container(
//height: 50,
padding: EdgeInsets.only(top: 2, left: 5, right: 5, bottom: 2),
color: Colors.white24,
child: RichText(
textScaleFactor: 0.8,
text: TextSpan(
style: GoogleFonts.inter(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.white,
),
children: [
TextSpan(
text: AppLocalizations.of(context)!.translate("Training Day") + ": \n",
style: GoogleFonts.inter(
fontSize: 14,
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,
),
],
)),
TextSpan(
text: element,
style: GoogleFonts.inter(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.yellow[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,
),
],
)),
])));
tabs.add(Tab(child: widget));
});
@@ -559,11 +574,17 @@ 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();
final String step = (widget.detail.exercises.length).toString();
String weight = widget.detail.weight != null ? widget.detail.weight!.toStringAsFixed(1) : "-";
bool isDrop = false;
if (widget.detail.weight == -3) {
weight = t("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) {
@@ -574,6 +595,9 @@ class _ExerciseTileState extends State<ExerciseTile> with Trans {
if (widget.detail.repeats! == -1) {
repeats = t("MAX");
}
final bool extraExercise = widget.detail.exerciseType!.name == "Warming Up" || widget.detail.exerciseType!.name == "Stretching";
bool buddyWarning = widget.detail.exerciseType == null ? false : widget.detail.exerciseType!.buddyWarning;
setContext(context);
return Container(
color: Colors.transparent,
@@ -620,10 +644,58 @@ class _ExerciseTileState extends State<ExerciseTile> with Trans {
Container(
width: 120,
height: 80,
child: MenuImage(
imageName: widget.bloc.getActualImageName(widget.detail.exerciseType!.exerciseTypeId),
workoutTreeId: widget.bloc.getActualWorkoutTreeId(widget.detail.exerciseType!.exerciseTypeId)!,
),
child: Badge(
elevation: 0,
padding: EdgeInsets.all(0),
position: BadgePosition.bottomStart(start: -5),
animationDuration: Duration(milliseconds: 500),
animationType: BadgeAnimationType.slide,
badgeColor: Colors.transparent,
showBadge: true,
badgeContent: IconButton(
onPressed: () => showDialog(
context: context,
builder: (BuildContext context) {
return DialogHTML(
title: widget.detail.exerciseType!.nameTranslation,
htmlData: '<p>' + widget.detail.exerciseType!.descriptionTranslation + '</p>');
}),
icon: Icon(
Icons.info_outline,
color: Colors.yellow[200],
)),
child: Badge(
elevation: 0,
padding: EdgeInsets.all(0),
position: BadgePosition.topEnd(end: -8),
animationDuration: Duration(milliseconds: 500),
animationType: BadgeAnimationType.slide,
badgeColor: Colors.transparent,
showBadge: buddyWarning,
badgeContent: IconButton(
onPressed: () => showDialog(
context: context,
builder: (BuildContext context) {
return DialogCommon(
warning: true,
text: "Warning",
descriptions: t("Attention!"),
description2: t("The safe and exact execution of this exercise you need a training buddy or a trainer"),
description3: t("Execution at your own risk!"),
onTap: () => Navigator.of(context).pop(),
onCancel: () => Navigator.of(context).pop(),
title: t('Training Buddy'),
);
}),
icon: Icon(
CustomIcon.exclamation_circle,
color: Colors.red[800],
)),
child: MenuImage(
imageName: widget.bloc.getActualImageName(widget.detail.exerciseType!.exerciseTypeId),
workoutTreeId: widget.bloc.getActualWorkoutTreeId(widget.detail.exerciseType!.exerciseTypeId)!,
radius: 12,
))),
),
SizedBox(
width: 10,
@@ -656,18 +728,18 @@ class _ExerciseTileState extends State<ExerciseTile> with Trans {
),
],
)),
widget.detail.exerciseType!.unitQuantityUnit != null
widget.detail.exerciseType!.unitQuantityUnit != null && !extraExercise
? TextSpan(
text: "\n",
)
: TextSpan(),
widget.detail.exerciseType!.unitQuantityUnit != null
widget.detail.exerciseType!.unitQuantityUnit != null && !extraExercise
? TextSpan(
text: t(widget.detail.exerciseType!.unitQuantityUnit!) + ": ",
style: GoogleFonts.inter(
fontSize: 12, color: done ? Colors.grey[100] : Colors.yellow[400], fontWeight: FontWeight.bold))
: TextSpan(),
widget.detail.exerciseType!.unitQuantityUnit != null
widget.detail.exerciseType!.unitQuantityUnit != null && !extraExercise
? TextSpan(
text: weight,
style: GoogleFonts.inter(
@@ -677,37 +749,50 @@ class _ExerciseTileState extends State<ExerciseTile> with Trans {
TextSpan(
text: "\n",
),
TextSpan(
text: t(widget.detail.exerciseType!.unit) + ": ",
style: GoogleFonts.inter(
fontSize: 12, color: done ? Colors.grey[100] : Colors.yellow[400], fontWeight: FontWeight.bold)),
TextSpan(
text: repeats,
style: GoogleFonts.inter(
fontSize: 12,
)),
!extraExercise
? TextSpan(
text: t(widget.detail.exerciseType!.unit) + ": ",
style: GoogleFonts.inter(
fontSize: 12, color: done ? Colors.grey[100] : Colors.yellow[400], fontWeight: FontWeight.bold))
: TextSpan(),
!extraExercise
? TextSpan(
text: repeats,
style: GoogleFonts.inter(
fontSize: 12,
))
: TextSpan(),
TextSpan(
text: "\n",
),
TextSpan(
text: t("Set") + ": ",
style: GoogleFonts.inter(
fontSize: 12, color: done ? Colors.grey[100] : Colors.yellow[400], fontWeight: FontWeight.bold)),
TextSpan(
text: step + "/" + countSerie,
style: GoogleFonts.inter(
fontSize: 12,
)),
!extraExercise
? TextSpan(
text: t("Set") + ": ",
style: GoogleFonts.inter(
fontSize: 12, color: done ? Colors.grey[100] : Colors.yellow[400], fontWeight: FontWeight.bold))
: TextSpan(),
!extraExercise
? TextSpan(
text: step + "/" + countSerie,
style: GoogleFonts.inter(
fontSize: 12,
))
: TextSpan(),
TextSpan(
text: "\n",
),
TextSpan(
text: t("Resting time") + ": ",
style: GoogleFonts.inter(
fontSize: 12, color: done ? Colors.grey[100] : Colors.yellow[400], fontWeight: FontWeight.bold)),
TextSpan(
text: restingTime + " " + t("min(s)"),
style: GoogleFonts.inter(fontSize: 12, color: done ? Colors.grey[100] : Colors.white, fontWeight: FontWeight.bold)),
!extraExercise
? TextSpan(
text: t("Resting time") + ": ",
style: GoogleFonts.inter(
fontSize: 12, color: done ? Colors.grey[100] : Colors.yellow[400], fontWeight: FontWeight.bold))
: TextSpan(),
!extraExercise
? TextSpan(
text: restingTime + " " + t("min(s)"),
style:
GoogleFonts.inter(fontSize: 12, color: done ? Colors.grey[100] : Colors.white, fontWeight: FontWeight.bold))
: TextSpan(),
]),
)),
isTest
@@ -740,7 +825,35 @@ class _ExerciseTileState extends State<ExerciseTile> with Trans {
)),
]);
})
: Offstage()
: 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()
]),
),
),
+5 -4
View File
@@ -6,7 +6,6 @@ import 'package:aitrainer_app/model/customer_training_plan_details.dart';
import 'package:aitrainer_app/util/trans.dart';
import 'package:aitrainer_app/widgets/app_bar.dart';
import 'package:aitrainer_app/widgets/exercise_save.dart';
import 'package:aitrainer_app/widgets/number_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:google_fonts/google_fonts.dart';
@@ -43,7 +42,7 @@ class TrainingPlanExercise extends StatelessWidget with Trans {
}, builder: (context, state) {
return ModalProgressHUD(
child: getExercises(bloc, detail),
inAsyncCall: state is TrainingPlanLoading,
inAsyncCall: state is TrainingPlanExerciseLoading,
opacity: 0.5,
color: Colors.black54,
progressIndicator: CircularProgressIndicator(),
@@ -52,6 +51,7 @@ class TrainingPlanExercise extends StatelessWidget with Trans {
),
floatingActionButton: Row(mainAxisAlignment: MainAxisAlignment.end, children: [
FloatingActionButton.extended(
heroTag: "skipButton",
onPressed: () => {
Navigator.of(context).pop(),
bloc.add(TrainingPlanSkipExercise(detail: detail)),
@@ -67,6 +67,7 @@ class TrainingPlanExercise extends StatelessWidget with Trans {
width: 20,
),
FloatingActionButton.extended(
heroTag: "saveButton",
onPressed: () => {
Navigator.of(context).pop(),
bloc.add(TrainingPlanSaveExercise(detail: detail)),
@@ -111,7 +112,7 @@ class TrainingPlanExercise extends StatelessWidget with Trans {
);
}
Widget getExerciseForm(TrainingPlanBloc bloc, CustomerTrainingPlanDetails detail) {
/* Widget getExerciseForm(TrainingPlanBloc bloc, CustomerTrainingPlanDetails detail) {
return Container(
padding: const EdgeInsets.only(top: 10, left: 25, right: 25),
child: SingleChildScrollView(
@@ -224,5 +225,5 @@ class TrainingPlanExercise extends StatelessWidget with Trans {
crossAxisAlignment: CrossAxisAlignment.start,
children: listWidgets,
);
}
} */
}
+1
View File
@@ -85,6 +85,7 @@ class MyTrainingPlans extends StatelessWidget with Trans, Logging {
getTrainingPlan(t("Training Plans for Women"), "asset/menu/training_plans_q_woman.jpg", "for_woman"),
getTrainingPlan(t("Training Plans of Celebrities"), "asset/menu/training_plans_q_celebrities.jpg", "celebrities"),
getTrainingPlan(t("Training Plans for Gain Strength"), "asset/menu/training_plans_q_gain_strength.jpg", "gain_strength"),
getTrainingPlan(t("Physical Prepare Program for Footgolfers"), "asset/menu/FG_2_edz.jpg", "footgolf"),
]),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
+1
View File
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:aitrainer_app/library/image_cache.dart' as wt;
import 'package:aitrainer_app/library/transparent_image.dart';
// ignore: must_be_immutable
class MenuImage extends StatelessWidget {
final int? workoutTreeId;
final String imageName;
+6 -4
View File
@@ -25,10 +25,12 @@ class TreeviewParentWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
Widget parentWidget = Text(
this.text,
style: GoogleFonts.archivoBlack(fontSize: fontSize, color: color ?? Colors.blue[800]!, backgroundColor: Colors.transparent),
);
Widget parentWidget = Text(this.text,
style: GoogleFonts.archivoBlack(
fontSize: fontSize,
color: color ?? Colors.blue[800]!,
backgroundColor: Colors.transparent,
));
return Card(
color: backgroundColor,