WT1.1.8+3 custom plan and training plan fixes
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'package:aitrainer_app/model/cache.dart';
|
||||
import 'package:aitrainer_app/model/exercise_plan_detail.dart';
|
||||
import 'package:aitrainer_app/model/model_change.dart';
|
||||
import 'package:aitrainer_app/model/workout_menu_tree.dart';
|
||||
@@ -29,6 +30,7 @@ class ExercisePlanBloc extends Bloc<ExercisePlanEvent, ExercisePlanState> {
|
||||
menuTreeRepository.sortByMuscleType();
|
||||
|
||||
menuTreeRepository.sortedTree.forEach((key, value) {
|
||||
print("menutree $key");
|
||||
List<WorkoutMenuTree> listWorkoutTree = value;
|
||||
listWorkoutTree.forEach((workoutTree) {
|
||||
workoutTree.selected = false;
|
||||
@@ -48,9 +50,13 @@ class ExercisePlanBloc extends Bloc<ExercisePlanEvent, ExercisePlanState> {
|
||||
Stream<ExercisePlanState> mapEventToState(ExercisePlanEvent event) async* {
|
||||
try {
|
||||
if (event is ExercisePlanLoad) {
|
||||
if (Cache().userLoggedIn == null || Cache().userLoggedIn!.customerId == null) {
|
||||
throw Exception("Please log in");
|
||||
}
|
||||
yield ExercisePlanLoading();
|
||||
Track().track(TrackingEvent.my_custom_exercise_plan);
|
||||
customerId = Cache().userLoggedIn!.customerId!;
|
||||
await this.getData();
|
||||
Track().track(TrackingEvent.my_custom_exercise_plan);
|
||||
yield ExercisePlanReady();
|
||||
}
|
||||
|
||||
|
||||
@@ -6,11 +6,15 @@ import 'package:aitrainer_app/model/customer_training_plan.dart';
|
||||
import 'package:aitrainer_app/model/customer_training_plan_details.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/model/workout_menu_tree.dart';
|
||||
import 'package:aitrainer_app/repository/training_plan_repository.dart';
|
||||
import 'package:aitrainer_app/service/exercise_service.dart';
|
||||
import 'package:aitrainer_app/util/app_language.dart';
|
||||
import 'package:aitrainer_app/util/enums.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
part 'training_plan_event.dart';
|
||||
part 'training_plan_state.dart';
|
||||
@@ -21,18 +25,34 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
|
||||
TrainingPlanBloc({required this.trainingPlanRepository, required this.menuBloc}) : super(TrainingPlanInitial());
|
||||
|
||||
CustomerTrainingPlan? _myPlan;
|
||||
CustomerTrainingPlanDetails? _myDetail;
|
||||
|
||||
bool started = false;
|
||||
final List<String> dayNames = [];
|
||||
bool restarting = false;
|
||||
bool celebrating = false;
|
||||
int activeDayIndex = 0;
|
||||
|
||||
CustomerTrainingPlan? getMyPlan() => this._myPlan;
|
||||
setMyPlan(CustomerTrainingPlan? myPlan) => this._myPlan = myPlan;
|
||||
|
||||
CustomerTrainingPlanDetails? getMyDetail() => this._myDetail;
|
||||
setMyDetail(CustomerTrainingPlanDetails? value) => this._myDetail = value;
|
||||
|
||||
@override
|
||||
Stream<TrainingPlanState> mapEventToState(TrainingPlanEvent event) async* {
|
||||
try {
|
||||
if (event is TrainingPlanActivate) {
|
||||
yield TrainingPlanLoading();
|
||||
_myPlan = await trainingPlanRepository.activateTrainingPlan(event.trainingPlanId);
|
||||
_myPlan!.type = CustomerTrainingPlanType.template;
|
||||
|
||||
menuBloc.menuTreeRepository.sortedTree.forEach((name, list) {
|
||||
final List<WorkoutMenuTree> menuList = list as List<WorkoutMenuTree>;
|
||||
menuList.forEach((element) {
|
||||
element.exerciseType!.trainingPlanState = ExerciseTypeTrainingPlanState.none;
|
||||
});
|
||||
});
|
||||
this.activateDays();
|
||||
Cache().myTrainingPlan = _myPlan;
|
||||
await Cache().saveMyTrainingPlan();
|
||||
@@ -47,6 +67,12 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
|
||||
|
||||
event.detail.repeats = event.repeats;
|
||||
|
||||
yield TrainingPlanReady();
|
||||
} else if (event is TrainingPlanSetChange) {
|
||||
yield TrainingPlanLoading();
|
||||
|
||||
event.detail.set = event.set;
|
||||
|
||||
yield TrainingPlanReady();
|
||||
} else if (event is TrainingPlanSaveExercise) {
|
||||
yield TrainingPlanLoading();
|
||||
@@ -74,12 +100,76 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
|
||||
|
||||
Cache().myTrainingPlan = _myPlan;
|
||||
await Cache().saveMyTrainingPlan();
|
||||
yield TrainingPlanReady();
|
||||
|
||||
if (isDayDone()) {
|
||||
this.add(TrainingPlanFinishDay());
|
||||
} else {
|
||||
yield TrainingPlanReady();
|
||||
}
|
||||
} else if (event is TrainingPlanSkipExercise) {
|
||||
yield TrainingPlanLoading();
|
||||
print("Skipping ${event.detail.exerciseTypeId}");
|
||||
event.detail.state = ExercisePlanDetailState.skipped;
|
||||
Cache().myTrainingPlan = _myPlan;
|
||||
await Cache().saveMyTrainingPlan();
|
||||
if (isDayDone()) {
|
||||
this.add(TrainingPlanFinishDay());
|
||||
} else {
|
||||
yield TrainingPlanReady();
|
||||
}
|
||||
} else if (event is TrainingPlanFinishDay) {
|
||||
yield TrainingPlanLoading();
|
||||
celebrating = true;
|
||||
yield TrainingPlanDayFinished();
|
||||
} else if (event is TrainingPlanGoToRestart) {
|
||||
yield TrainingPlanLoading();
|
||||
restarting = true;
|
||||
yield TrainingPlanDayReadyToRestart();
|
||||
} else if (event is TrainingPlanAddExerciseType) {
|
||||
if (_myDetail == null) {
|
||||
throw Exception("Create new Detail");
|
||||
}
|
||||
yield TrainingPlanLoading();
|
||||
_myDetail!.exerciseType!.trainingPlanState = ExerciseTypeTrainingPlanState.added;
|
||||
_myPlan!.details.add(this._myDetail!);
|
||||
yield TrainingPlanReady();
|
||||
} else if (event is TrainingPlanDeleteExerciseType) {
|
||||
if (_myPlan == null || _myPlan!.details.isEmpty) {
|
||||
throw Exception("No MyPlan");
|
||||
}
|
||||
yield TrainingPlanLoading();
|
||||
CustomerTrainingPlanDetails? remove;
|
||||
for (var detail in _myPlan!.details) {
|
||||
if (event.exerciseType.exerciseTypeId == detail.exerciseTypeId) {
|
||||
remove = detail;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (remove != null) {
|
||||
_myPlan!.details.remove(remove);
|
||||
}
|
||||
event.exerciseType.trainingPlanState = ExerciseTypeTrainingPlanState.none;
|
||||
yield TrainingPlanReady();
|
||||
} else if (event is TrainingPlanCustomAddLoad) {
|
||||
yield TrainingPlanLoading();
|
||||
addNewPlan();
|
||||
|
||||
_myDetail = CustomerTrainingPlanDetails();
|
||||
_myDetail!.exerciseType = event.exerciseType;
|
||||
_myDetail!.exerciseTypeId = event.exerciseType.exerciseTypeId;
|
||||
_myDetail!.repeats = 12;
|
||||
_myDetail!.weight = 30;
|
||||
_myDetail!.set = 3;
|
||||
_myDetail!.parallel = false;
|
||||
_myDetail!.day = "";
|
||||
_myDetail!.restingTime = 2;
|
||||
_myDetail!.state = ExercisePlanDetailState.start;
|
||||
|
||||
if (_myDetail!.exerciseType!.unitQuantityUnit != null) {
|
||||
_myDetail = trainingPlanRepository.getCalculatedWeightRepeats(event.exerciseType.exerciseTypeId, _myDetail!);
|
||||
} else {
|
||||
_myDetail!.weight = 0;
|
||||
}
|
||||
yield TrainingPlanReady();
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
@@ -87,10 +177,51 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
|
||||
}
|
||||
}
|
||||
|
||||
void addNewPlan() {
|
||||
if (_myPlan == null) {
|
||||
_myPlan = CustomerTrainingPlan();
|
||||
} else {
|
||||
if (!_myPlan!.type.equalsTo(CustomerTrainingPlanType.custom)) {
|
||||
_myPlan!.details.clear();
|
||||
}
|
||||
}
|
||||
_myPlan!.trainingPlanId = 0;
|
||||
_myPlan!.name = "Custom";
|
||||
_myPlan!.customerId = Cache().userLoggedIn == null ? Cache().userLoggedIn!.customerId : 0;
|
||||
|
||||
_myPlan!.type = CustomerTrainingPlanType.custom;
|
||||
|
||||
restart();
|
||||
|
||||
print("New custom plan: $_myPlan");
|
||||
}
|
||||
|
||||
void restart() {
|
||||
if (_myPlan == null) {
|
||||
return;
|
||||
}
|
||||
for (var day in dayNames) {
|
||||
_myPlan!.days[day]!.clear();
|
||||
}
|
||||
_myPlan!.details.forEach((element) {
|
||||
element.state = ExercisePlanDetailState.start;
|
||||
element.exercises.clear();
|
||||
});
|
||||
dayNames.clear();
|
||||
Cache().myTrainingPlan = _myPlan;
|
||||
Cache().saveMyTrainingPlan();
|
||||
restarting = false;
|
||||
print("Restarting finished");
|
||||
}
|
||||
|
||||
void activateDays() {
|
||||
if (_myPlan == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isDone100Percent()) {
|
||||
this.add(TrainingPlanGoToRestart());
|
||||
}
|
||||
dayNames.clear();
|
||||
_myPlan!.days.clear();
|
||||
String dayName = ".";
|
||||
@@ -113,6 +244,7 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
|
||||
_myPlan!.days[""] = [];
|
||||
_myPlan!.days[""]!.addAll(_myPlan!.details);
|
||||
}
|
||||
getActiveDayIndex();
|
||||
}
|
||||
|
||||
CustomerTrainingPlanDetails? getTrainingPlanDetail(int trainingPlanDetailsId) {
|
||||
@@ -177,6 +309,7 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
|
||||
}
|
||||
}
|
||||
print("Next detail $next");
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
@@ -192,6 +325,18 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
|
||||
}
|
||||
}
|
||||
|
||||
bool isDayDone() {
|
||||
bool isDone = true;
|
||||
final String day = dayNames[activeDayIndex];
|
||||
for (var detail in _myPlan!.days[day]!) {
|
||||
if (!detail.state.equalsTo(ExercisePlanDetailState.finished) && !detail.state.equalsTo(ExercisePlanDetailState.skipped)) {
|
||||
isDone = false;
|
||||
}
|
||||
}
|
||||
print("Is Day '$day' done: $isDone");
|
||||
return isDone;
|
||||
}
|
||||
|
||||
double getOffset() {
|
||||
double offset = 5;
|
||||
if (_myPlan == null) {
|
||||
@@ -199,7 +344,8 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
|
||||
}
|
||||
int indexInProgress = 0;
|
||||
int indexInStart = 0;
|
||||
for (var detail in _myPlan!.details) {
|
||||
final String day = dayNames[this.activeDayIndex];
|
||||
for (var detail in _myPlan!.days[day]!) {
|
||||
if (detail.state == ExercisePlanDetailState.inProgress) {
|
||||
break;
|
||||
}
|
||||
@@ -213,4 +359,126 @@ class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> {
|
||||
offset = index * 80;
|
||||
return offset;
|
||||
}
|
||||
|
||||
bool isDone100Percent() {
|
||||
bool done = true;
|
||||
if (_myPlan == null || _myPlan!.details.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
_myPlan!.details.forEach((element) {
|
||||
if (!element.state.equalsTo(ExercisePlanDetailState.finished) && !element.state.equalsTo(ExercisePlanDetailState.skipped)) {
|
||||
done = false;
|
||||
}
|
||||
});
|
||||
return done;
|
||||
}
|
||||
|
||||
int getActiveDayIndex() {
|
||||
if (restarting) {
|
||||
return 0;
|
||||
}
|
||||
if (_myPlan == null || _myPlan!.details.isEmpty) {
|
||||
throw Exception("No defined Training Plan");
|
||||
}
|
||||
|
||||
if (dayNames.isEmpty || dayNames.length == 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
activeDayIndex = 0;
|
||||
|
||||
for (var day in dayNames) {
|
||||
if (_myPlan!.days[day] == null) {
|
||||
throw Exception("Wrong activated day: $day does not exist");
|
||||
}
|
||||
bool isDone = true;
|
||||
_myPlan!.days[day]!.forEach((element) {
|
||||
if (!element.state.equalsTo(ExercisePlanDetailState.finished) && !element.state.equalsTo(ExercisePlanDetailState.skipped)) {
|
||||
isDone = false;
|
||||
}
|
||||
});
|
||||
if (!isDone) {
|
||||
break;
|
||||
}
|
||||
activeDayIndex++;
|
||||
}
|
||||
print("Active Day Index: $activeDayIndex");
|
||||
if (activeDayIndex >= dayNames.length) {
|
||||
activeDayIndex = 0;
|
||||
this.add(TrainingPlanGoToRestart());
|
||||
}
|
||||
|
||||
return activeDayIndex;
|
||||
}
|
||||
|
||||
String getCustomAddSummary() {
|
||||
String summary = "";
|
||||
if (_myDetail == null || _myDetail!.set == null || _myDetail!.repeats == null) {
|
||||
return summary;
|
||||
}
|
||||
summary = getMyDetail()!.set!.toStringAsFixed(0) + " x " + getMyDetail()!.repeats!.toStringAsFixed(0);
|
||||
return summary;
|
||||
}
|
||||
|
||||
String getExerciseName(Locale locale) {
|
||||
String exerciseName = "";
|
||||
if (_myDetail == null || _myDetail!.exerciseType == null) {
|
||||
return exerciseName;
|
||||
}
|
||||
exerciseName =
|
||||
AppLanguage().appLocal == Locale("en") ? getMyDetail()!.exerciseType!.name : getMyDetail()!.exerciseType!.nameTranslation;
|
||||
return exerciseName;
|
||||
}
|
||||
|
||||
String getWeightByExerciseType(ExerciseType exerciseType) {
|
||||
double weight = 0;
|
||||
|
||||
if (_myPlan == null || _myPlan!.details.isEmpty) {
|
||||
return weight.toStringAsFixed(0);
|
||||
}
|
||||
|
||||
for (var detail in _myPlan!.details) {
|
||||
if (exerciseType.exerciseTypeId == detail.exerciseTypeId) {
|
||||
weight = detail.weight!;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int decimal = weight % weight.round() == 0 ? 0 : 1;
|
||||
return weight.toStringAsFixed(decimal);
|
||||
}
|
||||
|
||||
String getSetByExerciseType(ExerciseType exerciseType) {
|
||||
int value = 0;
|
||||
|
||||
if (_myPlan == null || _myPlan!.details.isEmpty) {
|
||||
return value.toStringAsFixed(0);
|
||||
}
|
||||
|
||||
for (var detail in _myPlan!.details) {
|
||||
if (exerciseType.exerciseTypeId == detail.exerciseTypeId) {
|
||||
value = detail.set!;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return value.toStringAsFixed(0);
|
||||
}
|
||||
|
||||
String getRepeatsByExerciseType(ExerciseType exerciseType) {
|
||||
int value = 0;
|
||||
|
||||
if (_myPlan == null || _myPlan!.details.isEmpty) {
|
||||
return value.toStringAsFixed(0);
|
||||
}
|
||||
|
||||
for (var detail in _myPlan!.details) {
|
||||
if (exerciseType.exerciseTypeId == detail.exerciseTypeId) {
|
||||
value = detail.repeats!;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return value.toStringAsFixed(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,15 @@ class TrainingPlanRepeatsChange extends TrainingPlanEvent {
|
||||
List<Object> get props => [repeats, detail];
|
||||
}
|
||||
|
||||
class TrainingPlanSetChange extends TrainingPlanEvent {
|
||||
final CustomerTrainingPlanDetails detail;
|
||||
final int set;
|
||||
const TrainingPlanSetChange({required this.set, required this.detail});
|
||||
|
||||
@override
|
||||
List<Object> get props => [set, detail];
|
||||
}
|
||||
|
||||
class TrainingPlanSaveExercise extends TrainingPlanEvent {
|
||||
final CustomerTrainingPlanDetails detail;
|
||||
const TrainingPlanSaveExercise({required this.detail});
|
||||
@@ -45,8 +54,16 @@ class TrainingPlanSaveExercise extends TrainingPlanEvent {
|
||||
List<Object> get props => [detail];
|
||||
}
|
||||
|
||||
class TrainingPlanFinishTraining extends TrainingPlanEvent {
|
||||
const TrainingPlanFinishTraining();
|
||||
class TrainingPlanFinishDay extends TrainingPlanEvent {
|
||||
const TrainingPlanFinishDay();
|
||||
}
|
||||
|
||||
class TrainingPlanRestart extends TrainingPlanEvent {
|
||||
const TrainingPlanRestart();
|
||||
}
|
||||
|
||||
class TrainingPlanGoToRestart extends TrainingPlanEvent {
|
||||
const TrainingPlanGoToRestart();
|
||||
}
|
||||
|
||||
class TrainingPlanSkipExercise extends TrainingPlanEvent {
|
||||
@@ -56,3 +73,23 @@ class TrainingPlanSkipExercise extends TrainingPlanEvent {
|
||||
@override
|
||||
List<Object> get props => [detail];
|
||||
}
|
||||
|
||||
class TrainingPlanAddExerciseType extends TrainingPlanEvent {
|
||||
const TrainingPlanAddExerciseType();
|
||||
}
|
||||
|
||||
class TrainingPlanDeleteExerciseType extends TrainingPlanEvent {
|
||||
final ExerciseType exerciseType;
|
||||
const TrainingPlanDeleteExerciseType({required this.exerciseType});
|
||||
|
||||
@override
|
||||
List<Object> get props => [exerciseType];
|
||||
}
|
||||
|
||||
class TrainingPlanCustomAddLoad extends TrainingPlanEvent {
|
||||
final ExerciseType exerciseType;
|
||||
const TrainingPlanCustomAddLoad({required this.exerciseType});
|
||||
|
||||
@override
|
||||
List<Object> get props => [exerciseType];
|
||||
}
|
||||
|
||||
@@ -23,6 +23,14 @@ class TrainingPlanFinished extends TrainingPlanState {
|
||||
const TrainingPlanFinished();
|
||||
}
|
||||
|
||||
class TrainingPlanDayFinished extends TrainingPlanState {
|
||||
const TrainingPlanDayFinished();
|
||||
}
|
||||
|
||||
class TrainingPlanDayReadyToRestart extends TrainingPlanState {
|
||||
const TrainingPlanDayReadyToRestart();
|
||||
}
|
||||
|
||||
class TrainingPlanError extends TrainingPlanState {
|
||||
final String message;
|
||||
const TrainingPlanError({required this.message});
|
||||
|
||||
+5
-5
@@ -26,7 +26,9 @@ import 'package:aitrainer_app/view/exercise_plan_custom_detail_add_page.dart';
|
||||
import 'package:aitrainer_app/view/faq_page.dart';
|
||||
import 'package:aitrainer_app/view/login.dart';
|
||||
import 'package:aitrainer_app/view/exercise_new_page.dart';
|
||||
import 'package:aitrainer_app/view/my_training_plans_page.dart';
|
||||
import 'package:aitrainer_app/view/training_plan_custom.dart';
|
||||
import 'package:aitrainer_app/view/training_plan_custom_add.dart';
|
||||
import 'package:aitrainer_app/view/training_plans_page.dart';
|
||||
import 'package:aitrainer_app/view/mydevelopment_body_page.dart';
|
||||
import 'package:aitrainer_app/view/mydevelopment_muscle_page.dart';
|
||||
import 'package:aitrainer_app/view/mydevelopment_page.dart';
|
||||
@@ -60,7 +62,6 @@ import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
import 'bloc/account/account_bloc.dart';
|
||||
import 'bloc/body_development/body_development_bloc.dart';
|
||||
import 'bloc/development_by_muscle/development_by_muscle_bloc.dart';
|
||||
import 'bloc/exercise_execute_plan/exercise_execute_plan_bloc.dart';
|
||||
import 'bloc/exercise_plan/exercise_plan_bloc.dart';
|
||||
import 'bloc/menu/menu_bloc.dart';
|
||||
import 'bloc/session/session_bloc.dart';
|
||||
@@ -168,9 +169,6 @@ Future<Null> main() async {
|
||||
BlocProvider<ExercisePlanBloc>(
|
||||
create: (BuildContext context) => ExercisePlanBloc(menuTreeRepository: menuTreeRepository),
|
||||
),
|
||||
BlocProvider<ExerciseExecutePlanBloc>(
|
||||
create: (BuildContext context) => ExerciseExecutePlanBloc(menuTreeRepository: menuTreeRepository),
|
||||
),
|
||||
BlocProvider<DevelopmentByMuscleBloc>(
|
||||
create: (BuildContext context) => DevelopmentByMuscleBloc(workoutTreeRepository: menuTreeRepository),
|
||||
),
|
||||
@@ -273,6 +271,8 @@ class WorkoutTestApp extends StatelessWidget {
|
||||
'testSetControl': (context) => TestSetControl(),
|
||||
'faqPage': (context) => FaqPage(),
|
||||
'myTrainingPlans': (context) => MyTrainingPlans(),
|
||||
'myTrainingPlanCustom': (context) => TrainingPlanCustomPage(),
|
||||
'myTrainingPlanCustomAdd': (context) => TrainingPlanCustomAddPage(),
|
||||
'myTrainingPlanActivate': (context) => TrainingPlanActivatePage(),
|
||||
'myTrainingPlanExecute': (context) => TrainingPlanExecutePage(),
|
||||
'myTrainingPlanExercise': (context) => TrainingPlanExercise(),
|
||||
|
||||
@@ -4,6 +4,14 @@ import 'package:intl/intl.dart';
|
||||
|
||||
import 'package:aitrainer_app/model/customer_training_plan_details.dart';
|
||||
|
||||
enum CustomerTrainingPlanType { custom, template, none }
|
||||
|
||||
extension CustomerTrainingPlanTypeExt on CustomerTrainingPlanType {
|
||||
String toStr() => this.toString().split(".").last;
|
||||
bool equalsTo(CustomerTrainingPlanType type) => this.toString() == type.toString();
|
||||
bool equalsStringTo(String type) => this.toStr() == type;
|
||||
}
|
||||
|
||||
class CustomerTrainingPlan {
|
||||
int? customerTrainingPlanId;
|
||||
int? customerId;
|
||||
@@ -14,6 +22,8 @@ class CustomerTrainingPlan {
|
||||
|
||||
String? name;
|
||||
|
||||
CustomerTrainingPlanType type = CustomerTrainingPlanType.none;
|
||||
|
||||
CustomerTrainingPlan();
|
||||
|
||||
List<CustomerTrainingPlanDetails> details = [];
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:aitrainer_app/model/exercise_ability.dart';
|
||||
import 'package:aitrainer_app/util/app_language.dart';
|
||||
import 'package:aitrainer_app/util/enums.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ExerciseType {
|
||||
@@ -48,6 +49,10 @@ class ExerciseType {
|
||||
/// ability
|
||||
ExerciseAbility? ability;
|
||||
|
||||
/// TrainingPlanState - whether the exercise_type exists in the
|
||||
/// custom training plan
|
||||
ExerciseTypeTrainingPlanState trainingPlanState = ExerciseTypeTrainingPlanState.none;
|
||||
|
||||
ExerciseType({required this.name, required this.description});
|
||||
|
||||
ExerciseType.fromJson(Map json) {
|
||||
|
||||
@@ -299,10 +299,7 @@ class WorkoutTreeRepository with Logging {
|
||||
sortedTree = SplayTreeMap<String, List<WorkoutMenuTree>>();
|
||||
tree.forEach((key, value) {
|
||||
WorkoutMenuTree workoutTree = value;
|
||||
if (!workoutTree.nameEnglish.contains('Muscle Build') &&
|
||||
!workoutTree.nameEnglish.contains('Strength') &&
|
||||
workoutTree.is1RM &&
|
||||
workoutTree.exerciseTypeId == 0) {
|
||||
if (!workoutTree.internalName.contains('one_rep_max') && workoutTree.is1RM && workoutTree.exerciseTypeId == 0) {
|
||||
String treeName = getAntagonistSort(workoutTree.nameEnglish) + ". " + workoutTree.name;
|
||||
//print("TreeName $treeName ${workoutTree.name}");
|
||||
sortedTree[treeName] = this.getBranchList(workoutTree.id);
|
||||
|
||||
@@ -88,3 +88,11 @@ extension EvaluationTextExt on EvaluationText {
|
||||
bool equalsTo(EvaluationText eval) => this.toString() == eval.toString();
|
||||
bool equalsStringTo(String eval) => this.toStr() == eval;
|
||||
}
|
||||
|
||||
enum ExerciseTypeTrainingPlanState { none, added, executed }
|
||||
|
||||
extension ExerciseTypeTrainingPlanStateExt on ExerciseTypeTrainingPlanState {
|
||||
String toStr() => this.toString().split(".").last;
|
||||
bool equalsTo(ExerciseTypeTrainingPlanState state) => this.toString() == state.toString();
|
||||
bool equalsStringTo(String state) => this.toStr() == state;
|
||||
}
|
||||
|
||||
@@ -241,11 +241,11 @@ class TrainingPlanActivatePage extends StatelessWidget with Trans {
|
||||
useRootNavigator: true,
|
||||
context: context,
|
||||
builder: (_) => CupertinoAlertDialog(
|
||||
title: Text(t("You have an active Training Plan!")),
|
||||
title: Text(t("You have an active Training Plan")),
|
||||
content: Column(children: [
|
||||
Divider(),
|
||||
Text(
|
||||
t("Do you want to override it with "),
|
||||
t("Do you want to override it with"),
|
||||
style: (TextStyle(color: Colors.blue)),
|
||||
),
|
||||
Text(
|
||||
@@ -318,7 +318,7 @@ class TrainingPlanActivatePage extends StatelessWidget with Trans {
|
||||
padding: EdgeInsets.only(left: 8.0),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
'Exercise',
|
||||
t('Exercise'),
|
||||
textAlign: TextAlign.start,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
))),
|
||||
@@ -330,7 +330,7 @@ class TrainingPlanActivatePage extends StatelessWidget with Trans {
|
||||
padding: EdgeInsets.symmetric(horizontal: 8.0),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
'Exercise',
|
||||
t('Exercise'),
|
||||
textAlign: TextAlign.start,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
))),
|
||||
@@ -342,7 +342,7 @@ class TrainingPlanActivatePage extends StatelessWidget with Trans {
|
||||
padding: EdgeInsets.symmetric(horizontal: 2.0),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
'Set',
|
||||
t('Set'),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
))),
|
||||
GridTextColumn(
|
||||
@@ -353,7 +353,7 @@ class TrainingPlanActivatePage extends StatelessWidget with Trans {
|
||||
padding: EdgeInsets.symmetric(horizontal: 2.0),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
'Reps',
|
||||
t('Reps'),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
))),
|
||||
GridTextColumn(
|
||||
@@ -364,7 +364,7 @@ class TrainingPlanActivatePage extends StatelessWidget with Trans {
|
||||
padding: EdgeInsets.symmetric(horizontal: 2.0),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
'Weight',
|
||||
t('Weight'),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
))),
|
||||
GridTextColumn(
|
||||
@@ -375,7 +375,7 @@ class TrainingPlanActivatePage extends StatelessWidget with Trans {
|
||||
padding: EdgeInsets.symmetric(horizontal: 8.0),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
'Day',
|
||||
t('Day'),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
))),
|
||||
],
|
||||
|
||||
@@ -0,0 +1,372 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:aitrainer_app/bloc/training_plan/training_plan_bloc.dart';
|
||||
import 'package:aitrainer_app/library/custom_icon_icons.dart';
|
||||
import 'package:aitrainer_app/model/customer_training_plan.dart';
|
||||
import 'package:aitrainer_app/model/exercise_type.dart';
|
||||
import 'package:aitrainer_app/model/workout_menu_tree.dart';
|
||||
import 'package:aitrainer_app/library/tree_view.dart';
|
||||
import 'package:aitrainer_app/util/enums.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/menu_image.dart';
|
||||
import 'package:aitrainer_app/widgets/treeview_parent_widget.dart';
|
||||
import 'package:ezanimation/ezanimation.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:modal_progress_hud_nsn/modal_progress_hud_nsn.dart';
|
||||
import 'package:timeline_tile/timeline_tile.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class TrainingPlanCustomPage extends StatefulWidget {
|
||||
@override
|
||||
_ExercisePlanCustomPage createState() => _ExercisePlanCustomPage();
|
||||
}
|
||||
|
||||
class _ExercisePlanCustomPage extends State<TrainingPlanCustomPage> with Trans {
|
||||
TrainingPlanBloc? bloc;
|
||||
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
setContext(context);
|
||||
bloc = BlocProvider.of<TrainingPlanBloc>(context);
|
||||
bloc!.menuBloc.menuTreeRepository.sortByMuscleType();
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
appBar: AppBarNav(depth: 1),
|
||||
body: Container(
|
||||
padding: EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage('asset/image/WT_black_background.jpg'),
|
||||
fit: BoxFit.cover,
|
||||
alignment: Alignment.center,
|
||||
),
|
||||
),
|
||||
child: BlocConsumer<TrainingPlanBloc, TrainingPlanState>(listener: (context, state) {
|
||||
if (state is TrainingPlanError) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text(
|
||||
state.message,
|
||||
),
|
||||
backgroundColor: Colors.orange,
|
||||
));
|
||||
}
|
||||
}, builder: (context, state) {
|
||||
return ModalProgressHUD(
|
||||
child: exerciseWidget(bloc!),
|
||||
inAsyncCall: state is TrainingPlanLoading,
|
||||
opacity: 0.5,
|
||||
color: Colors.black54,
|
||||
progressIndicator: CircularProgressIndicator(),
|
||||
);
|
||||
})),
|
||||
bottomNavigationBar: BottomNavigator(bottomNavIndex: 2),
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
onPressed: () => Navigator.of(context).popAndPushNamed('myTrainingPlanExecute'),
|
||||
backgroundColor: Colors.orange[800],
|
||||
icon: Icon(CustomIcon.weight_hanging),
|
||||
label: Text(
|
||||
t("Start") + "!",
|
||||
style: GoogleFonts.inter(fontWeight: FontWeight.bold, fontSize: 16),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget exerciseWidget(TrainingPlanBloc bloc) {
|
||||
return TreeView(
|
||||
startExpanded: false,
|
||||
children: _getTreeChildren(bloc),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _getTreeChildren(TrainingPlanBloc bloc) {
|
||||
List<Widget> exerciseTypes = [];
|
||||
|
||||
Card explanation = Card(
|
||||
color: Colors.white60,
|
||||
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(" "),
|
||||
Text(
|
||||
t("Custom Exercise Plan"),
|
||||
style: GoogleFonts.archivoBlack(fontSize: 20),
|
||||
),
|
||||
],
|
||||
),
|
||||
Divider(
|
||||
color: Colors.transparent,
|
||||
),
|
||||
Text(
|
||||
t("Select manually the exercises what you would like to have in your plan. At the end don't forget to save."),
|
||||
style: GoogleFonts.inter(fontSize: 12, fontWeight: FontWeight.normal),
|
||||
),
|
||||
],
|
||||
)));
|
||||
exerciseTypes.add(explanation);
|
||||
|
||||
bloc.menuBloc.menuTreeRepository.sortedTree.forEach((name, list) {
|
||||
exerciseTypes.add(Container(
|
||||
margin: const EdgeInsets.only(left: 4.0),
|
||||
child: TreeViewChild(
|
||||
startExpanded: false,
|
||||
parent: TreeviewParentWidget(text: name),
|
||||
children: getTiles(list, bloc),
|
||||
)));
|
||||
});
|
||||
|
||||
return exerciseTypes;
|
||||
}
|
||||
|
||||
List<Widget> getTiles(List<WorkoutMenuTree> list, TrainingPlanBloc bloc) {
|
||||
List<Widget> tiles = [];
|
||||
tiles.addAll(getExerciseTiles(bloc, list));
|
||||
return tiles;
|
||||
}
|
||||
|
||||
List<Widget> getExerciseTiles(TrainingPlanBloc bloc, List<WorkoutMenuTree> listWorkoutTree) {
|
||||
List<Widget> tiles = [];
|
||||
|
||||
listWorkoutTree.forEach((element) {
|
||||
tiles.add(GestureDetector(
|
||||
onTap: () => {},
|
||||
child: ExerciseTile(
|
||||
bloc: bloc,
|
||||
exerciseType: element.exerciseType!,
|
||||
)));
|
||||
});
|
||||
|
||||
return tiles;
|
||||
}
|
||||
}
|
||||
|
||||
class ExerciseTile extends StatefulWidget {
|
||||
final TrainingPlanBloc bloc;
|
||||
final ExerciseType exerciseType;
|
||||
|
||||
ExerciseTile({required this.bloc, required this.exerciseType});
|
||||
|
||||
@override
|
||||
_ExerciseTileState createState() => _ExerciseTileState();
|
||||
}
|
||||
|
||||
class _ExerciseTileState extends State<ExerciseTile> with Trans {
|
||||
final EzAnimation animation = EzAnimation(1.0, 30.0, Duration(seconds: 3), reverseCurve: Curves.easeIn);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
animation.start();
|
||||
animation.addStatusListener((status) {
|
||||
if (status == AnimationStatus.completed) {}
|
||||
});
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
bool didUpdateWidget(ExerciseTile oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
Future.delayed(Duration(milliseconds: 400)).then((value) => animation.start());
|
||||
return true;
|
||||
}
|
||||
|
||||
void activateCustomPlan() {
|
||||
widget.bloc.add(TrainingPlanCustomAddLoad(exerciseType: widget.exerciseType));
|
||||
Navigator.of(context).popAndPushNamed("myTrainingPlanCustomAdd");
|
||||
}
|
||||
|
||||
Widget getIndicator() {
|
||||
if (widget.exerciseType.trainingPlanState.equalsTo(ExerciseTypeTrainingPlanState.none)) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
if (widget.bloc.getMyPlan() != null && !widget.bloc.getMyPlan()!.type.equalsTo(CustomerTrainingPlanType.custom)) {
|
||||
showCupertinoDialog(
|
||||
useRootNavigator: true,
|
||||
context: context,
|
||||
builder: (_) => CupertinoAlertDialog(
|
||||
title: Text(t("You have an active Training Plan")),
|
||||
content: Column(children: [
|
||||
Divider(),
|
||||
Text(
|
||||
t("Do you want to override it?"),
|
||||
style: (TextStyle(color: Colors.blue)),
|
||||
),
|
||||
]),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(t("No")),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
TextButton(
|
||||
child: Text(t("Yes")),
|
||||
onPressed: () => activateCustomPlan(),
|
||||
)
|
||||
],
|
||||
));
|
||||
} else {
|
||||
activateCustomPlan();
|
||||
}
|
||||
},
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(24.0),
|
||||
child: Container(
|
||||
color: Colors.blue,
|
||||
child: Icon(
|
||||
CustomIcon.plus_1,
|
||||
size: 28,
|
||||
color: Colors.white,
|
||||
))));
|
||||
} else if (widget.exerciseType.trainingPlanState.equalsTo(ExerciseTypeTrainingPlanState.added)) {
|
||||
return GestureDetector(
|
||||
onTap: () => widget.bloc.add(TrainingPlanDeleteExerciseType(exerciseType: widget.exerciseType)),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(24.0),
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(left: 8, bottom: 3),
|
||||
color: Colors.red[400],
|
||||
child: Text("X",
|
||||
style: GoogleFonts.archivoBlack(
|
||||
fontSize: 30,
|
||||
color: Colors.white,
|
||||
)))));
|
||||
} else {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(24.0),
|
||||
child: Container(
|
||||
color: Colors.blue,
|
||||
child: Icon(
|
||||
CustomIcon.down,
|
||||
size: 28,
|
||||
color: Colors.white,
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool added = widget.exerciseType.trainingPlanState.equalsTo(ExerciseTypeTrainingPlanState.added);
|
||||
setContext(context);
|
||||
return Container(
|
||||
color: Colors.transparent,
|
||||
child: TimelineTile(
|
||||
alignment: TimelineAlign.manual,
|
||||
lineXY: 0.1,
|
||||
beforeLineStyle: const LineStyle(
|
||||
color: Color(0xffb4f500),
|
||||
thickness: 6,
|
||||
),
|
||||
afterLineStyle: const LineStyle(
|
||||
color: Color(0xffb4f500),
|
||||
thickness: 6,
|
||||
),
|
||||
indicatorStyle: IndicatorStyle(
|
||||
width: 40,
|
||||
height: 40,
|
||||
indicator: getIndicator(),
|
||||
),
|
||||
endChild: Container(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Row(children: [
|
||||
Container(
|
||||
width: 120,
|
||||
height: 80,
|
||||
child: MenuImage(
|
||||
imageName: widget.bloc.getActualImageName(widget.exerciseType.exerciseTypeId),
|
||||
workoutTreeId: widget.bloc.getActualWorkoutTreeId(widget.exerciseType.exerciseTypeId)!,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Expanded(
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: added ? Colors.white : Colors.grey,
|
||||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: widget.exerciseType.nameTranslation,
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: added ? Colors.orange[500] : Colors.white,
|
||||
shadows: <Shadow>[
|
||||
Shadow(
|
||||
offset: Offset(5.0, 5.0),
|
||||
blurRadius: 12.0,
|
||||
color: Colors.black54,
|
||||
),
|
||||
Shadow(
|
||||
offset: Offset(-3.0, 3.0),
|
||||
blurRadius: 12.0,
|
||||
color: Colors.black54,
|
||||
),
|
||||
],
|
||||
)),
|
||||
widget.exerciseType.unitQuantityUnit != null
|
||||
? TextSpan(
|
||||
text: "\n",
|
||||
)
|
||||
: TextSpan(),
|
||||
widget.exerciseType.unitQuantityUnit != null
|
||||
? TextSpan(
|
||||
text: t(widget.exerciseType.unitQuantityUnit!) + ": ",
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 12, color: added ? Colors.yellow[400] : Colors.grey, fontWeight: FontWeight.bold))
|
||||
: TextSpan(),
|
||||
widget.exerciseType.unitQuantityUnit != null
|
||||
? TextSpan(
|
||||
text: added ? widget.bloc.getWeightByExerciseType(widget.exerciseType) : "?",
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 12,
|
||||
))
|
||||
: TextSpan(),
|
||||
TextSpan(
|
||||
text: "\n",
|
||||
),
|
||||
TextSpan(
|
||||
text: t(widget.exerciseType.unit) + ": ",
|
||||
style:
|
||||
GoogleFonts.inter(fontSize: 12, color: added ? Colors.yellow[400] : Colors.grey, fontWeight: FontWeight.bold)),
|
||||
TextSpan(
|
||||
text: added ? widget.bloc.getRepeatsByExerciseType(widget.exerciseType) : "?",
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 12,
|
||||
)),
|
||||
TextSpan(
|
||||
text: "\n",
|
||||
),
|
||||
TextSpan(
|
||||
text: t("Set") + ": ",
|
||||
style:
|
||||
GoogleFonts.inter(fontSize: 12, color: added ? Colors.yellow[400] : Colors.grey, fontWeight: FontWeight.bold)),
|
||||
TextSpan(
|
||||
text: added ? widget.bloc.getSetByExerciseType(widget.exerciseType) : "?",
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 12,
|
||||
)),
|
||||
]),
|
||||
)),
|
||||
]),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
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';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:keyboard_actions/keyboard_actions.dart';
|
||||
import 'package:keyboard_actions/keyboard_actions_config.dart';
|
||||
import 'package:keyboard_actions/keyboard_actions_item.dart';
|
||||
import 'package:modal_progress_hud_nsn/modal_progress_hud_nsn.dart';
|
||||
|
||||
class TrainingPlanCustomAddPage extends StatefulWidget {
|
||||
@override
|
||||
_ExercisePlanDetailAddPage createState() => _ExercisePlanDetailAddPage();
|
||||
}
|
||||
|
||||
class _ExercisePlanDetailAddPage extends State<TrainingPlanCustomAddPage> with Trans {
|
||||
final FocusNode _nodeText1 = FocusNode();
|
||||
final FocusNode _nodeText2 = FocusNode();
|
||||
final FocusNode _nodeText3 = FocusNode();
|
||||
|
||||
KeyboardActionsConfig _buildConfig(BuildContext context) {
|
||||
return KeyboardActionsConfig(
|
||||
keyboardActionsPlatform: KeyboardActionsPlatform.ALL,
|
||||
keyboardBarColor: Colors.grey[200],
|
||||
nextFocus: true,
|
||||
actions: [
|
||||
KeyboardActionsItem(focusNode: _nodeText2, toolbarButtons: [
|
||||
(node) {
|
||||
return GestureDetector(
|
||||
onTap: () => node.unfocus(),
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
color: Colors.orange[500],
|
||||
child: Text(
|
||||
t("Done"),
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
]),
|
||||
KeyboardActionsItem(
|
||||
focusNode: _nodeText1,
|
||||
toolbarButtons: [
|
||||
//button 2
|
||||
(node) {
|
||||
return GestureDetector(
|
||||
onTap: () => node.unfocus(),
|
||||
child: Container(
|
||||
color: Colors.orange,
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
t("Done"),
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
],
|
||||
),
|
||||
KeyboardActionsItem(
|
||||
focusNode: _nodeText3,
|
||||
toolbarButtons: [
|
||||
//button 2
|
||||
(node) {
|
||||
return GestureDetector(
|
||||
onTap: () => node.unfocus(),
|
||||
child: Container(
|
||||
color: Colors.orange,
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
t("Done"),
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
setContext(context);
|
||||
|
||||
return BlocConsumer<TrainingPlanBloc, TrainingPlanState>(
|
||||
listener: (context, state) {
|
||||
if (state is TrainingPlanError) {
|
||||
ScaffoldMessenger.of(context)
|
||||
.showSnackBar(SnackBar(backgroundColor: Colors.orange, content: Text(state.message, style: TextStyle(color: Colors.white))));
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
// ignore: close_sinks
|
||||
final bloc = BlocProvider.of<TrainingPlanBloc>(context);
|
||||
return ModalProgressHUD(
|
||||
child: getForm(bloc),
|
||||
inAsyncCall: state is TrainingPlanLoading,
|
||||
opacity: 0.5,
|
||||
color: Colors.black54,
|
||||
progressIndicator: CircularProgressIndicator(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget getForm(TrainingPlanBloc bloc) {
|
||||
String exerciseName = "";
|
||||
|
||||
exerciseName = bloc.getExerciseName(AppLanguage().appLocal);
|
||||
|
||||
final bool weightVisible = bloc.getMyDetail()!.exerciseType!.unitQuantityUnit != null;
|
||||
String summary = bloc.getCustomAddSummary();
|
||||
if (weightVisible && bloc.getMyDetail()!.weight != null && bloc.getMyDetail()!.weight! > 0) {
|
||||
summary += " x " + bloc.getMyDetail()!.weight!.toStringAsFixed(1) + " kg";
|
||||
}
|
||||
final String unit = bloc.getMyDetail()!.exerciseType!.unit;
|
||||
return Form(
|
||||
child: Scaffold(
|
||||
resizeToAvoidBottomInset: true,
|
||||
appBar: AppBarMin(back: true),
|
||||
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: KeyboardActions(
|
||||
config: _buildConfig(context),
|
||||
child: Container(
|
||||
child: SingleChildScrollView(
|
||||
padding: const 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'),
|
||||
textAlign: TextAlign.center,
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 14,
|
||||
color: Colors.white,
|
||||
)),
|
||||
Text(
|
||||
exerciseName,
|
||||
textAlign: TextAlign.center,
|
||||
style: GoogleFonts.archivoBlack(fontSize: 18, color: Colors.yellow[200]),
|
||||
overflow: TextOverflow.fade,
|
||||
maxLines: 3,
|
||||
softWrap: true,
|
||||
),
|
||||
Divider(
|
||||
color: Colors.transparent,
|
||||
height: 30,
|
||||
),
|
||||
TextFormField(
|
||||
decoration: InputDecoration(
|
||||
contentPadding: EdgeInsets.only(left: 25, top: 5, bottom: 5),
|
||||
labelText: t('Serie'),
|
||||
labelStyle: GoogleFonts.inter(fontSize: 20, color: Colors.yellow[50], decorationColor: Colors.black12),
|
||||
fillColor: Colors.white24,
|
||||
filled: true,
|
||||
border: OutlineInputBorder(
|
||||
gapPadding: 8.0,
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
borderSide: BorderSide(color: Colors.black26, width: 0.4),
|
||||
),
|
||||
),
|
||||
initialValue: bloc.getMyDetail()!.set!.toStringAsFixed(0),
|
||||
focusNode: _nodeText1,
|
||||
keyboardType: TextInputType.number,
|
||||
style: GoogleFonts.archivoBlack(fontSize: 60, color: Colors.yellow[200]),
|
||||
onChanged: (value) => {bloc.add(TrainingPlanSetChange(detail: bloc.getMyDetail()!, set: int.parse(value)))}),
|
||||
Divider(),
|
||||
TextFormField(
|
||||
decoration: InputDecoration(
|
||||
contentPadding: EdgeInsets.only(left: 25, top: 5, bottom: 5),
|
||||
labelText: t(unit),
|
||||
fillColor: Colors.white24,
|
||||
labelStyle: GoogleFonts.inter(fontSize: 20, color: Colors.yellow[50]),
|
||||
filled: true,
|
||||
border: OutlineInputBorder(
|
||||
gapPadding: 4.0,
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
borderSide: BorderSide(color: Colors.green[50]!, width: 0.4),
|
||||
),
|
||||
),
|
||||
focusNode: _nodeText2,
|
||||
initialValue: bloc.getMyDetail()!.repeats!.toStringAsFixed(0),
|
||||
keyboardType: TextInputType.number,
|
||||
style: GoogleFonts.archivoBlack(fontSize: 60, color: Colors.yellow[200]),
|
||||
onChanged: (value) => bloc.add(TrainingPlanRepeatsChange(detail: bloc.getMyDetail()!, repeats: int.parse(value))),
|
||||
),
|
||||
Divider(),
|
||||
weightVisible
|
||||
? TextFormField(
|
||||
decoration: InputDecoration(
|
||||
contentPadding: EdgeInsets.only(left: 25, top: 5, bottom: 5),
|
||||
labelText: t('Weight'),
|
||||
fillColor: Colors.white24,
|
||||
labelStyle: GoogleFonts.inter(fontSize: 20, color: Colors.yellow[50]),
|
||||
filled: true,
|
||||
border: OutlineInputBorder(
|
||||
gapPadding: 2.0,
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
borderSide: BorderSide(color: Colors.green[50]!, width: 0.4),
|
||||
),
|
||||
),
|
||||
focusNode: _nodeText3,
|
||||
initialValue: bloc.getMyDetail()!.weight!.toStringAsFixed(1),
|
||||
keyboardType: TextInputType.numberWithOptions(decimal: true),
|
||||
style: GoogleFonts.archivoBlack(fontSize: 60, color: Colors.yellow[200]),
|
||||
onChanged: (value) => {
|
||||
if (value.isNotEmpty)
|
||||
{
|
||||
value = value.replaceFirst(",", "."),
|
||||
value = value.replaceAll(RegExp(r'[^0-9.]'), ""),
|
||||
bloc.add(TrainingPlanWeightChange(detail: bloc.getMyDetail()!, weight: double.parse(value))),
|
||||
}
|
||||
})
|
||||
: Offstage(),
|
||||
Divider(),
|
||||
Text(
|
||||
summary,
|
||||
style: TextStyle(fontSize: 24, fontWeight: FontWeight.normal, color: Colors.yellow[50]),
|
||||
),
|
||||
Divider(),
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => {
|
||||
bloc.add(TrainingPlanAddExerciseType()),
|
||||
Navigator.of(context).popAndPushNamed("myTrainingPlanCustom"),
|
||||
},
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Image.asset('asset/icon/gomb_zold_b-1.png', width: 140, height: 60),
|
||||
Text(
|
||||
t("Save"),
|
||||
style: TextStyle(fontSize: 16, color: Colors.white),
|
||||
),
|
||||
],
|
||||
)),
|
||||
],
|
||||
),
|
||||
]),
|
||||
)))),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ 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/menu_image.dart';
|
||||
import 'package:aitrainer_app/widgets/victory_widget.dart';
|
||||
import 'package:extended_tabs/extended_tabs.dart';
|
||||
import 'package:ezanimation/ezanimation.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
@@ -48,7 +49,44 @@ class _TrainingPlanExecutePageState extends State<TrainingPlanExecutePage> with
|
||||
if (state is TrainingPlanError) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(backgroundColor: Colors.orange, content: Text(state.message, style: TextStyle(color: Colors.white))));
|
||||
} else if (state is TrainingPlanFinished) {}
|
||||
} else if (state is TrainingPlanDayFinished) {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
builder: (BuildContext context) {
|
||||
return Victory(
|
||||
victory: true,
|
||||
);
|
||||
});
|
||||
bloc!.celebrating = false;
|
||||
} else if (state is TrainingPlanDayReadyToRestart) {
|
||||
if (!bloc!.celebrating) {
|
||||
showCupertinoDialog(
|
||||
useRootNavigator: true,
|
||||
context: context,
|
||||
builder: (_) => CupertinoAlertDialog(
|
||||
title: Text(t("The training is finished")),
|
||||
content: Column(children: [Divider(), Text(t("Do you want to restart, or select a new Training Plan?"))]),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(t("New Training Plan"), textAlign: TextAlign.center),
|
||||
onPressed: () => {
|
||||
Navigator.pop(context),
|
||||
Navigator.of(context).popAndPushNamed('myTrainingPlans'),
|
||||
bloc!.restarting = false,
|
||||
}),
|
||||
TextButton(
|
||||
child: Text(t("Restart")),
|
||||
onPressed: () {
|
||||
bloc!.restart();
|
||||
Navigator.pop(context);
|
||||
Navigator.of(context).popAndPushNamed('home');
|
||||
},
|
||||
)
|
||||
],
|
||||
));
|
||||
}
|
||||
}
|
||||
}, builder: (context, state) {
|
||||
return ModalProgressHUD(
|
||||
child: ExerciseTabs(bloc: bloc!),
|
||||
@@ -66,7 +104,7 @@ class _TrainingPlanExecutePageState extends State<TrainingPlanExecutePage> with
|
||||
backgroundColor: Colors.orange[800],
|
||||
icon: Icon(CustomIcon.weight_hanging),
|
||||
label: Text(
|
||||
t("Training!"),
|
||||
t("Training") + "!",
|
||||
style: GoogleFonts.inter(fontWeight: FontWeight.bold, fontSize: 16),
|
||||
),
|
||||
),
|
||||
@@ -88,7 +126,7 @@ class _ExerciseTabs extends State<ExerciseTabs> with TickerProviderStateMixin {
|
||||
void initState() {
|
||||
super.initState();
|
||||
tabController = TabController(length: widget.bloc.dayNames.length, vsync: this);
|
||||
tabController.animateTo(0, duration: Duration(milliseconds: 300));
|
||||
tabController.animateTo(widget.bloc.activeDayIndex, duration: Duration(milliseconds: 300));
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -107,6 +145,7 @@ class _ExerciseTabs extends State<ExerciseTabs> with TickerProviderStateMixin {
|
||||
ExtendedTabBar(
|
||||
tabs: getTabNames(),
|
||||
controller: tabController,
|
||||
onTap: (index) => bloc.activeDayIndex = index,
|
||||
),
|
||||
Expanded(
|
||||
child: ExtendedTabBarView(
|
||||
@@ -257,7 +296,6 @@ class _ExerciseListState extends State<ExerciseList> with Trans {
|
||||
} else {
|
||||
startText = bloc.isStarted() ? t("Continue your training") : t("Start your training");
|
||||
explainingText = bloc.getMyPlan()!.name != null ? bloc.getMyPlan()!.name! : "";
|
||||
print(" *** Plan NAME ${bloc.getMyPlan()!.name}");
|
||||
}
|
||||
|
||||
return TimelineTile(
|
||||
@@ -393,7 +431,6 @@ class _ExerciseListState extends State<ExerciseList> with Trans {
|
||||
bloc.getMyPlan()!.days[widget.dayName] != null &&
|
||||
bloc.getMyPlan()!.days[widget.dayName]!.isNotEmpty) {
|
||||
bloc.getMyPlan()!.days[widget.dayName]!.forEach((element) {
|
||||
//bloc.getMyPlan()!.details.forEach((element) {
|
||||
tiles.add(GestureDetector(
|
||||
onTap: () => bloc.getNext() != null ? executeExercise(bloc, bloc.getNext()!, context) : Navigator.of(context).pushNamed('home'),
|
||||
child: ExerciseTile(
|
||||
@@ -670,7 +707,7 @@ class _ExerciseTileState extends State<ExerciseTile> with Trans {
|
||||
style: GoogleFonts.inter(fontSize: 12, color: done ? Colors.grey[100] : Colors.white, fontWeight: FontWeight.bold)),
|
||||
]),
|
||||
)),
|
||||
done
|
||||
/* done
|
||||
? AnimatedBuilder(
|
||||
animation: animation,
|
||||
builder: (context, snapshot) {
|
||||
@@ -682,7 +719,7 @@ class _ExerciseTileState extends State<ExerciseTile> with Trans {
|
||||
Text("Result", style: GoogleFonts.inter(fontSize: 10, color: Colors.white)),
|
||||
]);
|
||||
})
|
||||
: Offstage(),
|
||||
: Offstage(), */
|
||||
isTest
|
||||
? AnimatedBuilder(
|
||||
animation: animation,
|
||||
|
||||
@@ -52,6 +52,7 @@ class MyTrainingPlans extends StatelessWidget with Trans, Logging {
|
||||
} else if (state is TrainingPlanFinished) {
|
||||
Navigator.of(context).pop();
|
||||
final TrainingPlanBloc bloc = BlocProvider.of<TrainingPlanBloc>(context);
|
||||
bloc.setMyPlan(Cache().myTrainingPlan);
|
||||
Navigator.of(context).pushNamed("myTrainingPlanExecute", arguments: bloc);
|
||||
}
|
||||
},
|
||||
@@ -76,7 +77,8 @@ class MyTrainingPlans extends StatelessWidget with Trans, Logging {
|
||||
delegate: SliverChildListDelegate([
|
||||
getTrainingPlan(t("My Active Training"), "asset/image/exercise_plan_execute.jpg", "",
|
||||
color: Colors.yellow[400]!, route: "myTrainingPlanExecute"),
|
||||
getTrainingPlan(t("My Custom Plan"), "asset/image/exercise_plan_custom.jpg", ""),
|
||||
getTrainingPlan(t("My Custom Plan"), "asset/image/exercise_plan_custom.jpg", "",
|
||||
color: Colors.green[100]!, route: "myTrainingPlanCustom"),
|
||||
getTrainingPlan(t("Training Plans for Beginners"), "asset/menu/training_plans_q_beginner.jpg", "beginner"),
|
||||
getTrainingPlan(t("Training Plans for Home"), "asset/menu/training_plans_q_home.jpg", "home"),
|
||||
getTrainingPlan(t("Training Plans Advanced"), "asset/menu/training_plans_q_advanced.jpg", "advanced"),
|
||||
@@ -104,7 +106,7 @@ class MyTrainingPlans extends StatelessWidget with Trans, Logging {
|
||||
textAlignment: Alignment.topLeft,
|
||||
text: name,
|
||||
style: GoogleFonts.robotoMono(
|
||||
textStyle: TextStyle(fontSize: 14, color: color, fontWeight: FontWeight.bold, backgroundColor: Colors.black54.withOpacity(0.4))),
|
||||
textStyle: TextStyle(fontSize: 12, color: color, fontWeight: FontWeight.bold, backgroundColor: Colors.black54.withOpacity(0.4))),
|
||||
image: imageUrl,
|
||||
left: 5,
|
||||
textColor: color,
|
||||
@@ -113,7 +115,11 @@ class MyTrainingPlans extends StatelessWidget with Trans, Logging {
|
||||
if (route == "myTrainingPlanActivate") {
|
||||
HashMap<String, dynamic> args = HashMap();
|
||||
args['parentName'] = parentName;
|
||||
Navigator.of(context).pushNamed("myTrainingPlanActivate", arguments: args);
|
||||
Navigator.of(context).pushNamed(route, arguments: args);
|
||||
} else if (route == "myTrainingPlanExecute") {
|
||||
final TrainingPlanBloc bloc = BlocProvider.of<TrainingPlanBloc>(context);
|
||||
bloc.setMyPlan(Cache().myTrainingPlan);
|
||||
Navigator.of(context).pushNamed(route);
|
||||
} else {
|
||||
Navigator.of(context).pushNamed(route);
|
||||
}
|
||||
@@ -103,7 +103,7 @@ class _AppBarNav extends State<AppBarNav> with SingleTickerProviderStateMixin, C
|
||||
}
|
||||
else if (widget.depth != null)
|
||||
{
|
||||
if (widget.depth == 0) {Navigator.of(context).pushNamed('home')} else {Navigator.of(context).pop()}
|
||||
if (widget.depth == 0) {Navigator.of(context).popAndPushNamed('home')} else {Navigator.of(context).pop()}
|
||||
}
|
||||
},
|
||||
));
|
||||
|
||||
@@ -77,32 +77,27 @@ class _NawDrawerWidget extends State<BottomNavigator> with Trans, Logging {
|
||||
widget.bottomNavIndex = index;
|
||||
switch (index) {
|
||||
case 0:
|
||||
Navigator.of(context).pop();
|
||||
Track().track(TrackingEvent.home);
|
||||
Navigator.of(context).pushNamed('home');
|
||||
Navigator.of(context).popAndPushNamed('home');
|
||||
|
||||
break;
|
||||
case 1:
|
||||
Navigator.of(context).pop();
|
||||
Track().track(TrackingEvent.my_development);
|
||||
Navigator.of(context).pushNamed('myDevelopment');
|
||||
Navigator.of(context).popAndPushNamed('myDevelopment');
|
||||
break;
|
||||
case 2:
|
||||
Navigator.of(context).pop();
|
||||
Track().track(TrackingEvent.my_exerciseplan);
|
||||
Navigator.of(context).pushNamed('myTrainingPlans');
|
||||
Navigator.of(context).popAndPushNamed('myTrainingPlans');
|
||||
|
||||
break;
|
||||
case 3:
|
||||
Navigator.of(context).pop();
|
||||
Track().track(TrackingEvent.account);
|
||||
Navigator.of(context).pushNamed('account');
|
||||
Navigator.of(context).popAndPushNamed('account');
|
||||
|
||||
break;
|
||||
case 4:
|
||||
Navigator.of(context).pop();
|
||||
Track().track(TrackingEvent.settings);
|
||||
Navigator.of(context).pushNamed('settings');
|
||||
Navigator.of(context).popAndPushNamed('settings');
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ class ImageButton extends StatelessWidget {
|
||||
child: Text(text,
|
||||
maxLines: 2,
|
||||
style: GoogleFonts.archivoBlack(
|
||||
fontSize: 16,
|
||||
fontSize: 15,
|
||||
color: textColor,
|
||||
shadows: <Shadow>[
|
||||
Shadow(
|
||||
|
||||
@@ -309,7 +309,7 @@ class _MenuPageWidgetState extends State<MenuPageWidget> with Trans, Logging {
|
||||
} else {
|
||||
Track().track(TrackingEvent.search, eventValue: value.exerciseType!.name);
|
||||
menuBloc.ability = ExerciseAbility.oneRepMax;
|
||||
Navigator.of(context).pushNamed('exerciseNewPage', arguments: value.exerciseType);
|
||||
Navigator.of(context).popAndPushNamed('exerciseNewPage', arguments: value.exerciseType);
|
||||
}
|
||||
},
|
||||
),
|
||||
@@ -328,7 +328,7 @@ class _MenuPageWidgetState extends State<MenuPageWidget> with Trans, Logging {
|
||||
if (Cache().myTrainingPlan != null) {
|
||||
final TrainingPlanBloc bloc = BlocProvider.of<TrainingPlanBloc>(context);
|
||||
bloc.setMyPlan(Cache().myTrainingPlan);
|
||||
Navigator.of(context).pushNamed("myTrainingPlanExecute");
|
||||
Navigator.of(context).popAndPushNamed("myTrainingPlanExecute");
|
||||
}
|
||||
},
|
||||
onCancel: () => {
|
||||
@@ -348,11 +348,11 @@ class _MenuPageWidgetState extends State<MenuPageWidget> with Trans, Logging {
|
||||
);
|
||||
}))
|
||||
: Offstage(),
|
||||
activeExercisePlan
|
||||
/* activeExercisePlan
|
||||
? SizedBox(
|
||||
width: 10,
|
||||
)
|
||||
: Offstage(),
|
||||
: Offstage(), */
|
||||
Cache().activeExercisePlan != null
|
||||
? GestureDetector(
|
||||
onTap: () => showDialog(
|
||||
@@ -367,7 +367,7 @@ class _MenuPageWidgetState extends State<MenuPageWidget> with Trans, Logging {
|
||||
Navigator.of(context).pop(),
|
||||
if (Cache().activeExercisePlan != null)
|
||||
{
|
||||
Navigator.of(context).pushNamed("testSetExecute"),
|
||||
Navigator.of(context).popAndPushNamed("testSetExecute"),
|
||||
}
|
||||
},
|
||||
onCancel: () => {
|
||||
@@ -399,7 +399,6 @@ class _MenuPageWidgetState extends State<MenuPageWidget> with Trans, Logging {
|
||||
void menuClick(WorkoutMenuTree workoutTree, MenuBloc menuBloc) {
|
||||
if (tutorialBloc.isActive) {
|
||||
final String checkText = workoutTree.nameEnglish;
|
||||
print("Click: tutorial is active $checkText");
|
||||
if (!tutorialBloc.checkAction(checkText)) {
|
||||
return;
|
||||
}
|
||||
@@ -412,7 +411,6 @@ class _MenuPageWidgetState extends State<MenuPageWidget> with Trans, Logging {
|
||||
Navigator.of(context).pushNamed('testSetEdit', arguments: args);
|
||||
} else if (menuBloc.ability != null && ExerciseAbility.training.equalsTo(menuBloc.ability!) && workoutTree.parent != 0) {
|
||||
HashMap<String, dynamic> args = HashMap();
|
||||
print("menu ${workoutTree.internalName}");
|
||||
args['parentName'] = workoutTree.internalName;
|
||||
Navigator.of(context).pushNamed("myTrainingPlanActivate", arguments: args);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user