From 48804c50f049efde3d660e474361668504d5ca3d Mon Sep 17 00:00:00 2001 From: bossanyit Date: Sun, 30 May 2021 15:29:26 +0200 Subject: [PATCH] WT1.1.18+2 Training Plan days, skip --- ios/Runner.xcodeproj/project.pbxproj | 6 +- lib/bloc/menu/menu_bloc.dart | 3 + .../training_plan/training_plan_bloc.dart | 92 ++++-- .../training_plan/training_plan_event.dart | 6 +- lib/bloc/tutorial/tutorial_bloc.dart | 4 +- lib/model/cache.dart | 13 +- lib/model/customer_training_plan.dart | 20 +- lib/model/customer_training_plan_details.dart | 19 +- lib/model/exercise_ability.dart | 4 +- lib/model/exercise_plan_detail.dart | 3 +- lib/model/workout_menu_tree.dart | 21 +- lib/repository/workout_tree_repository.dart | 10 +- lib/view/training_plan_activate_page.dart | 2 +- lib/view/training_plan_execute_page.dart | 292 ++++++++++++++++-- lib/view/training_plan_exercise.dart | 8 +- lib/widgets/exercise_save.dart | 35 ++- lib/widgets/menu_page_widget.dart | 8 +- pubspec.lock | 7 + pubspec.yaml | 1 + 19 files changed, 453 insertions(+), 101 deletions(-) diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index fcc5215..b8acaf4 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -388,7 +388,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = SFJJBDCU6Z; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -531,7 +531,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = SFJJBDCU6Z; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -566,7 +566,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = SFJJBDCU6Z; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( diff --git a/lib/bloc/menu/menu_bloc.dart b/lib/bloc/menu/menu_bloc.dart index 5673052..a1ab9b3 100644 --- a/lib/bloc/menu/menu_bloc.dart +++ b/lib/bloc/menu/menu_bloc.dart @@ -131,6 +131,9 @@ class MenuBloc extends Bloc with Trans, Logging { case "Test Center": ability = ExerciseAbility.mini_test_set; break; + case "Training Plans": + ability = ExerciseAbility.training; + break; case "My Body": ability = ExerciseAbility.none; break; diff --git a/lib/bloc/training_plan/training_plan_bloc.dart b/lib/bloc/training_plan/training_plan_bloc.dart index ae6f378..b3eb374 100644 --- a/lib/bloc/training_plan/training_plan_bloc.dart +++ b/lib/bloc/training_plan/training_plan_bloc.dart @@ -4,13 +4,11 @@ import 'package:aitrainer_app/bloc/menu/menu_bloc.dart'; import 'package:aitrainer_app/model/cache.dart'; import 'package:aitrainer_app/model/customer_training_plan.dart'; import 'package:aitrainer_app/model/customer_training_plan_details.dart'; -import 'package:aitrainer_app/model/customer_training_plan_exercise.dart'; import 'package:aitrainer_app/model/exercise.dart'; import 'package:aitrainer_app/model/exercise_plan_detail.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/service/training_plan_service.dart'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; @@ -22,19 +20,21 @@ class TrainingPlanBloc extends Bloc { final MenuBloc menuBloc; TrainingPlanBloc({required this.trainingPlanRepository, required this.menuBloc}) : super(TrainingPlanInitial()); - CustomerTrainingPlan? myPlan; + CustomerTrainingPlan? _myPlan; bool started = false; + final List dayNames = []; - CustomerTrainingPlan? getMyPlan() => this.myPlan; - setMyPlan(CustomerTrainingPlan myPlan) => this.myPlan = myPlan; + CustomerTrainingPlan? getMyPlan() => this._myPlan; + setMyPlan(CustomerTrainingPlan? myPlan) => this._myPlan = myPlan; @override Stream mapEventToState(TrainingPlanEvent event) async* { try { if (event is TrainingPlanActivate) { yield TrainingPlanLoading(); - myPlan = await trainingPlanRepository.activateTrainingPlan(event.trainingPlanId); - Cache().myTrainingPlan = myPlan; + _myPlan = await trainingPlanRepository.activateTrainingPlan(event.trainingPlanId); + this.activateDays(); + Cache().myTrainingPlan = _myPlan; await Cache().saveMyTrainingPlan(); yield TrainingPlanFinished(); } else if (event is TrainingPlanWeightChange) { @@ -66,13 +66,19 @@ class TrainingPlanBloc extends Bloc { event.detail.state = ExercisePlanDetailState.inProgress; } - exercise.trainingPlanDetailsId = myPlan!.trainingPlanId; + exercise.trainingPlanDetailsId = _myPlan!.trainingPlanId; // save Exercise await ExerciseApi().addExercise(exercise); Cache().addExercise(exercise); - Cache().myTrainingPlan = myPlan; + Cache().myTrainingPlan = _myPlan; + await Cache().saveMyTrainingPlan(); + yield TrainingPlanReady(); + } else if (event is TrainingPlanSkipExercise) { + yield TrainingPlanLoading(); + event.detail.state = ExercisePlanDetailState.skipped; + Cache().myTrainingPlan = _myPlan; await Cache().saveMyTrainingPlan(); yield TrainingPlanReady(); } @@ -81,13 +87,41 @@ class TrainingPlanBloc extends Bloc { } } + void activateDays() { + if (_myPlan == null) { + return; + } + dayNames.clear(); + _myPlan!.days.clear(); + String dayName = "."; + _myPlan!.details.forEach((element) { + if (element.day != null && element.day != dayName) { + dayNames.add(element.day!); + dayName = element.day!; + } + if (_myPlan!.days[dayName] == null) { + if (dayName == ".") { + dayName = ""; + } + _myPlan!.days[dayName] = []; + } + _myPlan!.days[dayName]!.add(element); + }); + + if (dayNames.length == 0) { + dayNames.add(""); + _myPlan!.days[""] = []; + _myPlan!.days[""]!.addAll(_myPlan!.details); + } + } + CustomerTrainingPlanDetails? getTrainingPlanDetail(int trainingPlanDetailsId) { CustomerTrainingPlanDetails? detail; - if (myPlan == null || myPlan!.details.isEmpty) { + if (_myPlan == null || _myPlan!.details.isEmpty) { return null; } - for (final det in this.myPlan!.details) { + for (final det in this._myPlan!.details) { if (det.customerTrainingPlanDetailsId == trainingPlanDetailsId) { detail = det; break; @@ -118,21 +152,21 @@ class TrainingPlanBloc extends Bloc { } CustomerTrainingPlanDetails? getNext() { - if (myPlan == null || myPlan!.details.isEmpty) { + if (_myPlan == null || _myPlan!.details.isEmpty) { return null; } CustomerTrainingPlanDetails? next; int minStep = 99; - for (final detail in this.myPlan!.details) { + for (final detail in this._myPlan!.details) { if (!detail.state.equalsTo(ExercisePlanDetailState.finished)) { - if (detail.exercises.isEmpty) { + if (detail.exercises.isEmpty && !detail.state.equalsTo(ExercisePlanDetailState.skipped)) { next = detail; minStep = 1; break; } else { final int step = detail.exercises.length; - if (step < minStep) { + if (step < minStep && !detail.state.equalsTo(ExercisePlanDetailState.skipped)) { next = detail; minStep = step; if (detail.parallel != true) { @@ -142,19 +176,41 @@ class TrainingPlanBloc extends Bloc { } } } - + print("Next detail $next"); return next; } bool isStarted() { - if (myPlan == null || myPlan!.details.isEmpty) { + if (_myPlan == null || _myPlan!.details.isEmpty) { return false; } - if (myPlan!.details[0].state == ExercisePlanDetailState.start) { + if (_myPlan!.details[0].state == ExercisePlanDetailState.start) { return false; } else { return true; } } + + double getOffset() { + double offset = 5; + if (_myPlan == null) { + return offset; + } + int indexInProgress = 0; + int indexInStart = 0; + for (var detail in _myPlan!.details) { + if (detail.state == ExercisePlanDetailState.inProgress) { + break; + } + if (detail.state == ExercisePlanDetailState.start) { + break; + } + indexInStart++; + indexInProgress++; + } + int index = indexInStart > indexInProgress ? indexInStart : indexInProgress; + offset = index * 80; + return offset; + } } diff --git a/lib/bloc/training_plan/training_plan_event.dart b/lib/bloc/training_plan/training_plan_event.dart index b4efe50..7547119 100644 --- a/lib/bloc/training_plan/training_plan_event.dart +++ b/lib/bloc/training_plan/training_plan_event.dart @@ -50,5 +50,9 @@ class TrainingPlanFinishTraining extends TrainingPlanEvent { } class TrainingPlanSkipExercise extends TrainingPlanEvent { - const TrainingPlanSkipExercise(); + final CustomerTrainingPlanDetails detail; + const TrainingPlanSkipExercise({required this.detail}); + + @override + List get props => [detail]; } diff --git a/lib/bloc/tutorial/tutorial_bloc.dart b/lib/bloc/tutorial/tutorial_bloc.dart index 311b0d1..c73d261 100644 --- a/lib/bloc/tutorial/tutorial_bloc.dart +++ b/lib/bloc/tutorial/tutorial_bloc.dart @@ -40,7 +40,6 @@ class TutorialBloc extends Bloc with Logging { init() { if (Cache().tutorials != null) { - print("Actual step: $step"); final List tutorials = Cache().tutorials!; tutorials.forEach((element) { final String realTutorialName = tutorialName.replaceFirst("tutorial", "").toLowerCase(); @@ -64,7 +63,7 @@ class TutorialBloc extends Bloc with Logging { actualText = tutorial!.steps![step].tutorialTextTranslation; this.actualCheck = tutorial!.steps![step].checkText; - print("Step: $step, text: $actualCheck"); + this.checks = []; if (this.actualCheck != null) { @@ -91,7 +90,6 @@ class TutorialBloc extends Bloc with Logging { } bool? isActivityDone = Cache().activitiesDone[activityDone.toStr()]; - log("$tutorialName isActivityDone? $isActivityDone"); if (isActivityDone == null || isActivityDone == true) { return true; } diff --git a/lib/model/cache.dart b/lib/model/cache.dart index 4fc677a..813a921 100644 --- a/lib/model/cache.dart +++ b/lib/model/cache.dart @@ -238,10 +238,15 @@ class Cache with Logging { if (savedTrainingPlanJson == null) { return; } - - final Map map = JsonDecoder().convert(savedTrainingPlanJson); - print("Training plan: $savedTrainingPlanJson"); - this.myTrainingPlan = CustomerTrainingPlan.fromJsonWithDetails(map); + //String jsonPlan = savedTrainingPlanJson.replaceAllMapped(RegExp(r'[a-zA-Z]+\:'), (Match m) => "\"${m[0]}\""); + Map map; + try { + map = JsonDecoder().convert(savedTrainingPlanJson); + print("Training plan: $savedTrainingPlanJson"); + this.myTrainingPlan = CustomerTrainingPlan.fromJsonWithDetails(map); + } on Exception catch (e) { + print(e.toString()); + } } Future deleteActiveExercisePlan() async { diff --git a/lib/model/customer_training_plan.dart b/lib/model/customer_training_plan.dart index 903e81d..4a12a46 100644 --- a/lib/model/customer_training_plan.dart +++ b/lib/model/customer_training_plan.dart @@ -1,3 +1,4 @@ +import 'dart:collection'; import 'dart:convert'; import 'package:intl/intl.dart'; @@ -17,6 +18,8 @@ class CustomerTrainingPlan { List details = []; + HashMap> days = HashMap(); + CustomerTrainingPlan.fromJson(Map json) { this.customerTrainingPlanId = json['customerTrainingPlanId']; this.customerId = json['customerId']; @@ -38,12 +41,17 @@ class CustomerTrainingPlan { try { final String details = json['details']; - String jsonDetails = details.replaceAllMapped( - RegExp(r'([a-zA-Z]+|[0-9]{4}\-[0-9]{2}\-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2})'), (Match m) => "\"${m[0]}\""); + String jsonDetails = details.replaceAllMapped(RegExp(r'([a-zA-Z]+)\:'), (Match m) => "\"${m[1]}\":"); + jsonDetails = jsonDetails.replaceAllMapped(RegExp(r'\: ([a-zA-Z /]+)'), (Match m) => ":\"${m[1]}\""); - jsonDetails = jsonDetails.replaceAll(r'\"null\"', 'null'); - jsonDetails = jsonDetails.replaceAll(r'\"false\"', 'false'); - jsonDetails = jsonDetails.replaceAll(r'\"true\"', 'true'); + jsonDetails = + jsonDetails.replaceAllMapped(RegExp(r'([0-9]{4}\-[0-9]{2}\-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2})'), (Match m) => "\"${m[0]}\""); + + // jsonDetails = jsonDetails.replaceAll(r'\"null\"', 'null'); + // jsonDetails = jsonDetails.replaceAll(r'\"false\"', 'false'); + // jsonDetails = jsonDetails.replaceAll(r'\"true\"', 'true'); + + print("detail: $jsonDetails"); Iterable iterable = jsonDecode(jsonDetails); this.details = iterable.map((detail) => CustomerTrainingPlanDetails.fromJsonWithExerciseList(detail)).toList(); @@ -66,7 +74,7 @@ class CustomerTrainingPlan { "customerTrainingPlanId": this.customerTrainingPlanId, "customerId": this.customerId, "trainingPlanId": this.trainingPlanId, - "dateAdd": DateFormat('yyyy-MM-dd HH:mm:ss').format(this.dateAdd!), + "dateAdd": DateFormat('yyyy-MM-dd HH:mm:ss').format(this.dateAdd!).toString(), "name": this.name, "active": this.active, "status": this.status, diff --git a/lib/model/customer_training_plan_details.dart b/lib/model/customer_training_plan_details.dart index 46b8504..ba7531f 100644 --- a/lib/model/customer_training_plan_details.dart +++ b/lib/model/customer_training_plan_details.dart @@ -51,7 +51,7 @@ class CustomerTrainingPlanDetails { : json['customerTrainingPlanDetailsId']; this.exerciseTypeId = json['exerciseTypeId']; this.set = json['set']; - this.repeats = json['repeats']; + this.repeats = json['repeats'] == "null" ? -1 : json['repeats']; this.weight = json['weight']; this.restingTime = json['restingTime']; this.parallel = json['parallel'] == "false" @@ -59,7 +59,10 @@ class CustomerTrainingPlanDetails { : json['parallel'] == "true" ? true : null; - this.day = json['day']; + this.day = json['day'].toString(); + if (this.day == null || this.day == "null") { + this.day = ""; + } try { Iterable iterable = json['exercises']; this.exercises = iterable.map((exercise) => Exercise.fromJson(exercise)).toList(); @@ -67,13 +70,16 @@ class CustomerTrainingPlanDetails { print("JsonDecode error " + e.toString()); } - if (exercises.length >= this.set!) { + if (json['state'] == ExercisePlanDetailState.finished.toStr()) { this.state = ExercisePlanDetailState.finished; - } else if (exercises.length > 0) { + } else if (json['state'] == ExercisePlanDetailState.inProgress.toStr()) { this.state = ExercisePlanDetailState.inProgress; + } else if (json['state'] == ExercisePlanDetailState.skipped.toStr()) { + this.state = ExercisePlanDetailState.skipped; } else { this.state = ExercisePlanDetailState.start; } + this.exerciseType = Cache().getExerciseTypeById(exerciseTypeId!); } @@ -92,7 +98,7 @@ class CustomerTrainingPlanDetails { Map toJsonWithExercises() { final Map jsonMap = { - "customerTrainingPlanDetailsId": this.customerTrainingPlanDetailsId, + //"customerTrainingPlanDetailsId": this.customerTrainingPlanDetailsId, "exerciseTypeId": this.exerciseTypeId, "set": this.set, "repeats": this.repeats, @@ -100,6 +106,7 @@ class CustomerTrainingPlanDetails { "restingTime": this.restingTime, "parallel": this.parallel, 'exercises': exercises.isEmpty ? [].toString() : exercises.map((exercise) => exercise.toJson()).toList().toString(), + 'state': this.state.toStr(), }; if (this.day != null && this.day!.isNotEmpty) { jsonMap["day"] = this.day; @@ -110,5 +117,5 @@ class CustomerTrainingPlanDetails { } @override - String toString() => this.toJson().toString(); + String toString() => this.toJsonWithExercises().toString(); } diff --git a/lib/model/exercise_ability.dart b/lib/model/exercise_ability.dart index 93477b8..215b046 100644 --- a/lib/model/exercise_ability.dart +++ b/lib/model/exercise_ability.dart @@ -1,4 +1,4 @@ -enum ExerciseAbility { oneRepMax, endurance, running, mini_test_set, paralell_test, none } +enum ExerciseAbility { oneRepMax, endurance, running, mini_test_set, paralell_test, training, none } extension ExerciseAbilityExt on ExerciseAbility { String enumToString() => this.toString().split(".").last; @@ -16,6 +16,8 @@ extension ExerciseAbilityExt on ExerciseAbility { return "Compact Test"; case ExerciseAbility.paralell_test: return "Custom Test"; + case ExerciseAbility.training: + return "Training"; default: return "Compact Test"; } diff --git a/lib/model/exercise_plan_detail.dart b/lib/model/exercise_plan_detail.dart index 6f208d0..417254e 100644 --- a/lib/model/exercise_plan_detail.dart +++ b/lib/model/exercise_plan_detail.dart @@ -3,11 +3,12 @@ import 'dart:convert'; import 'package:aitrainer_app/model/exercise.dart'; import 'package:aitrainer_app/model/exercise_type.dart'; -enum ExercisePlanDetailState { start, inProgress, finished } +enum ExercisePlanDetailState { start, inProgress, skipped, finished } extension ExericisePlanDetailStateExt on ExercisePlanDetailState { bool equalsTo(ExercisePlanDetailState state) => this.toString() == state.toString(); bool equalsStringTo(String state) => this.toString() == state; + String toStr() => this.toString().split(".").last; } class ExercisePlanDetail { diff --git a/lib/model/workout_menu_tree.dart b/lib/model/workout_menu_tree.dart index 55a93e0..370e072 100644 --- a/lib/model/workout_menu_tree.dart +++ b/lib/model/workout_menu_tree.dart @@ -40,9 +40,26 @@ class WorkoutMenuTree { late String parentName; late String parentNameEnglish; late int sort; + late String internalName; - WorkoutMenuTree(this.id, this.parent, this.name, this.imageName, this.color, this.fontSize, this.child, this.exerciseTypeId, - this.exerciseType, this.base, this.is1RM, this.isRunning, this.nameEnglish, this.parentName, this.parentNameEnglish, this.sort); + WorkoutMenuTree( + this.id, + this.parent, + this.name, + this.imageName, + this.color, + this.fontSize, + this.child, + this.exerciseTypeId, + this.exerciseType, + this.base, + this.is1RM, + this.isRunning, + this.nameEnglish, + this.parentName, + this.parentNameEnglish, + this.sort, + this.internalName); Map toJson() { return { diff --git a/lib/repository/workout_tree_repository.dart b/lib/repository/workout_tree_repository.dart index a0d2a41..c722f40 100644 --- a/lib/repository/workout_tree_repository.dart +++ b/lib/repository/workout_tree_repository.dart @@ -38,12 +38,10 @@ class WorkoutTreeRepository with Logging { exerciseTree.sort((a, b) => a.sort!.compareTo(b.sort!)); exerciseTree.forEach((treeItem) async { - //log(" -- TreeItem " + treeItem.toJson().toString() + " active " + treeItem.active.toString()); if (treeItem.active == true) { String treeName = isEnglish! ? treeItem.name : treeItem.nameTranslation; - bool is1RM = - treeItem.name.contains("Muscle") || treeItem.name.contains("Shape") || treeItem.name.contains("Strength") ? true : false; + bool is1RM = treeItem.internalName != null && treeItem.internalName!.contains("one_rep_max") ? true : false; if (!is1RM) { is1RM = this.isParent1RM(treeItem.parentId); } @@ -69,7 +67,8 @@ class WorkoutTreeRepository with Logging { treeItem.name, parent != null ? parent.name : "", parent != null ? parent.nameEnglish : "", - treeItem.sort!); + treeItem.sort!, + treeItem.internalName != null ? treeItem.internalName! : ""); menuItem = this.setWorkoutTypes(menuItem, treeItem); this.tree[treeItem.name + "_" + treeItem.parentId.toString()] = menuItem; //log("WorkoutMenuTree item ${menuItem.toJson()}"); @@ -110,7 +109,8 @@ class WorkoutTreeRepository with Logging { exerciseType.name, parent != null ? parent.name : "", parent != null ? parent.nameEnglish : "", - 0); + 0, + ""); this.tree[exerciseType.name] = menuItem; if (isRunning || is1RM) { menuAsExercise.add(menuItem); diff --git a/lib/view/training_plan_activate_page.dart b/lib/view/training_plan_activate_page.dart index 6ab8df5..abc7413 100644 --- a/lib/view/training_plan_activate_page.dart +++ b/lib/view/training_plan_activate_page.dart @@ -234,7 +234,7 @@ class TrainingPlanActivatePage extends StatelessWidget with Trans { onPrimary: Colors.white, primary: Colors.orange, ), - child: Text(t("Activate")), + child: Text(t("Start")), onPressed: () { if (Cache().myTrainingPlan != null) { showCupertinoDialog( diff --git a/lib/view/training_plan_execute_page.dart b/lib/view/training_plan_execute_page.dart index a6e6bc4..05829eb 100644 --- a/lib/view/training_plan_execute_page.dart +++ b/lib/view/training_plan_execute_page.dart @@ -4,11 +4,14 @@ 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_details.dart'; import 'package:aitrainer_app/model/exercise_plan_detail.dart'; +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/menu_image.dart'; +import 'package:extended_tabs/extended_tabs.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'; @@ -16,10 +19,19 @@ import 'package:modal_progress_hud_nsn/modal_progress_hud_nsn.dart'; import 'package:timeline_tile/timeline_tile.dart'; // ignore: must_be_immutable -class TrainingPlanExecutePage extends StatelessWidget with Trans { +class TrainingPlanExecutePage extends StatefulWidget { + @override + _TrainingPlanExecutePageState createState() => _TrainingPlanExecutePageState(); +} + +class _TrainingPlanExecutePageState extends State with Trans { + final scrollController = ScrollController(); + TrainingPlanBloc? bloc; + @override Widget build(BuildContext context) { - final TrainingPlanBloc bloc = BlocProvider.of(context); + bloc = BlocProvider.of(context); + bloc!.activateDays(); setContext(context); return Scaffold( appBar: AppBarNav(depth: 0), @@ -39,7 +51,7 @@ class TrainingPlanExecutePage extends StatelessWidget with Trans { } else if (state is TrainingPlanFinished) {} }, builder: (context, state) { return ModalProgressHUD( - child: getExercises(bloc), + child: ExerciseTabs(bloc: bloc!), inAsyncCall: state is TrainingPlanLoading, opacity: 0.5, color: Colors.black54, @@ -48,7 +60,9 @@ class TrainingPlanExecutePage extends StatelessWidget with Trans { }), ), floatingActionButton: FloatingActionButton.extended( - onPressed: () => bloc.getNext() != null ? executeExercise(bloc, bloc.getNext()!) : Navigator.of(context).pushNamed('home'), + onPressed: () => bloc!.getNext() != null + ? _ExerciseListState.executeExercise(bloc!, bloc!.getNext()!, context) + : Navigator.of(context).pushNamed('home'), backgroundColor: Colors.orange[800], icon: Icon(CustomIcon.weight_hanging), label: Text( @@ -58,10 +72,171 @@ class TrainingPlanExecutePage extends StatelessWidget with Trans { ), ); } +} - Widget getExercises(TrainingPlanBloc bloc) { - return CustomScrollView(slivers: [ - SliverList(delegate: SliverChildListDelegate(getTiles(bloc))), +class ExerciseTabs extends StatefulWidget { + final TrainingPlanBloc bloc; + ExerciseTabs({required this.bloc}); + @override + _ExerciseTabs createState() => _ExerciseTabs(); +} + +class _ExerciseTabs extends State with TickerProviderStateMixin { + late TabController tabController; + + @override + void initState() { + super.initState(); + tabController = TabController(length: widget.bloc.dayNames.length, vsync: this); + tabController.animateTo(0, duration: Duration(milliseconds: 300)); + } + + @override + void dispose() { + tabController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return getTabs(widget.bloc); + } + + Widget getTabs(TrainingPlanBloc bloc) { + return Column(children: [ + ExtendedTabBar( + tabs: getTabNames(), + controller: tabController, + ), + Expanded( + child: ExtendedTabBarView( + children: getExerciseLists(), + controller: tabController, + + /// if link is true and current tabbarview over scroll, + /// it will check and scroll ancestor or child tabbarView. + link: true, + + /// cache page count + /// default is 0. + /// if cacheExtent is 1, it has two pages in cache + /// null is infinity, it will cache all pages + cacheExtent: 0, + )), + ]); + } + + List getTabNames() { + List 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( + 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( + 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)); + }); + return tabs; + } + + List getExerciseLists() { + List list = []; + widget.bloc.dayNames.forEach((element) { + list.add(ExerciseList(bloc: widget.bloc, dayName: element)); + }); + return list; + } +} + +class ExerciseList extends StatefulWidget { + final TrainingPlanBloc bloc; + final String dayName; + ExerciseList({required this.bloc, required this.dayName}); + + @override + _ExerciseListState createState() => _ExerciseListState(); +} + +class _ExerciseListState extends State with Trans { + final scrollController = ScrollController(); + double offset = 5; + + @override + void initState() { + WidgetsBinding.instance!.addPostFrameCallback((_) { + animate(); + }); + super.initState(); + } + + @override + void didUpdateWidget(ExerciseList page) { + super.didUpdateWidget(page); + WidgetsBinding.instance!.addPostFrameCallback((_) { + animate(); + }); + } + + void animate() { + offset = widget.bloc.getOffset(); + if (scrollController.hasClients) { + scrollController.animateTo(offset, duration: Duration(milliseconds: 300), curve: Curves.easeIn); + } + } + + @override + void dispose() { + scrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + setContext(context); + return CustomScrollView(controller: scrollController, slivers: [ + SliverList(delegate: SliverChildListDelegate(getTiles(widget.bloc))), ]); } @@ -69,7 +244,7 @@ class TrainingPlanExecutePage extends StatelessWidget with Trans { List tiles = []; tiles.add(getStartTile(bloc)); tiles.addAll(getExerciseTiles(bloc, context)); - if (bloc.myPlan != null) tiles.add(getEndTile()); + if (bloc.getMyPlan() != null) tiles.add(getEndTile()); return tiles; } @@ -77,11 +252,12 @@ class TrainingPlanExecutePage extends StatelessWidget with Trans { String startText = ""; String explainingText = ""; if (null == bloc.getMyPlan()) { - startText = "No Active Training Plan"; - explainingText = "Please select one in the Training menu, or create your custom plan"; + startText = t("No Active Training Plan"); + explainingText = t("Please select one in the Training menu, or create your custom plan"); } else { - startText = bloc.isStarted() ? "Continue your training" : "Start your training"; + 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( @@ -212,10 +388,14 @@ class TrainingPlanExecutePage extends StatelessWidget with Trans { List getExerciseTiles(TrainingPlanBloc bloc, BuildContext context) { List tiles = []; - if (bloc.myPlan != null && bloc.myPlan!.details.isNotEmpty) { - bloc.myPlan!.details.forEach((element) { + if (bloc.getMyPlan() != null && + bloc.getMyPlan()!.details.isNotEmpty && + 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: () => {}, + onTap: () => bloc.getNext() != null ? executeExercise(bloc, bloc.getNext()!, context) : Navigator.of(context).pushNamed('home'), child: ExerciseTile( bloc: bloc, detail: element, @@ -226,7 +406,7 @@ class TrainingPlanExecutePage extends StatelessWidget with Trans { return tiles; } - void executeExercise(TrainingPlanBloc bloc, CustomerTrainingPlanDetails detail) { + static void executeExercise(TrainingPlanBloc bloc, CustomerTrainingPlanDetails detail, BuildContext context) { CustomerTrainingPlanDetails? next = bloc.getNext(); if (next != null) { @@ -234,9 +414,10 @@ class TrainingPlanExecutePage extends StatelessWidget with Trans { String description = ""; String description2 = ""; if (next.exerciseTypeId != detail.exerciseTypeId) { - title = t("Stop!"); - description = t("Please continue with the next exercise in the queue:") + next.exerciseType!.nameTranslation; - description2 = t("Or, you can redifine this exercise queue in the Compact Test menu"); + title = AppLocalizations.of(context)!.translate("Stop!"); + description = AppLocalizations.of(context)!.translate("Please continue with the next exercise in the queue:") + + next.exerciseType!.nameTranslation; + description2 = AppLocalizations.of(context)!.translate("Or, you can redifine this exercise queue in the Compact Test menu"); } else { final HashMap args = HashMap(); args['exerciseType'] = next.exerciseType; @@ -264,8 +445,7 @@ class TrainingPlanExecutePage extends StatelessWidget with Trans { } } -// ignore: must_be_immutable -class ExerciseTile extends StatefulWidget with Trans { +class ExerciseTile extends StatefulWidget { final TrainingPlanBloc bloc; final CustomerTrainingPlanDetails detail; @@ -323,6 +503,16 @@ class _ExerciseTileState extends State with Trans { size: 40, color: Colors.green, ))); + } else if (state.equalsTo(ExercisePlanDetailState.skipped)) { + return ClipRRect( + borderRadius: BorderRadius.circular(24.0), + child: Container( + color: Colors.white, + child: Icon( + CustomIcon.stop_1, + size: 40, + color: Colors.grey, + ))); } else { return Image.asset( "asset/image/pict_reps_volumen_db.png", @@ -334,7 +524,7 @@ class _ExerciseTileState extends State with Trans { Widget build(BuildContext context) { setContext(context); final ExercisePlanDetailState state = widget.detail.state; - final bool done = state.equalsTo(ExercisePlanDetailState.finished); + 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!.toStringAsFixed(1); @@ -363,16 +553,38 @@ class _ExerciseTileState extends State with Trans { height: 40, indicator: getIndicator(state), ), + startChild: Container( + child: Column(children: [ + SizedBox( + height: 1, + ), + SizedBox( + height: 55, + ), + done + ? Offstage() + : IconButton( + padding: EdgeInsets.zero, + alignment: Alignment.centerLeft, + icon: Icon( + Icons.skip_next_sharp, + size: 30, + color: Colors.orange[300], + ), + onPressed: () => skip()), + ]), + ), endChild: Container( padding: EdgeInsets.only(left: 10), child: Row(children: [ Container( - width: 120, - height: 80, - child: MenuImage( - imageName: widget.bloc.getActualImageName(widget.detail.exerciseType!.exerciseTypeId), - workoutTreeId: widget.bloc.getActualWorkoutTreeId(widget.detail.exerciseType!.exerciseTypeId)!, - )), + width: 120, + height: 80, + child: MenuImage( + imageName: widget.bloc.getActualImageName(widget.detail.exerciseType!.exerciseTypeId), + workoutTreeId: widget.bloc.getActualWorkoutTreeId(widget.detail.exerciseType!.exerciseTypeId)!, + ), + ), SizedBox( width: 10, ), @@ -507,4 +719,30 @@ class _ExerciseTileState extends State with Trans { ), ); } + + void skip() { + showCupertinoDialog( + useRootNavigator: true, + context: context, + builder: (_) => CupertinoAlertDialog( + title: Text(t("You want to skip really this exercise?")), + content: Column(children: [ + Divider(), + ]), + actions: [ + TextButton( + child: Text(t("No")), + onPressed: () => { + Navigator.pop(context), + }), + TextButton( + child: Text(t("Yes")), + onPressed: () { + Navigator.pop(context); + widget.bloc.add(TrainingPlanSkipExercise(detail: widget.detail)); + }, + ) + ], + )); + } } diff --git a/lib/view/training_plan_exercise.dart b/lib/view/training_plan_exercise.dart index 93ed22f..94bf58b 100644 --- a/lib/view/training_plan_exercise.dart +++ b/lib/view/training_plan_exercise.dart @@ -12,7 +12,6 @@ import 'package:aitrainer_app/model/exercise_type.dart'; import 'package:aitrainer_app/util/trans.dart'; import 'package:aitrainer_app/widgets/app_bar.dart'; -import 'package:aitrainer_app/widgets/bottom_bar_multiple_exercises.dart'; import 'package:aitrainer_app/widgets/exercise_save.dart'; import 'package:aitrainer_app/widgets/number_picker.dart'; import 'package:flutter/material.dart'; @@ -25,7 +24,6 @@ class TrainingPlanExercise extends StatelessWidget with Trans { @override Widget build(BuildContext context) { final HashMap args = ModalRoute.of(context)!.settings.arguments as HashMap; - final ExerciseType exerciseType = args['exerciseType']; final CustomerTrainingPlanDetails detail = args['customerTrainingPlanDetails']; // ignore: close_sinks final TrainingPlanBloc bloc = BlocProvider.of(context); @@ -73,10 +71,6 @@ class TrainingPlanExercise extends StatelessWidget with Trans { style: GoogleFonts.inter(fontWeight: FontWeight.bold, fontSize: 16), ), ), - /* bottomNavigationBar: BottomBarMultipleExercises( - //isSet: executeBloc.miniTestSet == true, - exerciseTypeId: exerciseType.exerciseTypeId, - ), */ ); } @@ -98,6 +92,8 @@ class TrainingPlanExercise extends StatelessWidget with Trans { hasUnitQuantity: detail.exerciseType!.unitQuantityUnit != null, weight: detail.weight == -1 ? 30 : detail.weight, repeats: detail.repeats == -1 ? 12 : detail.repeats, + set: detail.set, + exerciseNr: detail.exercises.length + 1, onUnitQuantityChanged: (value) => bloc.add(TrainingPlanWeightChange(weight: value, detail: detail)), onQuantityChanged: (value) => bloc.add(TrainingPlanRepeatsChange(repeats: value.toInt(), detail: detail)), exerciseTypeId: detail.exerciseType!.exerciseTypeId, diff --git a/lib/widgets/exercise_save.dart b/lib/widgets/exercise_save.dart index 060966a..ec65689 100644 --- a/lib/widgets/exercise_save.dart +++ b/lib/widgets/exercise_save.dart @@ -24,21 +24,24 @@ class ExerciseSave extends StatefulWidget { final int exerciseTypeId; final double? weight; final int? repeats; + final int? set; + final int? exerciseNr; - ExerciseSave({ - required this.onQuantityChanged, - this.onUnitQuantityChanged, - this.onSubmit, - required this.hasUnitQuantity, - this.unitQuantityUnit, - required this.unit, - required this.exerciseName, - required this.exerciseDescription, - required this.exerciseTask, - required this.exerciseTypeId, - this.weight, - this.repeats, - }); + ExerciseSave( + {required this.onQuantityChanged, + this.onUnitQuantityChanged, + this.onSubmit, + required this.hasUnitQuantity, + this.unitQuantityUnit, + required this.unit, + required this.exerciseName, + required this.exerciseDescription, + required this.exerciseTask, + required this.exerciseTypeId, + this.weight, + this.repeats, + this.set, + this.exerciseNr}); @override _ExerciseSaveState createState() => _ExerciseSaveState(); } @@ -230,7 +233,9 @@ class _ExerciseSaveState extends State with Trans { ), widget.hasUnitQuantity ? Text( - t("Step") + ": " + "1/4", + widget.set == null || widget.exerciseNr == null + ? t("Step") + ": " + "1/4" + : t("Step") + ": " + "${widget.exerciseNr}/${widget.set}", style: GoogleFonts.inter( fontSize: 22, color: Colors.white, diff --git a/lib/widgets/menu_page_widget.dart b/lib/widgets/menu_page_widget.dart index 3846a36..4b144d4 100644 --- a/lib/widgets/menu_page_widget.dart +++ b/lib/widgets/menu_page_widget.dart @@ -79,7 +79,6 @@ class _MenuPageWidgetState extends State with Trans, Logging { } if (!tutorialBloc.isTutorialDone()) { if (tutorialBloc.isActive == false && tutorialBloc.canActivate) { - print("Activate tutorial"); tutorialBloc.canActivate = true; tutorialBloc.isActive = true; tutorialBloc.menuBloc = menuBloc; @@ -328,7 +327,7 @@ class _MenuPageWidgetState extends State with Trans, Logging { Navigator.of(context).pop(); if (Cache().myTrainingPlan != null) { final TrainingPlanBloc bloc = BlocProvider.of(context); - bloc.myPlan = Cache().myTrainingPlan; + bloc.setMyPlan(Cache().myTrainingPlan); Navigator.of(context).pushNamed("myTrainingPlanExecute"); } }, @@ -411,6 +410,11 @@ class _MenuPageWidgetState extends State with Trans, Logging { args['templateName'] = workoutTree.nameEnglish; args['templateNameTranslation'] = workoutTree.name; Navigator.of(context).pushNamed('testSetEdit', arguments: args); + } else if (menuBloc.ability != null && ExerciseAbility.training.equalsTo(menuBloc.ability!) && workoutTree.parent != 0) { + HashMap args = HashMap(); + print("menu ${workoutTree.internalName}"); + args['parentName'] = workoutTree.internalName; + Navigator.of(context).pushNamed("myTrainingPlanActivate", arguments: args); } menuBloc.add(MenuTreeDown(item: workoutTree, parent: workoutTree.id)); } else { diff --git a/pubspec.lock b/pubspec.lock index 97b7a7e..a1c72ad 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -274,6 +274,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.2" + extended_tabs: + dependency: "direct main" + description: + name: extended_tabs + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" ezanimation: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 63afae2..0e87cad 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -58,6 +58,7 @@ dependencies: flutter_app_badger: ^1.2.0 #super_tooltip: ^1.0.1 url_launcher: ^6.0.3 + extended_tabs: ^2.2.0 firebase_core: ^1.2.0 firebase_analytics: ^8.1.0