diff --git a/asset/menu/FG_2_edz.jpg b/asset/menu/FG_2_edz.jpg new file mode 100644 index 0000000..5e39527 Binary files /dev/null and b/asset/menu/FG_2_edz.jpg differ diff --git a/asset/menu/stretching.jpg b/asset/menu/stretching.jpg new file mode 100644 index 0000000..5539470 Binary files /dev/null and b/asset/menu/stretching.jpg differ diff --git a/asset/menu/warmup.jpg b/asset/menu/warmup.jpg new file mode 100644 index 0000000..a0b5afc Binary files /dev/null and b/asset/menu/warmup.jpg differ diff --git a/i18n/en.json b/i18n/en.json index e019450..aa24d00 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -436,7 +436,7 @@ "Do you want to restart, or select a new Training Plan?": "Do you want to restart, or select a new Training Plan?", "New Training Plan": "New Training Plan", "Restart": "Restart", - "Training Day": "Training Day", + "Training Day": "Day", "No Active Training Plan": "No Active Training Plan", "Please select one in the Training menu, or create your custom plan": "Please select one in the Training menu, or create your custom plan", "Continue your training": "Continue your training", @@ -473,5 +473,8 @@ "A new version of Workout Test is available!": "A new version of Workout Test is available!", "Update Now": "Update Now", "Update App?": "Update App?", - "Want to update?": "Please update the app" + "Want to update?": "Please update the app", + "Attention!": "Attention!", + "The safe and exact execution of this exercise you need a training buddy or a trainer": "The safe and exact execution of this exercise you need a training buddy or a trainer", + "Execution at your own risk!": "Execution at your own risk!" } \ No newline at end of file diff --git a/i18n/hu.json b/i18n/hu.json index b2663bd..5508c82 100644 --- a/i18n/hu.json +++ b/i18n/hu.json @@ -434,7 +434,7 @@ "Do you want to restart, or select a new Training Plan?": "Újra akarod indítani, vagy inkább egy másikat választasz?", "New Training Plan": "Másik edzésterv", "Restart": "Újraindítom", - "Training Day": "Edzésnap", + "Training Day": "Nap", "No Active Training Plan": "Nincs aktív edzésterv", "Please select one in the Training menu, or create your custom plan": "Kérlek válassz egyet a Tréning menüben, vagy hozd létre a saját egyéni edzésedet", "Continue your training": "Folytasd az edzést", @@ -471,5 +471,8 @@ "A new version of Workout Test is available!": "Egy új Workout Test verzió elérhető!", "Update Now": "Töltsd le most", "Update App?": "App frissítés", - "Want to update?": "Kérlek töltsd le" + "Want to update?": "Kérlek töltsd le", + "Attention!": "Figyelem!", + "The safe and exact execution of this exercise you need a training buddy or a trainer": "A gyakorlat biztonságos és maximális pontosságú végrehajtásához edzőtárs vagy edző segítsége szükséges", + "Execution at your own risk!": "Végrehajtás CSAK saját felelőségre!" } \ No newline at end of file diff --git a/ios/Podfile.lock b/ios/Podfile.lock index d9f910d..c5112c1 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -4,7 +4,7 @@ PODS: - AppAuth/ExternalUserAgent (= 1.4.0) - AppAuth/Core (1.4.0) - AppAuth/ExternalUserAgent (1.4.0) - - apple_sign_in (0.0.1): + - awesome_notifications (0.0.2): - Flutter - device_info (0.0.1): - Flutter @@ -116,10 +116,10 @@ PODS: - FirebaseInstallations (~> 8.0) - GoogleUtilities/Environment (~> 7.4) - "GoogleUtilities/NSData+zlib (~> 7.4)" - - flurry (0.0.4): + - Flurry-iOS-SDK/FlurrySDK (11.2.1) + - flurry_data (0.0.1): - Flurry-iOS-SDK/FlurrySDK - Flutter - - Flurry-iOS-SDK/FlurrySDK (11.2.1) - Flutter (1.0.0) - flutter_app_badger (0.0.1): - Flutter @@ -127,8 +127,6 @@ PODS: - FBSDKCoreKit (~> 9.1.0) - FBSDKLoginKit (~> 9.1.0) - Flutter - - flutter_local_notifications (0.0.1): - - Flutter - flutter_secure_storage (3.3.1): - Flutter - flutter_uxcam (2.0.0-beta.1): @@ -215,6 +213,8 @@ PODS: - Sentry (~> 7.0.3) - shared_preferences (0.0.1): - Flutter + - sign_in_with_apple (0.0.1): + - Flutter - sqflite (0.0.2): - Flutter - FMDB (>= 2.7.5) @@ -229,7 +229,7 @@ PODS: - Flutter DEPENDENCIES: - - apple_sign_in (from `.symlinks/plugins/apple_sign_in/ios`) + - awesome_notifications (from `.symlinks/plugins/awesome_notifications/ios`) - device_info (from `.symlinks/plugins/device_info/ios`) - devicelocale (from `.symlinks/plugins/devicelocale/ios`) - firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`) @@ -237,11 +237,10 @@ DEPENDENCIES: - firebase_core (from `.symlinks/plugins/firebase_core/ios`) - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) - firebase_remote_config (from `.symlinks/plugins/firebase_remote_config/ios`) - - flurry (from `.symlinks/plugins/flurry/ios`) + - flurry_data (from `.symlinks/plugins/flurry_data/ios`) - Flutter (from `Flutter`) - flutter_app_badger (from `.symlinks/plugins/flutter_app_badger/ios`) - flutter_facebook_auth (from `.symlinks/plugins/flutter_facebook_auth/ios`) - - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - flutter_uxcam (from `.symlinks/plugins/flutter_uxcam/ios`) - google_sign_in (from `.symlinks/plugins/google_sign_in/ios`) @@ -252,6 +251,7 @@ DEPENDENCIES: - purchases_flutter (from `.symlinks/plugins/purchases_flutter/ios`) - sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`) - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) + - sign_in_with_apple (from `.symlinks/plugins/sign_in_with_apple/ios`) - sqflite (from `.symlinks/plugins/sqflite/ios`) - url_launcher (from `.symlinks/plugins/url_launcher/ios`) - video_player (from `.symlinks/plugins/video_player/ios`) @@ -289,8 +289,8 @@ SPEC REPOS: - UXCam EXTERNAL SOURCES: - apple_sign_in: - :path: ".symlinks/plugins/apple_sign_in/ios" + awesome_notifications: + :path: ".symlinks/plugins/awesome_notifications/ios" device_info: :path: ".symlinks/plugins/device_info/ios" devicelocale: @@ -305,16 +305,14 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/firebase_messaging/ios" firebase_remote_config: :path: ".symlinks/plugins/firebase_remote_config/ios" - flurry: - :path: ".symlinks/plugins/flurry/ios" + flurry_data: + :path: ".symlinks/plugins/flurry_data/ios" Flutter: :path: Flutter flutter_app_badger: :path: ".symlinks/plugins/flutter_app_badger/ios" flutter_facebook_auth: :path: ".symlinks/plugins/flutter_facebook_auth/ios" - flutter_local_notifications: - :path: ".symlinks/plugins/flutter_local_notifications/ios" flutter_secure_storage: :path: ".symlinks/plugins/flutter_secure_storage/ios" flutter_uxcam: @@ -335,6 +333,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/sentry_flutter/ios" shared_preferences: :path: ".symlinks/plugins/shared_preferences/ios" + sign_in_with_apple: + :path: ".symlinks/plugins/sign_in_with_apple/ios" sqflite: :path: ".symlinks/plugins/sqflite/ios" url_launcher: @@ -348,7 +348,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: AppAuth: 31bcec809a638d7bd2f86ea8a52bd45f6e81e7c7 - apple_sign_in: 7716c7ddfa195aeab7dec0dc374ef4ff45d1adb4 + awesome_notifications: 74462bc8e68b11f8235d78422266886759e9da61 device_info: d7d233b645a32c40dfdc212de5cf646ca482f175 devicelocale: b22617f40038496deffba44747101255cee005b0 FBSDKCoreKit: a00fe2efd780c195a5e09201bf51c56106245b40 @@ -367,12 +367,11 @@ SPEC CHECKSUMS: FirebaseInstallations: c4aab1005d6547b00a7529777fe52f5d4d45165b FirebaseMessaging: 1a33b4af3c8042ed6ddacb6c031894af2064bfab FirebaseRemoteConfig: 055f6b5ba1751547596ded5032c4d5c6054ca501 - flurry: 15b01f664ab1367c62b50291541ea7f78ca85aad Flurry-iOS-SDK: 5831da8fc6bedb31fa1f94aac6fd204d36dd351d + flurry_data: 49b7066a283aa41f4306974c1f2d74c61231ad74 Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c flutter_app_badger: 65de4d6f0c34a891df49e6cfb8a1c0496426fa68 flutter_facebook_auth: 4b170c07b7fce791497093fcc3f134fb215f3f07 - flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743 flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec flutter_uxcam: ab8e5d3954eb448febd581375e2622e9eecb1066 FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a @@ -396,6 +395,7 @@ SPEC CHECKSUMS: Sentry: 5b16f877da362d23716d827e04db642455b26b40 sentry_flutter: 602dc1902e152269256115e2386e1029511f3440 shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d + sign_in_with_apple: 34f3f5456a45fd7ac5fb42905e2ad31dae061b4a sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef UXCam: c2c00873595ab89be227f197213dc3679ff88ae5 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 47784aa..028c493 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 = 11; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = SFJJBDCU6Z; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -405,7 +405,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - MARKETING_VERSION = 1.1.18; + MARKETING_VERSION = 1.1.20; PRODUCT_BUNDLE_IDENTIFIER = com.aitrainer.app; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -531,7 +531,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 11; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = SFJJBDCU6Z; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -548,7 +548,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - MARKETING_VERSION = 1.1.18; + MARKETING_VERSION = 1.1.20; PRODUCT_BUNDLE_IDENTIFIER = com.aitrainer.app; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -566,7 +566,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 11; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = SFJJBDCU6Z; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -583,7 +583,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - MARKETING_VERSION = 1.1.18; + MARKETING_VERSION = 1.1.20; PRODUCT_BUNDLE_IDENTIFIER = com.aitrainer.app; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; diff --git a/lib/bloc/login/login_bloc.dart b/lib/bloc/login/login_bloc.dart index 53b078f..69ed7c0 100644 --- a/lib/bloc/login/login_bloc.dart +++ b/lib/bloc/login/login_bloc.dart @@ -87,9 +87,9 @@ class LoginBloc extends Bloc with Trans { yield LoginSuccess(); } else if (event is RegistrationSubmit) { yield LoginLoading(); - if (!this.dataPolicyAllowed) { + /* if (!this.dataPolicyAllowed) { throw Exception("Please accept our data policy"); - } + } */ final String? validationError = validate(); if (validationError != null) { yield LoginError(message: validationError); @@ -104,9 +104,9 @@ class LoginBloc extends Bloc with Trans { } } else if (event is RegistrationFB) { yield LoginLoading(); - if (!this.dataPolicyAllowed) { + /* if (!this.dataPolicyAllowed) { throw Exception("Please accept our data policy"); - } + } */ Cache().setLoginType(LoginType.fb); await userRepository.addUserFB(); accountBloc.add(AccountLogInFinished(customer: Cache().userLoggedIn!)); @@ -116,9 +116,9 @@ class LoginBloc extends Bloc with Trans { yield LoginSuccess(); } else if (event is RegistrationGoogle) { yield LoginLoading(); - if (!this.dataPolicyAllowed) { + /* if (!this.dataPolicyAllowed) { throw Exception("Please accept our data policy"); - } + } */ Cache().setLoginType(LoginType.google); await userRepository.addUserGoogle(); accountBloc.add(AccountLogInFinished(customer: Cache().userLoggedIn!)); @@ -128,9 +128,9 @@ class LoginBloc extends Bloc with Trans { yield LoginSuccess(); } else if (event is RegistrationApple) { yield LoginLoading(); - if (!this.dataPolicyAllowed) { + /* if (!this.dataPolicyAllowed) { throw Exception("Please accept our data policy"); - } + } */ Cache().setLoginType(LoginType.apple); await userRepository.addUserApple(); accountBloc.add(AccountLogInFinished(customer: Cache().userLoggedIn!)); diff --git a/lib/bloc/training_plan/training_plan_bloc.dart b/lib/bloc/training_plan/training_plan_bloc.dart index d6bb4cb..c60b6a3 100644 --- a/lib/bloc/training_plan/training_plan_bloc.dart +++ b/lib/bloc/training_plan/training_plan_bloc.dart @@ -58,15 +58,16 @@ class TrainingPlanBloc extends Bloc { await Cache().saveMyTrainingPlan(); yield TrainingPlanFinished(); } else if (event is TrainingPlanWeightChange) { - yield TrainingPlanLoading(); + yield TrainingPlanExerciseLoading(); event.detail.weight = event.weight; + yield TrainingPlanExerciseReady(); yield TrainingPlanReady(); } else if (event is TrainingPlanRepeatsChange) { - yield TrainingPlanLoading(); + yield TrainingPlanExerciseLoading(); event.detail.repeats = event.repeats; - + yield TrainingPlanExerciseReady(); yield TrainingPlanReady(); } else if (event is TrainingPlanSetChange) { yield TrainingPlanLoading(); @@ -91,10 +92,13 @@ class TrainingPlanBloc extends Bloc { } else if (event.detail.exercises.length >= 0) { event.detail.state = ExercisePlanDetailState.inProgress; } - - // recalculate the weight to the original planned repeats - if (event.detail.isTest && event.detail.exercises.length == 1) { - trainingPlanRepository.recalculateDetail(_myPlan!.trainingPlanId!, event.detail); + // recalculate the weight to the original planned repeats for the next details + if (exercise.unitQuantity != null && exercise.unitQuantity! > 0) { + for (var nextDetail in _myPlan!.details) { + if (nextDetail.exerciseTypeId == event.detail.exerciseTypeId && nextDetail.weight == -2) { + trainingPlanRepository.recalculateDetail(_myPlan!.trainingPlanId!, event.detail, nextDetail); + } + } } exercise.trainingPlanDetailsId = _myPlan!.trainingPlanId; @@ -230,28 +234,61 @@ class TrainingPlanBloc extends Bloc { dayNames.clear(); _myPlan!.days.clear(); String dayName = "."; + String previousDay = "."; _myPlan!.details.forEach((element) { if (element.day != null && element.day != dayName) { dayNames.add(element.day!); dayName = element.day!; + if (previousDay != ".") { + this.addExtraExerciseType("Stretching", previousDay); + } } if (_myPlan!.days[dayName] == null) { if (dayName == ".") { dayName = ""; } _myPlan!.days[dayName] = []; + + this.addExtraExerciseType("Warming Up", dayName); + previousDay = dayName; } _myPlan!.days[dayName]!.add(element); }); if (dayNames.length == 0) { - dayNames.add(""); - _myPlan!.days[""] = []; - _myPlan!.days[""]!.addAll(_myPlan!.details); + dayName = ""; + dayNames.add(dayName); + _myPlan!.days[dayName] = []; + _myPlan!.days[dayName]!.addAll(_myPlan!.details); } getActiveDayIndex(); } + void addExtraExerciseType(String name, String dayName) { + if (Cache().getExerciseTypes() == null) { + return; + } + for (var exerciseType in Cache().getExerciseTypes()!) { + if (exerciseType.name == name) { + CustomerTrainingPlanDetails detail = CustomerTrainingPlanDetails(); + detail.customerTrainingPlanDetailsId = 0; + detail.trainingPlanDetailsId = 0; + detail.exerciseTypeId = exerciseType.exerciseTypeId; + detail.repeats = 1; + detail.set = 1; + detail.day = ""; + detail.parallel = false; + detail.restingTime = 0; + detail.exerciseType = exerciseType; + if (_myPlan!.days[dayName] == null) { + _myPlan!.days[dayName] = []; + } + _myPlan!.days[dayName]!.add(detail); + break; + } + } + } + CustomerTrainingPlanDetails? getTrainingPlanDetail(int trainingPlanDetailsId) { CustomerTrainingPlanDetails? detail; if (_myPlan == null || _myPlan!.details.isEmpty) { @@ -297,13 +334,14 @@ class TrainingPlanBloc extends Bloc { int minStep = 99; for (final detail in this._myPlan!.details) { if (!detail.state.equalsTo(ExercisePlanDetailState.finished)) { - if (detail.exercises.isEmpty && !detail.state.equalsTo(ExercisePlanDetailState.skipped)) { + final day = dayNames[this.activeDayIndex]; + if (detail.exercises.isEmpty && !detail.state.equalsTo(ExercisePlanDetailState.skipped) && day == detail.day) { next = detail; minStep = 1; break; } else { final int step = detail.exercises.length; - if (step < minStep && !detail.state.equalsTo(ExercisePlanDetailState.skipped)) { + if (step < minStep && !detail.state.equalsTo(ExercisePlanDetailState.skipped) && day == detail.day) { next = detail; minStep = step; if (detail.parallel != true) { @@ -313,7 +351,7 @@ class TrainingPlanBloc extends Bloc { } } } - print("Next detail $next"); + //print("Next detail $next"); return next; } @@ -379,20 +417,19 @@ class TrainingPlanBloc extends Bloc { } int getActiveDayIndex() { + activeDayIndex = 0; if (restarting) { return 0; } if (_myPlan == null || _myPlan!.details.isEmpty) { -// throw Exception("No defined Training Plan"); return 0; } if (dayNames.isEmpty || dayNames.length == 1) { + activeDayIndex = 0; return 0; } - activeDayIndex = 0; - for (var day in dayNames) { if (_myPlan!.days[day] == null) { throw Exception("Wrong activated day: $day does not exist"); @@ -413,7 +450,7 @@ class TrainingPlanBloc extends Bloc { activeDayIndex = 0; this.add(TrainingPlanGoToRestart()); } - + print("ActiveDayIndex $activeDayIndex"); return activeDayIndex; } diff --git a/lib/bloc/training_plan/training_plan_state.dart b/lib/bloc/training_plan/training_plan_state.dart index 4479945..d9d2c60 100644 --- a/lib/bloc/training_plan/training_plan_state.dart +++ b/lib/bloc/training_plan/training_plan_state.dart @@ -15,10 +15,18 @@ class TrainingPlanLoading extends TrainingPlanState { const TrainingPlanLoading(); } +class TrainingPlanExerciseLoading extends TrainingPlanState { + const TrainingPlanExerciseLoading(); +} + class TrainingPlanReady extends TrainingPlanState { const TrainingPlanReady(); } +class TrainingPlanExerciseReady extends TrainingPlanState { + const TrainingPlanExerciseReady(); +} + class TrainingPlanFinished extends TrainingPlanState { const TrainingPlanFinished(); } diff --git a/lib/main.dart b/lib/main.dart index b7c2d06..bf39926 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,7 +3,6 @@ import 'dart:io'; import 'package:aitrainer_app/bloc/test_set_execute/test_set_execute_bloc.dart'; import 'package:aitrainer_app/bloc/training_plan/training_plan_bloc.dart'; import 'package:aitrainer_app/bloc/tutorial/tutorial_bloc.dart'; -import 'package:aitrainer_app/push_notifications.dart'; import 'package:aitrainer_app/repository/customer_repository.dart'; import 'package:aitrainer_app/repository/training_plan_repository.dart'; import 'package:aitrainer_app/repository/workout_tree_repository.dart'; @@ -44,7 +43,7 @@ import 'package:aitrainer_app/widgets/home.dart'; import 'package:aitrainer_app/library/facebook_app_events/facebook_app_events.dart'; import 'package:firebase_analytics/firebase_analytics.dart'; import 'package:firebase_analytics/observer.dart'; -import 'package:flurry/flurry.dart'; +import 'package:flurry_data/flurry_data.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; @@ -110,6 +109,7 @@ Future main() async { if (isInDebugMode) { // In development mode simply print to console. FlutterError.dumpErrorToConsole(details); + FlurryData.logEvent("enter_test"); } else { // In production mode report to the application zone to report to // Sentry. @@ -192,10 +192,9 @@ Future main() async { Future initThirdParty() async { if (!isInDebugMode) { - await Flurry.initialize(androidKey: "JNYCTCWBT34FM3J8TV36", iosKey: "3QBG7BSMGPDH24S8TRQP", enableLog: true); + await FlurryData.initialize(androidKey: "JNYCTCWBT34FM3J8TV36", iosKey: "3QBG7BSMGPDH24S8TRQP", enableLog: true); FlutterUxcam.optIntoSchematicRecordings(); } - PushNotificationsManager().init(); } class WorkoutTestApp extends StatelessWidget { diff --git a/lib/model/cache.dart b/lib/model/cache.dart index cdbbe16..c72ff3e 100644 --- a/lib/model/cache.dart +++ b/lib/model/cache.dart @@ -19,6 +19,7 @@ import 'package:aitrainer_app/model/purchase.dart'; import 'package:aitrainer_app/model/split_test.dart'; import 'package:aitrainer_app/model/sport.dart'; import 'package:aitrainer_app/model/training_plan.dart'; +import 'package:aitrainer_app/model/training_plan_day.dart'; import 'package:aitrainer_app/model/tutorial.dart'; import 'package:aitrainer_app/model/workout_menu_tree.dart'; import 'package:aitrainer_app/repository/customer_repository.dart'; @@ -30,7 +31,7 @@ import 'package:aitrainer_app/util/enums.dart'; import 'package:aitrainer_app/util/env.dart'; import 'package:aitrainer_app/util/track.dart'; import 'package:firebase_remote_config/firebase_remote_config.dart'; -import 'package:flurry/flurry.dart'; +import 'package:flurry_data/flurry_data.dart'; import 'package:flutter_facebook_auth/flutter_facebook_auth.dart'; import 'package:flutter_uxcam/flutter_uxcam.dart'; import 'package:package_info/package_info.dart'; @@ -137,6 +138,7 @@ class Cache with Logging { List? _products; List _purchases = []; List _splitTests = []; + List _trainingPlanDays = []; List _exercisePlanTemplates = []; @@ -184,6 +186,7 @@ class Cache with Logging { String testEnv = EnvironmentConfig.test_env; this.testEnvironment = testEnv; if (testEnv == "1") { + print("testEnv $testEnv"); baseUrl = baseUrlTest; liveServer = false; } @@ -668,7 +671,7 @@ class Cache with Logging { await PackageApi().getCustomerPackage(customerId); if (!isInDebugMode) { - Flurry.setUserId(customerId.toString()); + FlurryData.setUserId(customerId.toString()); //Smartlook.setUserIdentifier(customerId.toString()); FlutterUxcam.setUserProperty("username", customerId.toString()); FlutterUxcam.setUserIdentity(customerId.toString()); @@ -737,4 +740,7 @@ class Cache with Logging { List getSplitTests() => this._splitTests; setSplitTests(value) => this._splitTests = value; + + List getTrainingPlanDays() => this._trainingPlanDays; + setTrainingPlanDays(value) => this._trainingPlanDays = value; } diff --git a/lib/model/customer_property.dart b/lib/model/customer_property.dart index ca246e6..52ee974 100644 --- a/lib/model/customer_property.dart +++ b/lib/model/customer_property.dart @@ -4,7 +4,7 @@ class CustomerProperty { int? customerPropertyId; late int propertyId; late int customerId; - late DateTime dateAdd; + DateTime? dateAdd; late double propertyValue; bool newData = false; @@ -14,7 +14,7 @@ class CustomerProperty { this.customerPropertyId = json['customerPropertyId']; this.propertyId = json['propertyId']; this.customerId = json['customerId']; - this.dateAdd = json['propertyName']; + this.dateAdd = json['dataAdd'] ?? DateTime.now(); this.propertyValue = json['propertyValue']; } @@ -24,14 +24,14 @@ class CustomerProperty { "customerPropertyId": this.customerPropertyId, "propertyId": this.propertyId, "customerId": this.customerId, - "dateAdd": DateFormat('yyyy-MM-dd HH:mm:ss').format(this.dateAdd), + "dateAdd": DateFormat('yyyy-MM-dd HH:mm:ss').format(this.dateAdd!), "propertyValue": this.propertyValue }; } else { return { "propertyId": this.propertyId, "customerId": this.customerId, - "dateAdd": DateFormat('yyyy-MM-dd HH:mm:ss').format(this.dateAdd), + "dateAdd": DateFormat('yyyy-MM-dd HH:mm:ss').format(this.dateAdd!), "propertyValue": this.propertyValue }; } diff --git a/lib/model/customer_training_plan_details.dart b/lib/model/customer_training_plan_details.dart index 21f993e..c0b1828 100644 --- a/lib/model/customer_training_plan_details.dart +++ b/lib/model/customer_training_plan_details.dart @@ -2,6 +2,7 @@ import 'package:aitrainer_app/model/cache.dart'; import 'package:aitrainer_app/model/exercise.dart'; import 'package:aitrainer_app/model/exercise_plan_detail.dart'; import 'package:aitrainer_app/model/exercise_type.dart'; +import 'package:aitrainer_app/repository/training_plan_day_repository.dart'; class CustomerTrainingPlanDetails { /// customerTrainingPlanDetails @@ -26,6 +27,8 @@ class CustomerTrainingPlanDetails { bool? parallel; String? day; + int? dayId; + /// exerciseType ExerciseType? exerciseType; @@ -63,10 +66,10 @@ class CustomerTrainingPlanDetails { : json['parallel'] == "true" ? true : null; - this.day = json['day'].toString(); - if (this.day == null || this.day == "null") { - this.day = ""; - } + this.dayId = json['dayId'] == "null" ? null : json['dayId']; + TrainingPlanDayRepository trainingPlanDayRepository = TrainingPlanDayRepository(); + this.day = trainingPlanDayRepository.getNameById(this.dayId); + try { Iterable iterable = json['exercises']; this.exercises = iterable.map((exercise) => Exercise.fromJson(exercise)).toList(); @@ -114,15 +117,30 @@ class CustomerTrainingPlanDetails { 'exercises': exercises.isEmpty ? [].toString() : exercises.map((exercise) => exercise.toJson()).toList().toString(), 'state': this.state.toStr(), "isTest": this.isTest, + "dayId": this.dayId, }; - if (this.day != null && this.day!.isNotEmpty) { - jsonMap["day"] = this.day; - } - //print("Detail $jsonMap"); + //print("Detail toJson $jsonMap"); return jsonMap; } @override String toString() => this.toJsonWithExercises().toString(); + + void copy(CustomerTrainingPlanDetails from) { + this.customerTrainingPlanDetailsId = from.customerTrainingPlanDetailsId; + this.trainingPlanDetailsId = from.trainingPlanDetailsId; + this.exerciseTypeId = from.exerciseTypeId; + this.exerciseType = from.exerciseType; + this.set = from.set; + this.repeats = from.repeats; + this.weight = from.weight; + this.restingTime = from.restingTime; + this.parallel = from.parallel; + this.exercises = from.exercises; + this.state = from.state; + this.isTest = from.isTest; + this.day = from.day; + this.dayId = from.dayId; + } } diff --git a/lib/model/evaluation.dart b/lib/model/evaluation.dart index b4e4bc9..ce4cfa9 100644 --- a/lib/model/evaluation.dart +++ b/lib/model/evaluation.dart @@ -1,10 +1,10 @@ import 'package:aitrainer_app/model/evaluation_attribute.dart'; class Evaluation { - late int evaluationId; + int? evaluationId; late String name; - late int exerciseTypeId; - late String unit; + int? exerciseTypeId; + String? unit; late List attributes; Evaluation.fromJson(Map json) { diff --git a/lib/model/evaluation_attribute.dart b/lib/model/evaluation_attribute.dart index 31250aa..3d4caff 100644 --- a/lib/model/evaluation_attribute.dart +++ b/lib/model/evaluation_attribute.dart @@ -1,6 +1,6 @@ class EvaluationAttribute { late int evaluationAttrId; - late int evaluationId; + int? evaluationId; late String name; late String sex; late int ageMin; diff --git a/lib/model/exercise_type.dart b/lib/model/exercise_type.dart index 3e5480e..c7ae7e1 100644 --- a/lib/model/exercise_type.dart +++ b/lib/model/exercise_type.dart @@ -17,7 +17,7 @@ class ExerciseType { late String unit; /// unitQuantity - late String unitQuantity; + String? unitQuantity; /// unitQuantityUnit String? unitQuantityUnit; @@ -28,6 +28,8 @@ class ExerciseType { /// base late bool base; + late bool buddyWarning; + /// imageUrl String imageUrl = ""; @@ -65,6 +67,7 @@ class ExerciseType { this.unitQuantityUnit = json['unitQuantityUnit']; this.active = json['active']; this.base = json['base']; + this.buddyWarning = json['buddyWarning']; if (json['images'].length > 0) { this.imageUrl = json['images'][0]['url']; } @@ -105,6 +108,8 @@ class ExerciseType { "unitQuantity": unitQuantity, "unitQuantityUnit": unitQuantityUnit, "active": active, + "base": base, + "buddyWarning": buddyWarning, "devices": this.devices.toString(), "nameTranslation": this.nameTranslation, "parents": this.parents.toString() diff --git a/lib/model/training_plan.dart b/lib/model/training_plan.dart index 223cc1b..9326e5b 100644 --- a/lib/model/training_plan.dart +++ b/lib/model/training_plan.dart @@ -4,10 +4,10 @@ import 'package:aitrainer_app/model/training_plan_detail.dart'; class TrainingPlan { late int trainingPlanId; - late String type; + String? type; late String name; - late String internalName; - late String description; + String? internalName; + String? description; late bool free; late bool active; int? treeId; @@ -28,7 +28,7 @@ class TrainingPlan { this.treeId = json['treeId']; nameTranslations['en'] = name; - descriptionTranslations['en'] = description; + descriptionTranslations['en'] = description ?? ""; if (json['translations'] != null && json['translations'].length > 0) { json['translations'].forEach((translation) { nameTranslations[translation['languageCode']] = translation['nameTranslation']; diff --git a/lib/model/training_plan_day.dart b/lib/model/training_plan_day.dart new file mode 100644 index 0000000..8286eb7 --- /dev/null +++ b/lib/model/training_plan_day.dart @@ -0,0 +1,29 @@ +import 'dart:collection'; + +class TrainingPlanDay { + late int dayId; + late String name; + + HashMap nameTranslations = HashMap(); + + TrainingPlanDay.fromJson(Map json) { + this.dayId = json['dayId']; + this.name = json['name']; + + nameTranslations['en'] = name; + if (json['translations'] != null && json['translations'].length > 0) { + json['translations'].forEach((translation) { + nameTranslations[translation['languageCode']] = translation['nameTranslation']; + }); + } + } + + Map toJson() => { + "dayId": this.dayId, + "name": this.name, + "nameTranslation": this.nameTranslations.toString(), + }; + + @override + String toString() => this.toJson().toString(); +} diff --git a/lib/model/training_plan_detail.dart b/lib/model/training_plan_detail.dart index d97a24e..bc299d8 100644 --- a/lib/model/training_plan_detail.dart +++ b/lib/model/training_plan_detail.dart @@ -1,14 +1,15 @@ class TrainingPlanDetail { late int trainingPlanDetailId; - late int trainingPlanId; + int? trainingPlanId; late int exerciseTypeId; late int sort; late int set; - late int repeats; - late double weight; - late int restingTime; - late bool parallel; - late String day; + int? repeats; + double? weight; + int? restingTime; + bool? parallel; + int? dayId; + String? day; TrainingPlanDetail.fromJson(Map json) { this.trainingPlanDetailId = json['trainingPlanDetailId']; @@ -20,7 +21,7 @@ class TrainingPlanDetail { this.weight = json['weight']; this.restingTime = json['restingTime']; this.parallel = json['parallel']; - this.day = json['day']; + this.dayId = json['dayId']; } Map toJson() => { @@ -32,6 +33,7 @@ class TrainingPlanDetail { "weight": this.weight, "restingTime": this.restingTime, "parallel": this.parallel, + "dayId": this.dayId, "day": this.day, }; diff --git a/lib/push_notifications.dart b/lib/push_notifications.dart deleted file mode 100644 index 882ea3d..0000000 --- a/lib/push_notifications.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:aitrainer_app/service/logging.dart'; -import 'package:firebase_messaging/firebase_messaging.dart'; -import 'package:flutter_local_notifications/flutter_local_notifications.dart'; - -class PushNotificationsManager with Logging { - PushNotificationsManager._(); - - factory PushNotificationsManager() => _instance; - - static final PushNotificationsManager _instance = PushNotificationsManager._(); - - Future init() async { - log(" --- Firebase Messagein init.."); - const AndroidNotificationChannel channel = AndroidNotificationChannel( - 'high_importance_channel', // id - 'High Importance Notifications', // title - 'This channel is used for important notifications.', // description - importance: Importance.max, - ); - final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); - - await flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation() - ?.createNotificationChannel(channel); - - await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions( - alert: true, // Required to display a heads up notification - badge: true, - sound: true, - ); - String? token = await FirebaseMessaging.instance.getToken(); - log("FirebaseMessaging token $token"); - } -} diff --git a/lib/repository/training_plan_day_repository.dart b/lib/repository/training_plan_day_repository.dart new file mode 100644 index 0000000..14e5443 --- /dev/null +++ b/lib/repository/training_plan_day_repository.dart @@ -0,0 +1,35 @@ +import 'package:aitrainer_app/model/cache.dart'; +import 'package:aitrainer_app/model/training_plan.dart'; +import 'package:aitrainer_app/util/app_language.dart'; + +class TrainingPlanDayRepository { + const TrainingPlanDayRepository(); + + void assignTrainingPlanDays() { + List? plans = Cache().getTrainingPlans(); + if (plans == null) { + return; + } + plans.forEach((plan) { + if (plan.details != null) { + plan.details!.forEach((element) { + element.day = this.getNameById(element.dayId); + }); + } + }); + } + + String? getNameById(int? dayId) { + if (dayId == null) { + return ""; + } + String? name; + for (var day in Cache().getTrainingPlanDays()) { + if (day.dayId == dayId) { + name = day.nameTranslations[AppLanguage().appLocal.languageCode]; + break; + } + } + return name; + } +} diff --git a/lib/repository/training_plan_repository.dart b/lib/repository/training_plan_repository.dart index 819674c..84ad968 100644 --- a/lib/repository/training_plan_repository.dart +++ b/lib/repository/training_plan_repository.dart @@ -5,6 +5,7 @@ import 'package:aitrainer_app/model/exercise.dart'; import 'package:aitrainer_app/model/exercise_plan_detail.dart'; import 'package:aitrainer_app/model/exercise_tree.dart'; import 'package:aitrainer_app/model/training_plan.dart'; +import 'package:aitrainer_app/repository/training_plan_day_repository.dart'; import 'package:aitrainer_app/service/training_plan_service.dart'; import 'package:aitrainer_app/util/common.dart'; @@ -66,6 +67,7 @@ class TrainingPlanRepository { // 3 calculate weights int index = 0; + trainingPlan.details!.forEach((elem) { CustomerTrainingPlanDetails detail = CustomerTrainingPlanDetails(); detail.customerTrainingPlanDetailsId = ++index; @@ -73,7 +75,9 @@ class TrainingPlanRepository { detail.exerciseTypeId = elem.exerciseTypeId; detail.repeats = elem.repeats; detail.set = elem.set; - detail.day = elem.day; + detail.dayId = elem.dayId; + TrainingPlanDayRepository trainingPlanDayRepository = TrainingPlanDayRepository(); + detail.day = trainingPlanDayRepository.getNameById(elem.dayId); detail.parallel = elem.parallel; detail.restingTime = elem.restingTime; detail.exerciseType = Cache().getExerciseTypeById(detail.exerciseTypeId!); @@ -83,6 +87,13 @@ class TrainingPlanRepository { } else { detail.weight = 0; } + } else if (elem.weight == -2) { + final CustomerTrainingPlanDetails calculated = this.isWeightCalculatedByExerciseType(elem.exerciseTypeId, detail, plan); + if (calculated.weight != -1) { + detail.weight = calculated.weight; + } else { + detail.weight = -2; + } } else { detail.weight = elem.weight; } @@ -98,6 +109,19 @@ class TrainingPlanRepository { return plan; } + CustomerTrainingPlanDetails isWeightCalculatedByExerciseType( + int exerciseTypeId, CustomerTrainingPlanDetails detail, CustomerTrainingPlan plan) { + CustomerTrainingPlanDetails calculated = detail; + for (var element in plan.details) { + if (element.exerciseTypeId == exerciseTypeId) { + calculated = element; + break; + } + } + + return calculated; + } + TrainingPlan? getTrainingPlanById(int trainingPlanId) { TrainingPlan? plan; if (Cache().getTrainingPlans() == null) { @@ -148,8 +172,9 @@ class TrainingPlanRepository { return detail; } - CustomerTrainingPlanDetails recalculateDetail(int trainingPlanId, CustomerTrainingPlanDetails detail) { - CustomerTrainingPlanDetails recalculatedDetail = detail; + CustomerTrainingPlanDetails recalculateDetail( + int trainingPlanId, CustomerTrainingPlanDetails detail, CustomerTrainingPlanDetails nextDetail) { + CustomerTrainingPlanDetails recalculatedDetail = nextDetail; // 1. get original repeats @@ -164,14 +189,14 @@ class TrainingPlanRepository { plan.details!.forEach((element) { if (element.trainingPlanDetailId == detail.trainingPlanDetailsId) { print("element $element"); - originalRepeats = element.repeats; + originalRepeats = element.repeats ?? 0; } }); // 2 get recalculated repeats - recalculatedDetail.weight = Common.calculateWeigthByChangedQuantity(detail.weight!, detail.repeats!.toDouble(), originalRepeats.toDouble()); + recalculatedDetail.weight = Common.roundWeight(recalculatedDetail.weight!); print("recalculated repeats for $originalRepeats: ${recalculatedDetail.weight}"); recalculatedDetail.repeats = originalRepeats; diff --git a/lib/repository/workout_tree_repository.dart b/lib/repository/workout_tree_repository.dart index 27076d9..d7e43b7 100644 --- a/lib/repository/workout_tree_repository.dart +++ b/lib/repository/workout_tree_repository.dart @@ -112,10 +112,10 @@ class WorkoutTreeRepository with Logging { 0, ""); this.tree[exerciseType.name] = menuItem; - if (isRunning || is1RM) { + if (isRunning || is1RM || exerciseType.name == "Warming Up" || exerciseType.name == "Stretching") { menuAsExercise.add(menuItem); } - //log("ExerciseType in Menu item ${exerciseType.toJson()} is1RM: $is1RM"); + //log("ExerciseType in Menu item ${exerciseType.toJson()}"); }); } else { //log("No Parents " + exerciseType.toJson().toString()); diff --git a/lib/service/firebase_api.dart b/lib/service/firebase_api.dart index 0ae3f0b..7369aef 100644 --- a/lib/service/firebase_api.dart +++ b/lib/service/firebase_api.dart @@ -1,15 +1,20 @@ +import 'dart:math' as math; +import 'dart:convert'; +import 'package:crypto/crypto.dart'; import 'package:aitrainer_app/model/cache.dart'; import 'package:aitrainer_app/service/logging.dart' as logging; -import 'package:apple_sign_in/apple_sign_in.dart'; +import 'package:sign_in_with_apple/sign_in_with_apple.dart'; +import 'package:awesome_notifications/awesome_notifications.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:firebase_remote_config/firebase_remote_config.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_facebook_auth/flutter_facebook_auth.dart'; import 'package:google_sign_in/google_sign_in.dart'; class FirebaseApi with logging.Logging { bool appleSignInAvailable = false; - //late FirebaseApi _instance; static final FirebaseAuth auth = FirebaseAuth.instance; @@ -29,15 +34,69 @@ class FirebaseApi with logging.Logging { Future initializeFlutterFire() async { try { // Wait for Firebase to initialize and set `_initialized` state to true - FirebaseApp app = await Firebase.initializeApp(); + await Firebase.initializeApp(); - this.appleSignInAvailable = await AppleSignIn.isAvailable(); + this.appleSignInAvailable = await SignInWithApple.isAvailable(); + AwesomeNotifications().initialize( + // set the icon to null if you want to use the default app icon + null, + [ + NotificationChannel( + channelKey: 'basic_channel', + channelName: 'Basic notifications', + channelDescription: 'Notification channel for basic tests', + defaultColor: Color(0xFF9D50DD), + ledColor: Colors.white) + ]); + + AwesomeNotifications().isNotificationAllowed().then((isAllowed) { + if (!isAllowed) { + // Insert here your friendly dialog box before call the request method + // This is very important to not harm the user experience + AwesomeNotifications().requestPermissionToSendNotifications(); + } + }); + + await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions( + alert: true, // Required to display a heads up notification + badge: true, + sound: true, + ); + String? token = await FirebaseMessaging.instance.getToken(); + FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); + log("FirebaseMessaging token $token"); } catch (e) { // Set `_error` state to true if Firebase initialization fails log("Error initializing Firebase"); } } + Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { + print('Handling a background message: ${message.messageId}'); + + if (!StringUtils.isNullOrEmpty(message.notification?.title, considerWhiteSpaceAsEmpty: true) || + !StringUtils.isNullOrEmpty(message.notification?.body, considerWhiteSpaceAsEmpty: true)) { + print('message also contained a notification: ${message.notification}'); + + String? imageUrl; + imageUrl ??= message.notification!.android?.imageUrl; + imageUrl ??= message.notification!.apple?.imageUrl; + + Map notificationAdapter = { + NOTIFICATION_CHANNEL_KEY: 'basic_channel', + NOTIFICATION_ID: message.data[NOTIFICATION_CONTENT]?[NOTIFICATION_ID] ?? message.messageId ?? math.Random().nextInt(2147483647), + NOTIFICATION_TITLE: message.data[NOTIFICATION_CONTENT]?[NOTIFICATION_TITLE] ?? message.notification?.title, + NOTIFICATION_BODY: message.data[NOTIFICATION_CONTENT]?[NOTIFICATION_BODY] ?? message.notification?.body, + NOTIFICATION_LAYOUT: StringUtils.isNullOrEmpty(imageUrl) ? 'Default' : 'BigPicture', + NOTIFICATION_BIG_PICTURE: imageUrl + }; + + AwesomeNotifications().createNotificationFromJsonData(notificationAdapter); + } else { + AwesomeNotifications().createNotificationFromJsonData(message.data); + } + } + Future signInEmail(String? email, String? password) async { if (email == null) { throw Exception("Please type an email address"); @@ -85,20 +144,33 @@ class FirebaseApi with logging.Logging { return rc; } + String generateNonce([int length = 32]) { + final charset = '0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._'; + final random = math.Random.secure(); + return List.generate(length, (_) => charset[random.nextInt(charset.length)]).join(); + } + + /// Returns the sha256 hash of [input] in hex notation. + String sha256ofString(String input) { + final bytes = utf8.encode(input); + final digest = sha256.convert(bytes); + return digest.toString(); + } + Future> signInWithApple() async { Map userData = Map(); - final AuthorizationResult result = await AppleSignIn.performRequests([ - AppleIdRequest(requestedScopes: [Scope.email, Scope.fullName]) + /* final apple.AuthorizationResult result = await SignInWithApple.performRequests([ + apple.AppleIdRequest(requestedScopes: [apple.Scope.email, apple.Scope.fullName]) ]); switch (result.status) { - case AuthorizationStatus.authorized: + case apple.AuthorizationStatus.authorized: print('User authorized'); break; - case AuthorizationStatus.error: + case apple.AuthorizationStatus.error: print('User error'); throw Exception("Apple Sign-In failed"); - case AuthorizationStatus.cancelled: + case apple.AuthorizationStatus.cancelled: print('User cancelled'); throw Exception("Apple Sign-In cancelled"); } @@ -106,20 +178,36 @@ class FirebaseApi with logging.Logging { // Create an `OAuthCredential` from the credential returned by Apple. final oauthCredential = OAuthProvider("apple.com").credential( idToken: String.fromCharCodes(result.credential.identityToken), - accessToken: String.fromCharCodes(result.credential.authorizationCode)); + accessToken: String.fromCharCodes(result.credential.authorizationCode!)); + */ + // To prevent replay attacks with the credential returned from Apple, we + // include a nonce in the credential request. When signing in with + // Firebase, the nonce in the id token returned by Apple, is expected to + // match the sha256 hash of `rawNonce`. + final rawNonce = generateNonce(); + final nonce = sha256ofString(rawNonce); + // Request credential for the currently signed in Apple account. + final appleCredential = await SignInWithApple.getAppleIDCredential( + scopes: [ + AppleIDAuthorizationScopes.email, + AppleIDAuthorizationScopes.fullName, + ], + nonce: nonce, + ); + + // Create an `OAuthCredential` from the credential returned by Apple. + final oauthCredential = OAuthProvider("apple.com").credential( + idToken: appleCredential.identityToken, + rawNonce: rawNonce, + ); // Sign in the user with Firebase. If the nonce we generated earlier does // not match the nonce in `appleCredential.identityToken`, sign in will fail. UserCredential userCredential = await FirebaseAuth.instance.signInWithCredential(oauthCredential); Cache().firebaseUid = userCredential.user!.uid; log("userCredential: " + userCredential.toString()); - log("Apple Credentials: " + - result.credential.user.toString() + - " state " + - result.credential.state.toString() + - " email " + - userCredential.user!.email!); + log("Apple Credentials: ${appleCredential.userIdentifier} state ${appleCredential.state} email ${userCredential.user!.email!}"); userData['email'] = userCredential.user!.email; return userData; @@ -127,26 +215,43 @@ class FirebaseApi with logging.Logging { Future> registerWithApple() async { Map userData = Map(); - final AuthorizationResult result = await AppleSignIn.performRequests([ - AppleIdRequest(requestedScopes: [Scope.email, Scope.fullName]) + /* final apple.AuthorizationResult result = await apple.TheAppleSignIn.performRequests([ + apple.AppleIdRequest(requestedScopes: [apple.Scope.email, apple.Scope.fullName]) ]); switch (result.status) { - case AuthorizationStatus.authorized: + case apple.AuthorizationStatus.authorized: print('Apple User authorized'); break; - case AuthorizationStatus.error: + case apple.AuthorizationStatus.error: print('Apple User error'); throw Exception("Apple Sign-In failed"); - case AuthorizationStatus.cancelled: + case apple.AuthorizationStatus.cancelled: print('User cancelled'); throw Exception("Apple Sign-In cancelled"); } + */ + final rawNonce = generateNonce(); + final nonce = sha256ofString(rawNonce); - // Create an `OAuthCredential` from the credential returned by Apple. + // Request credential for the currently signed in Apple account. + final appleCredential = await SignInWithApple.getAppleIDCredential( + scopes: [ + AppleIDAuthorizationScopes.email, + AppleIDAuthorizationScopes.fullName, + ], + nonce: nonce, + ); + + final oauthCredential = OAuthProvider("apple.com").credential( + idToken: appleCredential.identityToken, + rawNonce: rawNonce, + ); + + /* // Create an `OAuthCredential` from the credential returned by Apple. final oauthCredential = OAuthProvider("apple.com").credential( idToken: String.fromCharCodes(result.credential.identityToken), accessToken: String.fromCharCodes(result.credential.authorizationCode)); - + */ // Sign in the user with Firebase. If the nonce we generated earlier does // not match the nonce in `appleCredential.identityToken`, sign in will fail. UserCredential userCredential = await FirebaseAuth.instance.signInWithCredential(oauthCredential); diff --git a/lib/service/package_service.dart b/lib/service/package_service.dart index 73d8a2f..32e0477 100644 --- a/lib/service/package_service.dart +++ b/lib/service/package_service.dart @@ -19,7 +19,9 @@ import 'package:aitrainer_app/model/property.dart'; import 'package:aitrainer_app/model/purchase.dart'; import 'package:aitrainer_app/model/split_test.dart'; import 'package:aitrainer_app/model/training_plan.dart'; +import 'package:aitrainer_app/model/training_plan_day.dart'; import 'package:aitrainer_app/model/tutorial.dart'; +import 'package:aitrainer_app/repository/training_plan_day_repository.dart'; import 'package:aitrainer_app/service/api.dart'; import 'package:aitrainer_app/service/exercise_type_service.dart'; import 'package:aitrainer_app/util/not_found_exception.dart'; @@ -105,6 +107,10 @@ class PackageApi { final List? tests = json.map((test) => SplitTest.fromJson(test)).toList(); //print("A/B tests: $tests"); Cache().setSplitTests(tests); + } else if (headRecord[0] == "TrainingPlanDay") { + final Iterable json = jsonDecode(headRecord[1]); + final List? days = json.map((day) => TrainingPlanDay.fromJson(day)).toList(); + Cache().setTrainingPlanDays(days); } }); @@ -114,9 +120,11 @@ class PackageApi { ExerciseTree tree = element as ExerciseTree; tree.imageUrl = await ExerciseTreeApi().buildImage(tree.imageUrl, tree.treeId); }); - //print("tree: $exerciseTree"); Cache().setExerciseTree(exerciseTree); + TrainingPlanDayRepository trainingPlanDayRepository = TrainingPlanDayRepository(); + trainingPlanDayRepository.assignTrainingPlanDays(); + return; } diff --git a/lib/util/track.dart b/lib/util/track.dart index 3bf721a..76c5993 100644 --- a/lib/util/track.dart +++ b/lib/util/track.dart @@ -5,7 +5,7 @@ import 'package:aitrainer_app/service/tracking_service.dart'; import 'package:aitrainer_app/util/enums.dart'; import 'package:aitrainer_app/model/tracking.dart' as model; import 'package:firebase_analytics/firebase_analytics.dart'; -import 'package:flurry/flurry.dart'; +import 'package:flurry_data/flurry_data.dart'; import 'package:flutter_uxcam/flutter_uxcam.dart'; class Track with Logging { @@ -20,7 +20,7 @@ class Track with Logging { void track(TrackingEvent event, {String eventValue = ""}) { if (!isInDebugMode) { - Flurry.logEvent(event.toString()); + FlurryData.logEvent(event.toString()); // Smartlook.setGlobalEventProperty(event.toString(), eventValue, false); FlutterUxcam.logEventWithProperties(event.enumToString(), {"value": eventValue}); model.Tracking tracking = model.Tracking(); diff --git a/lib/view/registration.dart b/lib/view/registration.dart index ba5bd00..2e59dde 100644 --- a/lib/view/registration.dart +++ b/lib/view/registration.dart @@ -202,8 +202,8 @@ class RegistrationPage extends StatelessWidget with Trans { Divider( color: Colors.transparent, ), - getDataProtection(loginBloc), - loginBloc.emailCheckbox ? getEmailSubscription(loginBloc) : Offstage(), + // getDataProtection(loginBloc), + // loginBloc.emailCheckbox ? getEmailSubscription(loginBloc) : Offstage(), Divider( color: Colors.transparent, ), diff --git a/lib/view/training_plan_activate_page.dart b/lib/view/training_plan_activate_page.dart index 3473152..43d7021 100644 --- a/lib/view/training_plan_activate_page.dart +++ b/lib/view/training_plan_activate_page.dart @@ -254,7 +254,7 @@ class TrainingPlanActivatePage extends StatelessWidget with Trans { ElevatedButton( style: ElevatedButton.styleFrom( onPrimary: Colors.white, - primary: Colors.orange, + primary: restricted ? Colors.grey[600] : Colors.orange, ), child: Text(t("Start")), onPressed: () { @@ -294,15 +294,25 @@ class TrainingPlanActivatePage extends StatelessWidget with Trans { } }, ), - restricted + /* restricted ? Container( padding: EdgeInsets.only(bottom: 8), child: Text( t("This is a premium function"), - style: GoogleFonts.inter(color: Colors.blue[700]), + style: GoogleFonts.inter( + color: Colors.deepOrange[800], + fontWeight: FontWeight.bold, + shadows: [ + Shadow( + offset: Offset(2.0, 2.0), + blurRadius: 4.0, + color: Colors.black54, + ), + ], + ), ), ) - : Offstage(), + : Offstage(), */ ]), ))); @@ -352,7 +362,7 @@ class TrainingPlanActivatePage extends StatelessWidget with Trans { Widget getPlanDetails(TrainingPlan plan, TrainingPlanBloc bloc) { return SfDataGrid( headerRowHeight: 30, - rowHeight: 45, + rowHeight: 60, source: TrainingPlanDetailSource( plan: plan, menuBloc: bloc.menuBloc, @@ -390,14 +400,33 @@ class TrainingPlanActivatePage extends StatelessWidget with Trans { Navigator.of(context).pop(), }, ); + }), + }, + onDropsetTap: () => { + showDialog( + context: context, + builder: (BuildContext context) { + return DialogCommon( + title: t("Dropset"), + descriptions: t("A drop set is an advanced resistance training technique "), + description2: + t(" in which you focus on completing a set until failure - or the inability to do another repetition."), + text: "OK", + onTap: () => { + Navigator.of(context).pop(), + }, + onCancel: () => { + Navigator.of(context).pop(), + }, + ); }) }), headerGridLinesVisibility: GridLinesVisibility.both, gridLinesVisibility: GridLinesVisibility.both, columns: [ GridTextColumn( - //columnWidthMode: ColumnWidthMode.lastColumnFill, - maximumWidth: 120, + columnWidthMode: ColumnWidthMode.lastColumnFill, + maximumWidth: 160, columnName: 'exerciseImage', label: Container( color: Colors.green[50], @@ -472,11 +501,13 @@ class TrainingPlanActivatePage extends StatelessWidget with Trans { class TrainingPlanDetailSource extends DataGridSource { final TrainingPlan plan; final MenuBloc menuBloc; + final VoidCallback onDropsetTap; final VoidCallback onWeightTap; final VoidCallback onRepeatTap; TrainingPlanDetailSource({ required this.plan, required this.menuBloc, + required this.onDropsetTap, required this.onWeightTap, required this.onRepeatTap, }) { @@ -540,7 +571,7 @@ class TrainingPlanDetailSource extends DataGridSource { ]), )) ]) - : dataGridCell.columnName == "weight" && dataGridCell.value == -1 + : dataGridCell.columnName == "weight" && (dataGridCell.value == -1 || dataGridCell.value == -2) ? GestureDetector( onTap: () { onWeightTap(); @@ -549,21 +580,30 @@ class TrainingPlanDetailSource extends DataGridSource { CustomIcon.question_circle, color: Colors.indigo[300], )) - : dataGridCell.columnName == "reps" && dataGridCell.value == -1 + : dataGridCell.columnName == "weight" && dataGridCell.value == -3 ? GestureDetector( onTap: () { - onRepeatTap(); + onDropsetTap(); }, child: Icon( CustomIcon.question_circle, - color: Colors.indigo[600], + color: Colors.orange[400], )) - : Text(dataGridCell.value.toString(), - style: GoogleFonts.inter( - fontSize: 14, - color: Colors.indigo, - fontWeight: FontWeight.bold, - ))); + : dataGridCell.columnName == "reps" && dataGridCell.value == -1 + ? GestureDetector( + onTap: () { + onRepeatTap(); + }, + child: Icon( + CustomIcon.question_circle, + color: Colors.indigo[600], + )) + : Text(dataGridCell.value.toString(), + style: GoogleFonts.inter( + fontSize: 14, + color: Colors.indigo, + fontWeight: FontWeight.bold, + ))); }).toList()); } } diff --git a/lib/view/training_plan_execute_page.dart b/lib/view/training_plan_execute_page.dart index 398d32f..fb23b98 100644 --- a/lib/view/training_plan_execute_page.dart +++ b/lib/view/training_plan_execute_page.dart @@ -8,8 +8,10 @@ import 'package:aitrainer_app/util/app_localization.dart'; import 'package:aitrainer_app/util/trans.dart'; import 'package:aitrainer_app/widgets/app_bar.dart'; import 'package:aitrainer_app/widgets/dialog_common.dart'; +import 'package:aitrainer_app/widgets/dialog_html.dart'; import 'package:aitrainer_app/widgets/menu_image.dart'; import 'package:aitrainer_app/widgets/victory_widget.dart'; +import 'package:badges/badges.dart'; import 'package:extended_tabs/extended_tabs.dart'; import 'package:ezanimation/ezanimation.dart'; import 'package:flutter/cupertino.dart'; @@ -125,6 +127,7 @@ class _ExerciseTabs extends State with TickerProviderStateMixin { @override void initState() { super.initState(); + print("init TAB ${widget.bloc.dayNames.length} index ${widget.bloc.activeDayIndex}"); tabController = TabController(length: widget.bloc.dayNames.length, vsync: this); tabController.animateTo(widget.bloc.activeDayIndex, duration: Duration(milliseconds: 300)); } @@ -143,6 +146,13 @@ class _ExerciseTabs extends State with TickerProviderStateMixin { Widget getTabs(TrainingPlanBloc bloc) { return Column(children: [ ExtendedTabBar( + indicator: BoxDecoration( + color: Colors.black87, + border: Border( + bottom: BorderSide(width: 4.0, color: Colors.blue), + top: BorderSide(width: 4.0, color: Colors.blue), + )), + labelPadding: EdgeInsets.only(left: 5, right: 5), tabs: getTabNames(), controller: tabController, onTap: (index) => bloc.activeDayIndex = index, @@ -168,52 +178,57 @@ class _ExerciseTabs extends State with TickerProviderStateMixin { 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, - ), - ], - )), - ])); + final Widget widget = Container( + //height: 50, + padding: EdgeInsets.only(top: 2, left: 5, right: 5, bottom: 2), + color: Colors.white24, + child: RichText( + textScaleFactor: 0.8, + text: TextSpan( + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + children: [ + TextSpan( + text: AppLocalizations.of(context)!.translate("Training Day") + ": \n", + style: GoogleFonts.inter( + fontSize: 14, + color: Colors.white, + shadows: [ + Shadow( + 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)); }); @@ -559,11 +574,17 @@ class _ExerciseTileState extends State with Trans { @override Widget build(BuildContext context) { setContext(context); + print("detail ${widget.detail}"); final ExercisePlanDetailState state = widget.detail.state; final bool done = state.equalsTo(ExercisePlanDetailState.finished) || state.equalsTo(ExercisePlanDetailState.skipped); final String countSerie = widget.detail.set.toString(); final String step = (widget.detail.exercises.length).toString(); String weight = widget.detail.weight != null ? widget.detail.weight!.toStringAsFixed(1) : "-"; + bool isDrop = false; + if (widget.detail.weight == -3) { + weight = t("DROP"); + isDrop = true; + } String restingTime = widget.detail.restingTime == null ? "" : widget.detail.restingTime!.toStringAsFixed(0); bool isTest = false; if (widget.detail.weight != null && widget.detail.weight! == -1) { @@ -574,6 +595,9 @@ class _ExerciseTileState extends State with Trans { if (widget.detail.repeats! == -1) { repeats = t("MAX"); } + final bool extraExercise = widget.detail.exerciseType!.name == "Warming Up" || widget.detail.exerciseType!.name == "Stretching"; + + bool buddyWarning = widget.detail.exerciseType == null ? false : widget.detail.exerciseType!.buddyWarning; setContext(context); return Container( color: Colors.transparent, @@ -620,10 +644,58 @@ class _ExerciseTileState extends State with Trans { Container( width: 120, height: 80, - child: MenuImage( - imageName: widget.bloc.getActualImageName(widget.detail.exerciseType!.exerciseTypeId), - workoutTreeId: widget.bloc.getActualWorkoutTreeId(widget.detail.exerciseType!.exerciseTypeId)!, - ), + child: Badge( + elevation: 0, + padding: EdgeInsets.all(0), + position: BadgePosition.bottomStart(start: -5), + animationDuration: Duration(milliseconds: 500), + animationType: BadgeAnimationType.slide, + badgeColor: Colors.transparent, + showBadge: true, + badgeContent: IconButton( + onPressed: () => showDialog( + context: context, + builder: (BuildContext context) { + return DialogHTML( + title: widget.detail.exerciseType!.nameTranslation, + htmlData: '

' + widget.detail.exerciseType!.descriptionTranslation + '

'); + }), + icon: Icon( + Icons.info_outline, + color: Colors.yellow[200], + )), + child: Badge( + elevation: 0, + padding: EdgeInsets.all(0), + position: BadgePosition.topEnd(end: -8), + animationDuration: Duration(milliseconds: 500), + animationType: BadgeAnimationType.slide, + badgeColor: Colors.transparent, + showBadge: buddyWarning, + badgeContent: IconButton( + onPressed: () => showDialog( + context: context, + builder: (BuildContext context) { + return DialogCommon( + warning: true, + text: "Warning", + descriptions: t("Attention!"), + description2: t("The safe and exact execution of this exercise you need a training buddy or a trainer"), + description3: t("Execution at your own risk!"), + onTap: () => Navigator.of(context).pop(), + onCancel: () => Navigator.of(context).pop(), + title: t('Training Buddy'), + ); + }), + icon: Icon( + CustomIcon.exclamation_circle, + color: Colors.red[800], + )), + child: MenuImage( + imageName: widget.bloc.getActualImageName(widget.detail.exerciseType!.exerciseTypeId), + workoutTreeId: widget.bloc.getActualWorkoutTreeId(widget.detail.exerciseType!.exerciseTypeId)!, + radius: 12, + ))), ), SizedBox( width: 10, @@ -656,18 +728,18 @@ class _ExerciseTileState extends State with Trans { ), ], )), - widget.detail.exerciseType!.unitQuantityUnit != null + widget.detail.exerciseType!.unitQuantityUnit != null && !extraExercise ? TextSpan( text: "\n", ) : TextSpan(), - widget.detail.exerciseType!.unitQuantityUnit != null + widget.detail.exerciseType!.unitQuantityUnit != null && !extraExercise ? TextSpan( text: t(widget.detail.exerciseType!.unitQuantityUnit!) + ": ", style: GoogleFonts.inter( fontSize: 12, color: done ? Colors.grey[100] : Colors.yellow[400], fontWeight: FontWeight.bold)) : TextSpan(), - widget.detail.exerciseType!.unitQuantityUnit != null + widget.detail.exerciseType!.unitQuantityUnit != null && !extraExercise ? TextSpan( text: weight, style: GoogleFonts.inter( @@ -677,37 +749,50 @@ class _ExerciseTileState extends State with Trans { TextSpan( text: "\n", ), - TextSpan( - text: t(widget.detail.exerciseType!.unit) + ": ", - style: GoogleFonts.inter( - fontSize: 12, color: done ? Colors.grey[100] : Colors.yellow[400], fontWeight: FontWeight.bold)), - TextSpan( - text: repeats, - style: GoogleFonts.inter( - fontSize: 12, - )), + !extraExercise + ? TextSpan( + text: t(widget.detail.exerciseType!.unit) + ": ", + style: GoogleFonts.inter( + fontSize: 12, color: done ? Colors.grey[100] : Colors.yellow[400], fontWeight: FontWeight.bold)) + : TextSpan(), + !extraExercise + ? TextSpan( + text: repeats, + style: GoogleFonts.inter( + fontSize: 12, + )) + : TextSpan(), TextSpan( text: "\n", ), - TextSpan( - text: t("Set") + ": ", - style: GoogleFonts.inter( - fontSize: 12, color: done ? Colors.grey[100] : Colors.yellow[400], fontWeight: FontWeight.bold)), - TextSpan( - text: step + "/" + countSerie, - style: GoogleFonts.inter( - fontSize: 12, - )), + !extraExercise + ? TextSpan( + text: t("Set") + ": ", + style: GoogleFonts.inter( + fontSize: 12, color: done ? Colors.grey[100] : Colors.yellow[400], fontWeight: FontWeight.bold)) + : TextSpan(), + !extraExercise + ? TextSpan( + text: step + "/" + countSerie, + style: GoogleFonts.inter( + fontSize: 12, + )) + : TextSpan(), TextSpan( text: "\n", ), - TextSpan( - text: t("Resting time") + ": ", - style: GoogleFonts.inter( - fontSize: 12, color: done ? Colors.grey[100] : Colors.yellow[400], fontWeight: FontWeight.bold)), - TextSpan( - text: restingTime + " " + t("min(s)"), - style: GoogleFonts.inter(fontSize: 12, color: done ? Colors.grey[100] : Colors.white, fontWeight: FontWeight.bold)), + !extraExercise + ? TextSpan( + text: t("Resting time") + ": ", + style: GoogleFonts.inter( + fontSize: 12, color: done ? Colors.grey[100] : Colors.yellow[400], fontWeight: FontWeight.bold)) + : TextSpan(), + !extraExercise + ? TextSpan( + text: restingTime + " " + t("min(s)"), + style: + GoogleFonts.inter(fontSize: 12, color: done ? Colors.grey[100] : Colors.white, fontWeight: FontWeight.bold)) + : TextSpan(), ]), )), isTest @@ -740,7 +825,35 @@ class _ExerciseTileState extends State with Trans { )), ]); }) - : Offstage() + : isDrop + ? AnimatedBuilder( + animation: animation, + builder: (context, snapshot) { + return Column(mainAxisAlignment: MainAxisAlignment.center, children: [ + GestureDetector( + onTap: () => showDialog( + context: context, + builder: (BuildContext context) { + return DialogCommon( + warning: false, + title: t("Drop set"), + descriptions: t("Drop set"), + description2: t("Recommended method:"), + text: "OK", + onTap: () => Navigator.of(context).pop(), + onCancel: () => { + Navigator.of(context).pop(), + }, + ); + }), + child: Icon( + CustomIcon.question_circle, + color: Colors.orange[200], + size: 16, + )), + ]); + }) + : Offstage() ]), ), ), diff --git a/lib/view/training_plan_exercise.dart b/lib/view/training_plan_exercise.dart index 9617ed0..4adfa5d 100644 --- a/lib/view/training_plan_exercise.dart +++ b/lib/view/training_plan_exercise.dart @@ -6,7 +6,6 @@ import 'package:aitrainer_app/model/customer_training_plan_details.dart'; import 'package:aitrainer_app/util/trans.dart'; import 'package:aitrainer_app/widgets/app_bar.dart'; import 'package:aitrainer_app/widgets/exercise_save.dart'; -import 'package:aitrainer_app/widgets/number_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:google_fonts/google_fonts.dart'; @@ -43,7 +42,7 @@ class TrainingPlanExercise extends StatelessWidget with Trans { }, builder: (context, state) { return ModalProgressHUD( child: getExercises(bloc, detail), - inAsyncCall: state is TrainingPlanLoading, + inAsyncCall: state is TrainingPlanExerciseLoading, opacity: 0.5, color: Colors.black54, progressIndicator: CircularProgressIndicator(), @@ -52,6 +51,7 @@ class TrainingPlanExercise extends StatelessWidget with Trans { ), floatingActionButton: Row(mainAxisAlignment: MainAxisAlignment.end, children: [ FloatingActionButton.extended( + heroTag: "skipButton", onPressed: () => { Navigator.of(context).pop(), bloc.add(TrainingPlanSkipExercise(detail: detail)), @@ -67,6 +67,7 @@ class TrainingPlanExercise extends StatelessWidget with Trans { width: 20, ), FloatingActionButton.extended( + heroTag: "saveButton", onPressed: () => { Navigator.of(context).pop(), bloc.add(TrainingPlanSaveExercise(detail: detail)), @@ -111,7 +112,7 @@ class TrainingPlanExercise extends StatelessWidget with Trans { ); } - Widget getExerciseForm(TrainingPlanBloc bloc, CustomerTrainingPlanDetails detail) { + /* Widget getExerciseForm(TrainingPlanBloc bloc, CustomerTrainingPlanDetails detail) { return Container( padding: const EdgeInsets.only(top: 10, left: 25, right: 25), child: SingleChildScrollView( @@ -224,5 +225,5 @@ class TrainingPlanExercise extends StatelessWidget with Trans { crossAxisAlignment: CrossAxisAlignment.start, children: listWidgets, ); - } + } */ } diff --git a/lib/view/training_plans_page.dart b/lib/view/training_plans_page.dart index d778eac..1216a3f 100644 --- a/lib/view/training_plans_page.dart +++ b/lib/view/training_plans_page.dart @@ -85,6 +85,7 @@ class MyTrainingPlans extends StatelessWidget with Trans, Logging { getTrainingPlan(t("Training Plans for Women"), "asset/menu/training_plans_q_woman.jpg", "for_woman"), getTrainingPlan(t("Training Plans of Celebrities"), "asset/menu/training_plans_q_celebrities.jpg", "celebrities"), getTrainingPlan(t("Training Plans for Gain Strength"), "asset/menu/training_plans_q_gain_strength.jpg", "gain_strength"), + getTrainingPlan(t("Physical Prepare Program for Footgolfers"), "asset/menu/FG_2_edz.jpg", "footgolf"), ]), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, diff --git a/lib/widgets/menu_image.dart b/lib/widgets/menu_image.dart index 40e53f9..3fc511f 100644 --- a/lib/widgets/menu_image.dart +++ b/lib/widgets/menu_image.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:aitrainer_app/library/image_cache.dart' as wt; import 'package:aitrainer_app/library/transparent_image.dart'; +// ignore: must_be_immutable class MenuImage extends StatelessWidget { final int? workoutTreeId; final String imageName; diff --git a/lib/widgets/treeview_parent_widget.dart b/lib/widgets/treeview_parent_widget.dart index b4046da..60c107a 100644 --- a/lib/widgets/treeview_parent_widget.dart +++ b/lib/widgets/treeview_parent_widget.dart @@ -25,10 +25,12 @@ class TreeviewParentWidget extends StatelessWidget { @override Widget build(BuildContext context) { - Widget parentWidget = Text( - this.text, - style: GoogleFonts.archivoBlack(fontSize: fontSize, color: color ?? Colors.blue[800]!, backgroundColor: Colors.transparent), - ); + Widget parentWidget = Text(this.text, + style: GoogleFonts.archivoBlack( + fontSize: fontSize, + color: color ?? Colors.blue[800]!, + backgroundColor: Colors.transparent, + )); return Card( color: backgroundColor, diff --git a/pubspec.lock b/pubspec.lock index d571604..f5d8b68 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -22,13 +22,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.6" - apple_sign_in: - dependency: "direct main" - description: - name: apple_sign_in - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.0" archive: dependency: transitive description: @@ -50,6 +43,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.6.1" + awesome_notifications: + dependency: "direct main" + description: + name: awesome_notifications + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.6+9" badges: dependency: "direct main" description: @@ -442,13 +442,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.36.1" - flurry: + flurry_data: dependency: "direct main" description: - name: flurry + name: flurry_data url: "https://pub.dartlang.org" source: hosted - version: "0.0.7" + version: "0.0.1" flutter: dependency: "direct main" description: flutter @@ -517,20 +517,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.1" - flutter_local_notifications: - dependency: "direct main" - description: - name: flutter_local_notifications - url: "https://pub.dartlang.org" - source: hosted - version: "5.0.0+4" - flutter_local_notifications_platform_interface: - dependency: transitive - description: - name: flutter_local_notifications_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.0" flutter_localizations: dependency: "direct main" description: flutter @@ -1064,6 +1050,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.1" + sign_in_with_apple: + dependency: "direct main" + description: + name: sign_in_with_apple + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" sky_engine: dependency: transitive description: flutter @@ -1216,13 +1209,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.0" - timezone: - dependency: transitive - description: - name: timezone - url: "https://pub.dartlang.org" - source: hosted - version: "0.7.0" timing: dependency: transitive description: @@ -1435,4 +1421,4 @@ packages: version: "3.1.0" sdks: dart: ">=2.12.0 <3.0.0" - flutter: ">=2.0.0" + flutter: ">=2.0.4" diff --git a/pubspec.yaml b/pubspec.yaml index 8477fa9..4b7c322 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.1.19+89 +version: 1.1.19+90 environment: sdk: ">=2.12.0 <3.0.0" @@ -53,31 +53,28 @@ dependencies: confetti: ^0.6.0-nullsafety crypto: ^3.0.0 carousel_slider: ^4.0.0-nullsafety.0 - #dropdown_search: ^0.5.0 convex_bottom_bar: ^3.0.0 flutter_app_badger: ^1.2.0 - #super_tooltip: ^1.0.1 url_launcher: ^6.0.3 extended_tabs: ^2.2.0 upgrader: ^3.3.0 - + firebase_core: ^1.2.0 firebase_analytics: ^8.1.0 firebase_messaging: ^10.0.0 - flutter_local_notifications: ^5.0.0 firebase_auth: ^1.2.0 firebase_remote_config: ^0.10.0 + awesome_notifications: ^0.0.6+9 syncfusion_flutter_gauges: ^19.1.63 syncfusion_flutter_datagrid: ^19.1.63 flutter_facebook_auth: ^3.4.0 google_sign_in: ^5.0.3 - apple_sign_in: ^0.1.0 - #sign_in_with_apple: ^3.0.0 - + sign_in_with_apple: ^3.0.0 + #smartlook: ^1.0.7 - flurry: ^0.0.4 + flurry_data: ^0.0.1 flutter_uxcam: ^2.0.0-beta.1 animated_widgets: ^1.0.6 @@ -249,6 +246,7 @@ flutter: - asset/menu/400m.jpg - asset/menu/FG_1_test.jpg - asset/menu/FG_1_training.jpg + - asset/menu/FG_2_edz.jpg - asset/menu/alternate_dumbbell_presses.jpg - asset/menu/alternate_standing_shoulder_press.jpg - asset/menu/arnold_press.jpg @@ -385,6 +383,7 @@ flutter: - asset/menu/standing_triceps_extension.jpg - asset/menu/stiff_legged_deadlift.jpg - asset/menu/straight-arm_rope_pull-down.jpg + - asset/menu/stretching.jpg - asset/menu/t_bar_rows.jpg - asset/menu/test_center.jpg - asset/menu/test_on_machines.jpg @@ -411,6 +410,7 @@ flutter: - asset/menu/upper_body.jpg - asset/menu/v_ups.jpg - asset/menu/wall_sit.jpg + - asset/menu/warmup.jpg - asset/menu/warrior_stand.jpg - asset/menu/weight_free_test.jpg - asset/menu/weighted_bench_dip.jpg