diff --git a/asset/menu/training_plans_advanced.jpg b/asset/menu/training_plans_advanced.jpg new file mode 100644 index 0000000..c1a8a29 Binary files /dev/null and b/asset/menu/training_plans_advanced.jpg differ diff --git a/asset/menu/training_plans_beginner.jpg b/asset/menu/training_plans_beginner.jpg new file mode 100644 index 0000000..9077c3f Binary files /dev/null and b/asset/menu/training_plans_beginner.jpg differ diff --git a/asset/menu/training_plans_celebrities.jpg b/asset/menu/training_plans_celebrities.jpg new file mode 100644 index 0000000..2ca6dfb Binary files /dev/null and b/asset/menu/training_plans_celebrities.jpg differ diff --git a/asset/menu/training_plans_home.jpg b/asset/menu/training_plans_home.jpg new file mode 100644 index 0000000..12ff110 Binary files /dev/null and b/asset/menu/training_plans_home.jpg differ diff --git a/asset/menu/training_plans_menu.jpg b/asset/menu/training_plans_menu.jpg new file mode 100644 index 0000000..7bad09d Binary files /dev/null and b/asset/menu/training_plans_menu.jpg differ diff --git a/asset/menu/training_plans_q_advanced.jpg b/asset/menu/training_plans_q_advanced.jpg new file mode 100644 index 0000000..e69e746 Binary files /dev/null and b/asset/menu/training_plans_q_advanced.jpg differ diff --git a/asset/menu/training_plans_q_beginner.jpg b/asset/menu/training_plans_q_beginner.jpg new file mode 100644 index 0000000..852b6e9 Binary files /dev/null and b/asset/menu/training_plans_q_beginner.jpg differ diff --git a/asset/menu/training_plans_q_celebrities.jpg b/asset/menu/training_plans_q_celebrities.jpg new file mode 100644 index 0000000..1b2eba8 Binary files /dev/null and b/asset/menu/training_plans_q_celebrities.jpg differ diff --git a/asset/menu/training_plans_q_gain_strength.jpg b/asset/menu/training_plans_q_gain_strength.jpg new file mode 100644 index 0000000..7159111 Binary files /dev/null and b/asset/menu/training_plans_q_gain_strength.jpg differ diff --git a/asset/menu/training_plans_q_home.jpg b/asset/menu/training_plans_q_home.jpg new file mode 100644 index 0000000..6c68b9d Binary files /dev/null and b/asset/menu/training_plans_q_home.jpg differ diff --git a/asset/menu/training_plans_q_woman.jpg b/asset/menu/training_plans_q_woman.jpg new file mode 100644 index 0000000..962211d Binary files /dev/null and b/asset/menu/training_plans_q_woman.jpg differ diff --git a/asset/menu/training_plans_strength_gain.jpg b/asset/menu/training_plans_strength_gain.jpg new file mode 100644 index 0000000..bdebf10 Binary files /dev/null and b/asset/menu/training_plans_strength_gain.jpg differ diff --git a/asset/menu/training_plans_woman.jpg b/asset/menu/training_plans_woman.jpg new file mode 100644 index 0000000..8e6456a Binary files /dev/null and b/asset/menu/training_plans_woman.jpg differ diff --git a/i18n/en.json b/i18n/en.json index 7b830c0..a0ee8f3 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -477,6 +477,15 @@ "Try free for 3 days!":"Try it without risk for 3 days! In this period you can cancel any time without lasting your account.", "View other alternatives":"View other alternatives", - "Please log in, because we can calculate the best suggestions for you":"Please log in, because we can calculate the best suggestions for you" + "Please log in, because we can calculate the best suggestions for you":"Please log in, because we can calculate the best suggestions for you", + + "My Active Training":"My Active Training", + "My Custom Plan":"My Custom Plan", + "Training Plans for Beginners":"Training Plans for Beginners", + "Training Plans for Home":"Training Plans for Home", + "Training Plans Advanced":"Training Plans Advanced", + "Training Plans for Women":"Training Plans for Women", + "Training Plans of Celebrities":"Training Plans of Celebrities", + "Training Plans for Gain Strength":"Training Plans for Gain Strength" } \ No newline at end of file diff --git a/i18n/hu.json b/i18n/hu.json index f627cf1..b82ba8b 100644 --- a/i18n/hu.json +++ b/i18n/hu.json @@ -469,5 +469,14 @@ "Try free for 3 days!":"Próbáld ki kockázat nélkül 3 napig! Ebben az időszakban bármikor lemondhatod, anélkül, hogy megterhelnénk a számládat.", "View other alternatives":"Megnézek egy másik lehetőséget", - "Please log in, because we can calculate the best suggestions for you":"Kérlek jelentkezz be, mert csak így tudjuk neked a legjobb gyakorlatokat kalkulálni" + "Please log in, because we can calculate the best suggestions for you":"Kérlek jelentkezz be, mert csak így tudjuk neked a legjobb gyakorlatokat kalkulálni", + + "My Active Training":"Aktív edzésem", + "My Custom Plan":"Egyéni edzésterv", + "Training Plans for Beginners":"Kezdő edzésprogramok", + "Training Plans for Home":"Otthoni edzésprogramok", + "Training Plans Advanced":"Haladó edzésprogramok", + "Training Plans for Women":"Edzésprogramok nőknek", + "Training Plans of Celebrities":"Celebek edzésprogramjai", + "Training Plans for Gain Strength":"Erőnövelő edzésprogramok" } \ No newline at end of file diff --git a/ios/Podfile.lock b/ios/Podfile.lock index b798013..90d462c 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -18,112 +18,106 @@ PODS: - FBSDKLoginKit/Login (= 9.1.0) - FBSDKLoginKit/Login (9.1.0): - FBSDKCoreKit (~> 9.1.0) - - Firebase/Analytics (7.11.0): + - Firebase/Analytics (8.0.0): - Firebase/Core - - Firebase/Auth (7.11.0): + - Firebase/Auth (8.0.0): - Firebase/CoreOnly - - FirebaseAuth (~> 7.11.0) - - Firebase/Core (7.11.0): + - FirebaseAuth (~> 8.0.0) + - Firebase/Core (8.0.0): - Firebase/CoreOnly - - FirebaseAnalytics (~> 7.11.0) - - Firebase/CoreOnly (7.11.0): - - FirebaseCore (= 7.11.0) - - Firebase/Messaging (7.11.0): + - FirebaseAnalytics (~> 8.0.0) + - Firebase/CoreOnly (8.0.0): + - FirebaseCore (= 8.0.0) + - Firebase/Messaging (8.0.0): - Firebase/CoreOnly - - FirebaseMessaging (~> 7.11.0) - - Firebase/RemoteConfig (7.11.0): + - FirebaseMessaging (~> 8.0.0) + - Firebase/RemoteConfig (8.0.0): - Firebase/CoreOnly - - FirebaseRemoteConfig (~> 7.11.0) - - firebase_analytics (8.0.2): - - Firebase/Analytics (= 7.11.0) + - FirebaseRemoteConfig (~> 8.0.0) + - firebase_analytics (8.1.0): + - Firebase/Analytics (= 8.0.0) - firebase_core - Flutter - - firebase_auth (1.1.2): - - Firebase/Auth (= 7.11.0) + - firebase_auth (1.2.0): + - Firebase/Auth (= 8.0.0) - firebase_core - Flutter - - firebase_core (1.1.0): - - Firebase/CoreOnly (= 7.11.0) + - firebase_core (1.2.0): + - Firebase/CoreOnly (= 8.0.0) - Flutter - - firebase_messaging (9.1.3): - - Firebase/Messaging (= 7.11.0) + - firebase_messaging (10.0.0): + - Firebase/Messaging (= 8.0.0) - firebase_core - Flutter - - firebase_remote_config (0.10.0-dev.2): - - Firebase/RemoteConfig (= 7.11.0) + - firebase_remote_config (0.10.0): + - Firebase/RemoteConfig (= 8.0.0) - firebase_core - Flutter - - FirebaseABTesting (7.11.0): - - FirebaseCore (~> 7.0) - - FirebaseAnalytics (7.11.0): - - FirebaseAnalytics/AdIdSupport (= 7.11.0) - - FirebaseCore (~> 7.0) - - FirebaseInstallations (~> 7.0) - - GoogleUtilities/AppDelegateSwizzler (~> 7.0) - - GoogleUtilities/MethodSwizzler (~> 7.0) - - GoogleUtilities/Network (~> 7.0) - - "GoogleUtilities/NSData+zlib (~> 7.0)" + - FirebaseABTesting (8.0.0): + - FirebaseCore (~> 8.0) + - FirebaseAnalytics (8.0.0): + - FirebaseAnalytics/AdIdSupport (= 8.0.0) + - FirebaseCore (~> 8.0) + - FirebaseInstallations (~> 8.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.4) + - GoogleUtilities/MethodSwizzler (~> 7.4) + - GoogleUtilities/Network (~> 7.4) + - "GoogleUtilities/NSData+zlib (~> 7.4)" - nanopb (~> 2.30908.0) - - FirebaseAnalytics/AdIdSupport (7.11.0): - - FirebaseAnalytics/Base (= 7.11.0) - - FirebaseCore (~> 7.0) - - FirebaseInstallations (~> 7.0) - - GoogleAppMeasurement/AdIdSupport (= 7.11.0) - - GoogleUtilities/AppDelegateSwizzler (~> 7.0) - - GoogleUtilities/MethodSwizzler (~> 7.0) - - GoogleUtilities/Network (~> 7.0) - - "GoogleUtilities/NSData+zlib (~> 7.0)" + - FirebaseAnalytics/AdIdSupport (8.0.0): + - FirebaseAnalytics/Base (= 8.0.0) + - FirebaseCore (~> 8.0) + - FirebaseInstallations (~> 8.0) + - GoogleAppMeasurement (= 8.0.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.4) + - GoogleUtilities/MethodSwizzler (~> 7.4) + - GoogleUtilities/Network (~> 7.4) + - "GoogleUtilities/NSData+zlib (~> 7.4)" - nanopb (~> 2.30908.0) - - FirebaseAnalytics/Base (7.11.0): - - FirebaseCore (~> 7.0) - - FirebaseInstallations (~> 7.0) - - GoogleUtilities/AppDelegateSwizzler (~> 7.0) - - GoogleUtilities/MethodSwizzler (~> 7.0) - - GoogleUtilities/Network (~> 7.0) - - "GoogleUtilities/NSData+zlib (~> 7.0)" + - FirebaseAnalytics/Base (8.0.0): + - FirebaseCore (~> 8.0) + - FirebaseInstallations (~> 8.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.4) + - GoogleUtilities/MethodSwizzler (~> 7.4) + - GoogleUtilities/Network (~> 7.4) + - "GoogleUtilities/NSData+zlib (~> 7.4)" - nanopb (~> 2.30908.0) - - FirebaseAuth (7.11.0): - - FirebaseCore (~> 7.0) - - GoogleUtilities/AppDelegateSwizzler (~> 7.0) - - GoogleUtilities/Environment (~> 7.0) - - GTMSessionFetcher/Core (~> 1.4) - - FirebaseCore (7.11.0): - - FirebaseCoreDiagnostics (~> 7.4) - - GoogleUtilities/Environment (~> 7.0) - - GoogleUtilities/Logger (~> 7.0) - - FirebaseCoreDiagnostics (7.11.0): - - GoogleDataTransport (~> 8.4) - - GoogleUtilities/Environment (~> 7.0) - - GoogleUtilities/Logger (~> 7.0) + - FirebaseAuth (8.0.0): + - FirebaseCore (~> 8.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.4) + - GoogleUtilities/Environment (~> 7.4) + - GTMSessionFetcher/Core (~> 1.5) + - FirebaseCore (8.0.0): + - FirebaseCoreDiagnostics (~> 8.0) + - GoogleUtilities/Environment (~> 7.4) + - GoogleUtilities/Logger (~> 7.4) + - FirebaseCoreDiagnostics (8.0.0): + - GoogleDataTransport (~> 9.0) + - GoogleUtilities/Environment (~> 7.4) + - GoogleUtilities/Logger (~> 7.4) - nanopb (~> 2.30908.0) - - FirebaseInstallations (7.11.0): - - FirebaseCore (~> 7.0) - - GoogleUtilities/Environment (~> 7.0) - - GoogleUtilities/UserDefaults (~> 7.0) + - FirebaseInstallations (8.0.0): + - FirebaseCore (~> 8.0) + - GoogleUtilities/Environment (~> 7.4) + - GoogleUtilities/UserDefaults (~> 7.4) - PromisesObjC (~> 1.2) - - FirebaseInstanceID (7.11.0): - - FirebaseCore (~> 7.0) - - FirebaseInstallations (~> 7.0) - - GoogleUtilities/Environment (~> 7.0) - - GoogleUtilities/UserDefaults (~> 7.0) - - FirebaseMessaging (7.11.0): - - FirebaseCore (~> 7.0) - - FirebaseInstallations (~> 7.0) - - FirebaseInstanceID (~> 7.0) - - GoogleUtilities/AppDelegateSwizzler (~> 7.0) - - GoogleUtilities/Environment (~> 7.0) - - GoogleUtilities/Reachability (~> 7.0) - - GoogleUtilities/UserDefaults (~> 7.0) - - FirebaseRemoteConfig (7.11.0): - - FirebaseABTesting (~> 7.0) - - FirebaseCore (~> 7.0) - - FirebaseInstallations (~> 7.0) - - GoogleUtilities/Environment (~> 7.0) - - "GoogleUtilities/NSData+zlib (~> 7.0)" + - FirebaseMessaging (8.0.0): + - FirebaseCore (~> 8.0) + - FirebaseInstallations (~> 8.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.4) + - GoogleUtilities/Environment (~> 7.4) + - GoogleUtilities/Reachability (~> 7.4) + - GoogleUtilities/UserDefaults (~> 7.4) + - FirebaseRemoteConfig (8.0.0): + - FirebaseABTesting (~> 8.0) + - FirebaseCore (~> 8.0) + - FirebaseInstallations (~> 8.0) + - GoogleUtilities/Environment (~> 7.4) + - "GoogleUtilities/NSData+zlib (~> 7.4)" - flurry (0.0.4): - Flurry-iOS-SDK/FlurrySDK - Flutter - - Flurry-iOS-SDK/FlurrySDK (11.2.0) + - Flurry-iOS-SDK/FlurrySDK (11.2.1) - Flutter (1.0.0) - flutter_app_badger (0.0.1): - Flutter @@ -144,13 +138,20 @@ PODS: - google_sign_in (0.0.1): - Flutter - GoogleSignIn (~> 5.0) - - GoogleAppMeasurement/AdIdSupport (7.11.0): - - GoogleUtilities/AppDelegateSwizzler (~> 7.0) - - GoogleUtilities/MethodSwizzler (~> 7.0) - - GoogleUtilities/Network (~> 7.0) - - "GoogleUtilities/NSData+zlib (~> 7.0)" + - GoogleAppMeasurement (8.0.0): + - GoogleAppMeasurement/AdIdSupport (= 8.0.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.4) + - GoogleUtilities/MethodSwizzler (~> 7.4) + - GoogleUtilities/Network (~> 7.4) + - "GoogleUtilities/NSData+zlib (~> 7.4)" - nanopb (~> 2.30908.0) - - GoogleDataTransport (8.4.0): + - GoogleAppMeasurement/AdIdSupport (8.0.0): + - GoogleUtilities/AppDelegateSwizzler (~> 7.4) + - GoogleUtilities/MethodSwizzler (~> 7.4) + - GoogleUtilities/Network (~> 7.4) + - "GoogleUtilities/NSData+zlib (~> 7.4)" + - nanopb (~> 2.30908.0) + - GoogleDataTransport (9.0.0): - GoogleUtilities/Environment (~> 7.2) - nanopb (~> 2.30908.0) - PromisesObjC (~> 1.2) @@ -158,33 +159,29 @@ PODS: - AppAuth (~> 1.2) - GTMAppAuth (~> 1.0) - GTMSessionFetcher/Core (~> 1.1) - - GoogleUtilities/AppDelegateSwizzler (7.4.0): + - GoogleUtilities/AppDelegateSwizzler (7.4.1): - GoogleUtilities/Environment - GoogleUtilities/Logger - GoogleUtilities/Network - - GoogleUtilities/Environment (7.4.0): + - GoogleUtilities/Environment (7.4.1): - PromisesObjC (~> 1.2) - - GoogleUtilities/Logger (7.4.0): + - GoogleUtilities/Logger (7.4.1): - GoogleUtilities/Environment - - GoogleUtilities/MethodSwizzler (7.4.0): + - GoogleUtilities/MethodSwizzler (7.4.1): - GoogleUtilities/Logger - - GoogleUtilities/Network (7.4.0): + - GoogleUtilities/Network (7.4.1): - GoogleUtilities/Logger - "GoogleUtilities/NSData+zlib" - GoogleUtilities/Reachability - - "GoogleUtilities/NSData+zlib (7.4.0)" - - GoogleUtilities/Reachability (7.4.0): + - "GoogleUtilities/NSData+zlib (7.4.1)" + - GoogleUtilities/Reachability (7.4.1): - GoogleUtilities/Logger - - GoogleUtilities/UserDefaults (7.4.0): + - GoogleUtilities/UserDefaults (7.4.1): - GoogleUtilities/Logger - - GTMAppAuth (1.1.0): + - GTMAppAuth (1.2.2): - AppAuth/Core (~> 1.4) - - GTMSessionFetcher (~> 1.4) - - GTMSessionFetcher (1.5.0): - - GTMSessionFetcher/Full (= 1.5.0) + - GTMSessionFetcher/Core (~> 1.5) - GTMSessionFetcher/Core (1.5.0) - - GTMSessionFetcher/Full (1.5.0): - - GTMSessionFetcher/Core (= 1.5.0) - modal_progress_hud_nsn (0.0.1): - Flutter - nanopb (2.30908.0): @@ -199,20 +196,21 @@ PODS: - path_provider (0.0.1): - Flutter - PromisesObjC (1.2.12) - - Purchases (3.10.7): - - PurchasesCoreSwift (= 3.10.7) - - purchases_flutter (3.2.1): + - Purchases (3.11.1): + - PurchasesCoreSwift (= 3.11.1) + - purchases_flutter (3.2.2): - Flutter - - PurchasesHybridCommon (= 1.6.2) - - PurchasesCoreSwift (3.10.7) - - PurchasesHybridCommon (1.6.2): - - Purchases (= 3.10.7) - - Sentry (6.2.1): - - Sentry/Core (= 6.2.1) - - Sentry/Core (6.2.1) + - PurchasesHybridCommon (= 1.6.3) + - PurchasesCoreSwift (3.11.1) + - PurchasesHybridCommon (1.6.3): + - Purchases (= 3.11.1) + - Sentry (7.0.3): + - Sentry/Core (= 7.0.3) + - Sentry/Core (7.0.3) - sentry_flutter (0.0.1): - Flutter - - Sentry (~> 6.2.1) + - FlutterMacOS + - Sentry (~> 7.0.3) - shared_preferences (0.0.1): - Flutter - sqflite (0.0.2): @@ -269,7 +267,6 @@ SPEC REPOS: - FirebaseCore - FirebaseCoreDiagnostics - FirebaseInstallations - - FirebaseInstanceID - FirebaseMessaging - FirebaseRemoteConfig - Flurry-iOS-SDK @@ -350,23 +347,22 @@ SPEC CHECKSUMS: devicelocale: b22617f40038496deffba44747101255cee005b0 FBSDKCoreKit: a00fe2efd780c195a5e09201bf51c56106245b40 FBSDKLoginKit: d98498c598ec09de657385a9349a1f21119b7f86 - Firebase: c121feb35e4126c0b355e3313fa9b487d47319fd - firebase_analytics: 620e8cc1705feb6b9c40b6127bea9b39e03e3970 - firebase_auth: e7065954aa2a7c8ef1a8502fba3009bcdd8fc91a - firebase_core: 84dcd80ac6d29c3d1039071b7306ee99688eb9c7 - firebase_messaging: 7aecb08eada5e5cde85b10875141706a8d18b818 - firebase_remote_config: f855065886b7d6ccc38144c9a3cecbdc7887f33e - FirebaseABTesting: e66f1f80747792630d9b292966de206d5df9853b - FirebaseAnalytics: cd3bd84d722a24a8923918af8af8e5236f615d77 - FirebaseAuth: 5fe4585c2ed847319f0ea68bd1d82c77e49ff9a0 - FirebaseCore: 907447d8917a4d3eb0cce2829c5a0ad21d90b432 - FirebaseCoreDiagnostics: 68ad972f99206cef818230f3f3179d52ccfb7f8c - FirebaseInstallations: a58d4f72ec5861840b84df489f2668d970df558a - FirebaseInstanceID: ad5135045a498d7775903efd39762d2cdfa1be27 - FirebaseMessaging: 163435fb6db065e3b6228f1e577b10ed2cc506d2 - FirebaseRemoteConfig: 0ea30de5fb0231df8c1bdcdf3b6c23bdc5066131 + Firebase: 73c3e3b216ec1ecbc54d2ffdd4670c65c749edb1 + firebase_analytics: 221d3bc4e8f1b5144a4bd4cc6b33790ee51bd543 + firebase_auth: f960df4ddd8cb415859dbc01a02d7859925ddef0 + firebase_core: e4d3efb030a2b2021819f8faa538bb23deb46695 + firebase_messaging: 3b6e0657b21261a57a1cd041fafa713f2aa6923f + firebase_remote_config: 3a6e2db440f0e95baba3dfc3d4118b1a4bc792c4 + FirebaseABTesting: daebc95ec8829607d07dfe5e92dc3285aca29bc4 + FirebaseAnalytics: dcb92c7c9ef4fa7ffac276e8f87bd4fc8c97f1b8 + FirebaseAuth: b8cd992fca5b53dc6eec09e873a3f375f000c5a1 + FirebaseCore: 3f09591d51292843e2a46f18358d60bf4e996255 + FirebaseCoreDiagnostics: a31d987ba0fe16d59886a5dbadc2f1de871f88c8 + FirebaseInstallations: c4aab1005d6547b00a7529777fe52f5d4d45165b + FirebaseMessaging: 1a33b4af3c8042ed6ddacb6c031894af2064bfab + FirebaseRemoteConfig: 055f6b5ba1751547596ded5032c4d5c6054ca501 flurry: 15b01f664ab1367c62b50291541ea7f78ca85aad - Flurry-iOS-SDK: 6636d30c30f12010e7c7c71d84b443416a168efc + Flurry-iOS-SDK: 5831da8fc6bedb31fa1f94aac6fd204d36dd351d Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c flutter_app_badger: 65de4d6f0c34a891df49e6cfb8a1c0496426fa68 flutter_facebook_auth: 4b170c07b7fce791497093fcc3f134fb215f3f07 @@ -375,11 +371,11 @@ SPEC CHECKSUMS: flutter_uxcam: ab8e5d3954eb448febd581375e2622e9eecb1066 FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a google_sign_in: 6bd214b9c154f881422f5fe27b66aaa7bbd580cc - GoogleAppMeasurement: fd19169c3034975cb934e865e5667bfdce59df7f - GoogleDataTransport: cd9db2180fcecd8da1b561aea31e3e56cf834aa7 + GoogleAppMeasurement: c6bbc9753d046b5456dd4f940057fbad2c28419e + GoogleDataTransport: 11e3a5f2c190327df1a4a5d7e7ae3d4d5b9c9e4c GoogleSignIn: 7137d297ddc022a7e0aa4619c86d72c909fa7213 - GoogleUtilities: 284cddc7fffc14ae1907efb6f78ab95c1fccaedc - GTMAppAuth: 197a8dabfea5d665224aa00d17f164fc2248dab9 + GoogleUtilities: f8a43108b38a68eebe8b3540e1f4f2d28843ce20 + GTMAppAuth: ad5c2b70b9a8689e1a04033c9369c4915bfcbe89 GTMSessionFetcher: b3503b20a988c4e20cc189aa798fd18220133f52 modal_progress_hud_nsn: f6fb744cd060653d66ed8f325360ef3650eb2fde nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96 @@ -387,12 +383,12 @@ SPEC CHECKSUMS: package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c PromisesObjC: 3113f7f76903778cf4a0586bd1ab89329a0b7b97 - Purchases: b8b8fb6e856ac8166e217f6e014df894d821dda1 - purchases_flutter: 0130970b895c903e4e0aad793dd3a4c1b70bb434 - PurchasesCoreSwift: 8ae0f08e020f0bc97c1befa4e38a0dbc8e9732e0 - PurchasesHybridCommon: 5f5c1c245b12fc5e8760af7d11cb10f888109a9b - Sentry: 9b922b396b0e0bca8516a10e36b0ea3ebea5faf7 - sentry_flutter: 5b3c6d717db5b7482504a313c831b318297d4d37 + Purchases: 6351f9ff6bd514e5ec5aa0f989ea181effa94bf5 + purchases_flutter: 627527b070d80cdaf486fabe8b3d1dbe8d5cad92 + PurchasesCoreSwift: ee857e4c21e6254b09d7e303a756fcf2b9164408 + PurchasesHybridCommon: d65a799a61d688588534b80338edbcbf604ca93d + Sentry: 5b16f877da362d23716d827e04db642455b26b40 + sentry_flutter: 602dc1902e152269256115e2386e1029511f3440 shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 459e2f4..fcc5215 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 = 2; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = SFJJBDCU6Z; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -405,7 +405,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - MARKETING_VERSION = 1.1.17; + MARKETING_VERSION = 1.1.18; 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 = 2; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = SFJJBDCU6Z; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -548,7 +548,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - MARKETING_VERSION = 1.1.17; + MARKETING_VERSION = 1.1.18; 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 = 2; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = SFJJBDCU6Z; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -583,7 +583,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - MARKETING_VERSION = 1.1.17; + MARKETING_VERSION = 1.1.18; PRODUCT_BUNDLE_IDENTIFIER = com.aitrainer.app; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; diff --git a/lib/bloc/menu/menu_bloc.dart b/lib/bloc/menu/menu_bloc.dart index bb4bba2..5673052 100644 --- a/lib/bloc/menu/menu_bloc.dart +++ b/lib/bloc/menu/menu_bloc.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:collection'; -import 'package:aitrainer_app/bloc/tutorial/tutorial_bloc.dart'; import 'package:aitrainer_app/model/cache.dart'; import 'package:aitrainer_app/model/exercise_ability.dart'; import 'package:aitrainer_app/model/workout_menu_tree.dart'; diff --git a/lib/bloc/test_set_execute/test_set_execute_bloc.dart b/lib/bloc/test_set_execute/test_set_execute_bloc.dart index 4f6e7a6..ca6c6cd 100644 --- a/lib/bloc/test_set_execute/test_set_execute_bloc.dart +++ b/lib/bloc/test_set_execute/test_set_execute_bloc.dart @@ -309,7 +309,6 @@ class TestSetExecuteBloc extends Bloc<TestSetExecuteEvent, TestSetExecuteState> bool existsActivePlan() { final bool exists = exercisePlan != null && exercisePlanDetails != null && exercisePlanDetails!.isNotEmpty; - print("Exists active plan: $exists"); return exists; } diff --git a/lib/bloc/training_plan/training_plan_bloc.dart b/lib/bloc/training_plan/training_plan_bloc.dart new file mode 100644 index 0000000..ae6f378 --- /dev/null +++ b/lib/bloc/training_plan/training_plan_bloc.dart @@ -0,0 +1,160 @@ +import 'dart:async'; + +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'; + +part 'training_plan_event.dart'; +part 'training_plan_state.dart'; + +class TrainingPlanBloc extends Bloc<TrainingPlanEvent, TrainingPlanState> { + final TrainingPlanRepository trainingPlanRepository; + final MenuBloc menuBloc; + TrainingPlanBloc({required this.trainingPlanRepository, required this.menuBloc}) : super(TrainingPlanInitial()); + + CustomerTrainingPlan? myPlan; + bool started = false; + + CustomerTrainingPlan? getMyPlan() => this.myPlan; + setMyPlan(CustomerTrainingPlan myPlan) => this.myPlan = myPlan; + + @override + Stream<TrainingPlanState> mapEventToState(TrainingPlanEvent event) async* { + try { + if (event is TrainingPlanActivate) { + yield TrainingPlanLoading(); + myPlan = await trainingPlanRepository.activateTrainingPlan(event.trainingPlanId); + Cache().myTrainingPlan = myPlan; + await Cache().saveMyTrainingPlan(); + yield TrainingPlanFinished(); + } else if (event is TrainingPlanWeightChange) { + yield TrainingPlanLoading(); + event.detail.weight = event.weight; + + yield TrainingPlanReady(); + } else if (event is TrainingPlanRepeatsChange) { + yield TrainingPlanLoading(); + + event.detail.repeats = event.repeats; + + yield TrainingPlanReady(); + } else if (event is TrainingPlanSaveExercise) { + yield TrainingPlanLoading(); + + event.detail.state = ExercisePlanDetailState.inProgress; + final Exercise exercise = Exercise(); + exercise.customerId = Cache().userLoggedIn!.customerId!; + exercise.exerciseTypeId = event.detail.exerciseTypeId; + exercise.quantity = event.detail.repeats!.toDouble(); + exercise.unit = event.detail.exerciseType!.unit; + exercise.unitQuantity = event.detail.weight; + exercise.dateAdd = DateTime.now(); + event.detail.exercises.add(exercise); + if (event.detail.exercises.length >= event.detail.set!) { + event.detail.state = ExercisePlanDetailState.finished; + } else if (event.detail.exercises.length >= 0) { + event.detail.state = ExercisePlanDetailState.inProgress; + } + + exercise.trainingPlanDetailsId = myPlan!.trainingPlanId; + + // save Exercise + await ExerciseApi().addExercise(exercise); + Cache().addExercise(exercise); + + Cache().myTrainingPlan = myPlan; + await Cache().saveMyTrainingPlan(); + yield TrainingPlanReady(); + } + } on Exception catch (e) { + yield TrainingPlanError(message: e.toString()); + } + } + + CustomerTrainingPlanDetails? getTrainingPlanDetail(int trainingPlanDetailsId) { + CustomerTrainingPlanDetails? detail; + if (myPlan == null || myPlan!.details.isEmpty) { + return null; + } + + for (final det in this.myPlan!.details) { + if (det.customerTrainingPlanDetailsId == trainingPlanDetailsId) { + detail = det; + break; + } + } + + return detail; + } + + int? getActualWorkoutTreeId(int exerciseTypeId) { + final WorkoutMenuTree? workoutTree = this.menuBloc.menuTreeRepository.getMenuItemByExerciseTypeId(exerciseTypeId); + if (workoutTree == null) { + return null; + } + return workoutTree.id; + } + + String getActualImageName(int exerciseTypeId) { + if (exerciseTypeId <= 0) { + return ""; + } + final WorkoutMenuTree? workoutTree = this.menuBloc.menuTreeRepository.getMenuItemByExerciseTypeId(exerciseTypeId); + if (workoutTree == null) { + return ""; + } + + return workoutTree.imageName; + } + + CustomerTrainingPlanDetails? getNext() { + if (myPlan == null || myPlan!.details.isEmpty) { + return null; + } + + CustomerTrainingPlanDetails? next; + int minStep = 99; + for (final detail in this.myPlan!.details) { + if (!detail.state.equalsTo(ExercisePlanDetailState.finished)) { + if (detail.exercises.isEmpty) { + next = detail; + minStep = 1; + break; + } else { + final int step = detail.exercises.length; + if (step < minStep) { + next = detail; + minStep = step; + if (detail.parallel != true) { + break; + } + } + } + } + } + + return next; + } + + bool isStarted() { + if (myPlan == null || myPlan!.details.isEmpty) { + return false; + } + + if (myPlan!.details[0].state == ExercisePlanDetailState.start) { + return false; + } else { + return true; + } + } +} diff --git a/lib/bloc/training_plan/training_plan_event.dart b/lib/bloc/training_plan/training_plan_event.dart new file mode 100644 index 0000000..b4efe50 --- /dev/null +++ b/lib/bloc/training_plan/training_plan_event.dart @@ -0,0 +1,54 @@ +part of 'training_plan_bloc.dart'; + +abstract class TrainingPlanEvent extends Equatable { + const TrainingPlanEvent(); + + @override + List<Object> get props => []; +} + +class TrainingPlanLoad extends TrainingPlanEvent { + const TrainingPlanLoad(); +} + +class TrainingPlanActivate extends TrainingPlanEvent { + final int trainingPlanId; + const TrainingPlanActivate({required this.trainingPlanId}); + + @override + List<Object> get props => [trainingPlanId]; +} + +class TrainingPlanWeightChange extends TrainingPlanEvent { + final CustomerTrainingPlanDetails detail; + final double weight; + const TrainingPlanWeightChange({required this.weight, required this.detail}); + + @override + List<Object> get props => [weight, detail]; +} + +class TrainingPlanRepeatsChange extends TrainingPlanEvent { + final CustomerTrainingPlanDetails detail; + final int repeats; + const TrainingPlanRepeatsChange({required this.repeats, required this.detail}); + + @override + List<Object> get props => [repeats, detail]; +} + +class TrainingPlanSaveExercise extends TrainingPlanEvent { + final CustomerTrainingPlanDetails detail; + const TrainingPlanSaveExercise({required this.detail}); + + @override + List<Object> get props => [detail]; +} + +class TrainingPlanFinishTraining extends TrainingPlanEvent { + const TrainingPlanFinishTraining(); +} + +class TrainingPlanSkipExercise extends TrainingPlanEvent { + const TrainingPlanSkipExercise(); +} diff --git a/lib/bloc/training_plan/training_plan_state.dart b/lib/bloc/training_plan/training_plan_state.dart new file mode 100644 index 0000000..9d30710 --- /dev/null +++ b/lib/bloc/training_plan/training_plan_state.dart @@ -0,0 +1,32 @@ +part of 'training_plan_bloc.dart'; + +abstract class TrainingPlanState extends Equatable { + const TrainingPlanState(); + + @override + List<Object> get props => []; +} + +class TrainingPlanInitial extends TrainingPlanState { + const TrainingPlanInitial(); +} + +class TrainingPlanLoading extends TrainingPlanState { + const TrainingPlanLoading(); +} + +class TrainingPlanReady extends TrainingPlanState { + const TrainingPlanReady(); +} + +class TrainingPlanFinished extends TrainingPlanState { + const TrainingPlanFinished(); +} + +class TrainingPlanError extends TrainingPlanState { + final String message; + const TrainingPlanError({required this.message}); + + @override + List<Object> get props => [message]; +} diff --git a/lib/main.dart b/lib/main.dart index e4e6dd1..faabc77 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,9 +1,11 @@ import 'dart:async'; 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'; import 'package:aitrainer_app/service/firebase_api.dart'; import 'package:aitrainer_app/util/session.dart'; @@ -24,6 +26,7 @@ import 'package:aitrainer_app/view/exercise_plan_custom_detail_add_page.dart'; import 'package:aitrainer_app/view/faq_page.dart'; import 'package:aitrainer_app/view/login.dart'; import 'package:aitrainer_app/view/exercise_new_page.dart'; +import 'package:aitrainer_app/view/my_training_plans_page.dart'; import 'package:aitrainer_app/view/mydevelopment_body_page.dart'; import 'package:aitrainer_app/view/mydevelopment_muscle_page.dart'; import 'package:aitrainer_app/view/mydevelopment_page.dart'; @@ -37,6 +40,9 @@ import 'package:aitrainer_app/view/test_set_control.dart'; import 'package:aitrainer_app/view/test_set_edit.dart'; import 'package:aitrainer_app/view/test_set_execute.dart'; import 'package:aitrainer_app/view/test_set_new.dart'; +import 'package:aitrainer_app/view/training_plan_activate_page.dart'; +import 'package:aitrainer_app/view/training_plan_execute_page.dart'; +import 'package:aitrainer_app/view/training_plan_exercise.dart'; 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'; @@ -178,6 +184,10 @@ Future<Null> main() async { create: (BuildContext context) => TestSetExecuteBloc(), ), BlocProvider<TutorialBloc>(create: (BuildContext context) => TutorialBloc(tutorialName: ActivityDone.tutorialBasic.toStr())), + BlocProvider<TrainingPlanBloc>(create: (context) { + final MenuBloc menuBloc = BlocProvider.of<MenuBloc>(context); + return TrainingPlanBloc(menuBloc: menuBloc, trainingPlanRepository: TrainingPlanRepository()); + }), ], child: WorkoutTestApp(), )); @@ -262,6 +272,10 @@ class WorkoutTestApp extends StatelessWidget { 'testSetNew': (context) => TestSetNew(), 'testSetControl': (context) => TestSetControl(), 'faqPage': (context) => FaqPage(), + 'myTrainingPlans': (context) => MyTrainingPlans(), + 'myTrainingPlanActivate': (context) => TrainingPlanActivatePage(), + 'myTrainingPlanExecute': (context) => TrainingPlanExecutePage(), + 'myTrainingPlanExercise': (context) => TrainingPlanExercise(), }, initialRoute: 'home', title: 'WorkoutTest', diff --git a/lib/model/cache.dart b/lib/model/cache.dart index aaeb40f..4fc677a 100644 --- a/lib/model/cache.dart +++ b/lib/model/cache.dart @@ -2,6 +2,7 @@ import 'dart:collection'; import 'dart:convert'; import 'package:aitrainer_app/model/customer.dart'; import 'package:aitrainer_app/model/customer_activity.dart'; +import 'package:aitrainer_app/model/customer_training_plan.dart'; import 'package:aitrainer_app/model/description.dart'; import 'package:aitrainer_app/model/evaluation.dart'; import 'package:aitrainer_app/model/exercise_plan.dart'; @@ -17,6 +18,7 @@ import 'package:aitrainer_app/model/product_test.dart'; import 'package:aitrainer_app/model/property.dart'; import 'package:aitrainer_app/model/purchase.dart'; import 'package:aitrainer_app/model/sport.dart'; +import 'package:aitrainer_app/model/training_plan.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'; @@ -104,6 +106,7 @@ class Cache with Logging { static final String activeExercisePlanKey = "active_exercise_plan"; static final String activeExercisePlanDateKey = "active_exercise_plan_date"; static final String activeExercisePlanDetailsKey = "active_exercise_details_plan"; + static final String myTrainingPlanKey = "myTrainingPlan"; static String baseUrlLive = 'https://aitrainer.info:8943/api/'; static String baseUrlTest = 'https://aitrainer.info:8843/api/'; @@ -137,14 +140,19 @@ class Cache with Logging { List<ExercisePlanTemplate> _exercisePlanTemplates = []; ExercisePlan? activeExercisePlan; + CustomerTrainingPlan? myTrainingPlan; List<ExercisePlanDetail>? activeExercisePlanDetails; List<ExerciseDevice>? _devices; + List<CustomerExerciseDevice>? _customerDevices; List<CustomerActivity>? _customerActivities; + List<CustomerTrainingPlan>? _customerTrainingPlans; + List<Tutorial>? _tutorials; List<Description>? _descriptions; List<Faq>? _faqs; + List<TrainingPlan>? _trainingPlans; LinkedHashMap<int, ExercisePlanDetail> _myExercisesPlanDetails = LinkedHashMap<int, ExercisePlanDetail>(); @@ -209,6 +217,33 @@ class Cache with Logging { sharedPreferences.setString(Cache.activeExercisePlanDateKey, savingDay); } + Future<void> saveMyTrainingPlan() async { + if (myTrainingPlan == null) { + return; + } + String myTrainingPlanJson = JsonEncoder().convert(myTrainingPlan!.toJsonWithDetails()); + + Future<SharedPreferences> prefs = SharedPreferences.getInstance(); + SharedPreferences sharedPreferences; + sharedPreferences = await prefs; + sharedPreferences.setString(Cache.myTrainingPlanKey, myTrainingPlanJson); + } + + Future<void> getMyTrainingPlan() async { + Future<SharedPreferences> prefs = SharedPreferences.getInstance(); + SharedPreferences sharedPreferences; + sharedPreferences = await prefs; + + final String? savedTrainingPlanJson = sharedPreferences.getString(Cache.myTrainingPlanKey); + if (savedTrainingPlanJson == null) { + return; + } + + final Map<String, dynamic> map = JsonDecoder().convert(savedTrainingPlanJson); + print("Training plan: $savedTrainingPlanJson"); + this.myTrainingPlan = CustomerTrainingPlan.fromJsonWithDetails(map); + } + Future<void> deleteActiveExercisePlan() async { Future<SharedPreferences> prefs = SharedPreferences.getInstance(); SharedPreferences sharedPreferences; @@ -644,6 +679,8 @@ class Cache with Logging { await isActivityDonePrefs(activity); }); + await getMyTrainingPlan(); + Cache().startPage = "home"; } @@ -690,4 +727,10 @@ class Cache with Logging { List<Faq>? getFaqs() => this._faqs; setFaqs(List<Faq>? value) => this._faqs = value; + + List<TrainingPlan>? getTrainingPlans() => this._trainingPlans; + setTrainingPlans(List<TrainingPlan>? value) => this._trainingPlans = value; + + List<CustomerTrainingPlan>? getCustomerTrainingPlans() => this._customerTrainingPlans; + setCustomerTrainingPlans(value) => this._customerTrainingPlans = value; } diff --git a/lib/model/customer_training_plan.dart b/lib/model/customer_training_plan.dart new file mode 100644 index 0000000..903e81d --- /dev/null +++ b/lib/model/customer_training_plan.dart @@ -0,0 +1,78 @@ +import 'dart:convert'; +import 'package:intl/intl.dart'; + +import 'package:aitrainer_app/model/customer_training_plan_details.dart'; + +class CustomerTrainingPlan { + int? customerTrainingPlanId; + int? customerId; + int? trainingPlanId; + DateTime? dateAdd; + bool? active; + String? status; + + String? name; + + CustomerTrainingPlan(); + + List<CustomerTrainingPlanDetails> details = []; + + CustomerTrainingPlan.fromJson(Map json) { + this.customerTrainingPlanId = json['customerTrainingPlanId']; + this.customerId = json['customerId']; + this.trainingPlanId = json['trainingPlanId']; + this.dateAdd = DateTime.parse(json['dateAdd']); + this.active = json['active']; + this.status = json['status']; + this.name = json['name']; + } + + CustomerTrainingPlan.fromJsonWithDetails(Map json) { + this.customerTrainingPlanId = json['customerTrainingPlanId']; + this.customerId = json['customerId']; + this.trainingPlanId = json['trainingPlanId']; + this.dateAdd = json['dateAdd'] != null ? DateTime.parse(json['dateAdd']) : DateTime.now(); + this.active = json['active']; + this.status = json['status']; + this.name = json['name']; + + 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]}\""); + + jsonDetails = jsonDetails.replaceAll(r'\"null\"', 'null'); + jsonDetails = jsonDetails.replaceAll(r'\"false\"', 'false'); + jsonDetails = jsonDetails.replaceAll(r'\"true\"', 'true'); + + Iterable iterable = jsonDecode(jsonDetails); + this.details = iterable.map((detail) => CustomerTrainingPlanDetails.fromJsonWithExerciseList(detail)).toList(); + } on Exception catch (e) { + print("JsonDecode error " + e.toString()); + } + } + + Map<String, dynamic> toJson() => { + "customerTrainingPlanId": this.customerTrainingPlanId, + "customerId": this.customerId, + "trainingPlanId": this.trainingPlanId, + "dateAdd": DateFormat('yyyy-MM-dd HH:mm:ss').format(this.dateAdd!), + "name": this.name, + "active": this.active, + "status": this.status + }; + + Map<String, dynamic> toJsonWithDetails() => { + "customerTrainingPlanId": this.customerTrainingPlanId, + "customerId": this.customerId, + "trainingPlanId": this.trainingPlanId, + "dateAdd": DateFormat('yyyy-MM-dd HH:mm:ss').format(this.dateAdd!), + "name": this.name, + "active": this.active, + "status": this.status, + 'details': details.isEmpty ? [].toString() : details.map((detail) => detail.toJsonWithExercises()).toList().toString(), + }; + + @override + String toString() => this.toJson().toString(); +} diff --git a/lib/model/customer_training_plan_details.dart b/lib/model/customer_training_plan_details.dart new file mode 100644 index 0000000..46b8504 --- /dev/null +++ b/lib/model/customer_training_plan_details.dart @@ -0,0 +1,114 @@ +import 'dart:convert'; + +import 'package:aitrainer_app/model/cache.dart'; +import 'package:aitrainer_app/model/exercise.dart'; +import 'package:aitrainer_app/model/exercise_plan_detail.dart'; +import 'package:aitrainer_app/model/exercise_type.dart'; + +class CustomerTrainingPlanDetails { + /// customerTrainingPlanDetails + int? customerTrainingPlanDetailsId; + + /// exerciseTypeId + int? exerciseTypeId; + + /// set + int? set; + + /// repeats + int? repeats; + + /// weight + double? weight; + + int? restingTime; + bool? parallel; + String? day; + + /// exerciseType + ExerciseType? exerciseType; + + ExercisePlanDetailState state = ExercisePlanDetailState.start; + + List<Exercise> exercises = []; + + CustomerTrainingPlanDetails(); + + CustomerTrainingPlanDetails.fromJson(Map json) { + this.customerTrainingPlanDetailsId = json['customerTrainingPlanDetailsId']; + this.exerciseTypeId = json['exerciseTypeId']; + this.set = json['set']; + this.repeats = json['repeats']; + this.weight = json['weight']; + this.restingTime = json['restingTime']; + this.parallel = json['parallel']; + this.day = json['day']; + } + + CustomerTrainingPlanDetails.fromJsonWithExerciseList(Map json) { + this.customerTrainingPlanDetailsId = json['customerTrainingPlanDetailsId'] == "null" || json['customerTrainingPlanDetailsId'] == null + ? 0 + : json['customerTrainingPlanDetailsId']; + this.exerciseTypeId = json['exerciseTypeId']; + this.set = json['set']; + this.repeats = json['repeats']; + this.weight = json['weight']; + this.restingTime = json['restingTime']; + this.parallel = json['parallel'] == "false" + ? false + : json['parallel'] == "true" + ? true + : null; + this.day = json['day']; + try { + Iterable iterable = json['exercises']; + this.exercises = iterable.map((exercise) => Exercise.fromJson(exercise)).toList(); + } on Exception catch (e) { + print("JsonDecode error " + e.toString()); + } + + if (exercises.length >= this.set!) { + this.state = ExercisePlanDetailState.finished; + } else if (exercises.length > 0) { + this.state = ExercisePlanDetailState.inProgress; + } else { + this.state = ExercisePlanDetailState.start; + } + this.exerciseType = Cache().getExerciseTypeById(exerciseTypeId!); + } + + ExerciseType? getExerciseType() => exerciseType; + + Map<String, dynamic> toJson() => { + "customerTrainingPlanDetailsId": this.customerTrainingPlanDetailsId, + "exerciseTypeId": this.exerciseTypeId, + "set": this.set, + "repeats": this.repeats, + "weight": this.weight, + "restingTime": this.restingTime, + "parallel": this.parallel, + "day": this.day == null ? '1.' : this.day, + }; + + Map<String, dynamic> toJsonWithExercises() { + final Map<String, dynamic> jsonMap = { + "customerTrainingPlanDetailsId": this.customerTrainingPlanDetailsId, + "exerciseTypeId": this.exerciseTypeId, + "set": this.set, + "repeats": this.repeats, + "weight": this.weight, + "restingTime": this.restingTime, + "parallel": this.parallel, + 'exercises': exercises.isEmpty ? [].toString() : exercises.map((exercise) => exercise.toJson()).toList().toString(), + }; + if (this.day != null && this.day!.isNotEmpty) { + jsonMap["day"] = this.day; + } + + //print("Detail $jsonMap"); + return jsonMap; + } + + @override + String toString() => this.toJson().toString(); +} diff --git a/lib/model/customer_training_plan_exercise.dart b/lib/model/customer_training_plan_exercise.dart new file mode 100644 index 0000000..5c05fb6 --- /dev/null +++ b/lib/model/customer_training_plan_exercise.dart @@ -0,0 +1,30 @@ +class CustomerTrainingPlanExercise { + int? customerTrainingPlanExerciseId; + int? customerTrainingPlanDetailsId; + int? customerId; + int? exerciseId; + double? weight; + int? repeats; + + CustomerTrainingPlanExercise(); + + CustomerTrainingPlanExercise.fromJson(Map json) { + this.customerTrainingPlanExerciseId = json['customerTrainingPlanExerciseId']; + this.customerTrainingPlanDetailsId = json['customerTrainingPlanDetailsId']; + this.customerId = json['customerId']; + this.exerciseId = json['exerciseId']; + this.repeats = json['repeats']; + this.weight = json['weight']; + } + + Map<String, dynamic> toJson() => { + "customerTrainingPlanDetailsId": this.customerTrainingPlanDetailsId, + "customerId": this.customerId, + "exerciseId": this.exerciseId, + "weight": this.weight, + "repeats": this.repeats + }; + + @override + String toString() => this.toJson().toString(); +} diff --git a/lib/model/exercise.dart b/lib/model/exercise.dart index 0a0301e..79ee76e 100644 --- a/lib/model/exercise.dart +++ b/lib/model/exercise.dart @@ -9,6 +9,7 @@ class Exercise { double? unitQuantity; DateTime? dateAdd; int? exercisePlanDetailId; + int? trainingPlanDetailsId; String? datePart; double? calculated; @@ -26,6 +27,8 @@ class Exercise { this.dateAdd = DateTime.parse(json['dateAdd']); this.datePart = DateFormat('yyyy-MM-dd').format(this.dateAdd!); this.calculated = quantity; + this.exercisePlanDetailId = json['exercisePlanDetailId'] == "null" ? null : json['exercisePlanDetailId']; + this.trainingPlanDetailsId = json['trainingPlanDetailsId'] == "null" ? null : json['trainingPlanDetailsId']; } Map<String, dynamic> toJson() => { @@ -36,6 +39,7 @@ class Exercise { "unitQuantity": unitQuantity, "dateAdd": DateFormat('yyyy-MM-dd HH:mm:ss').format(this.dateAdd!), "exercisePlanDetailId": exercisePlanDetailId, + "trainingPlanDetailsId": trainingPlanDetailsId, }; Map<String, dynamic> toJsonDatePart() => { diff --git a/lib/model/exercise_tree.dart b/lib/model/exercise_tree.dart index 5f8e91f..b2ad6b0 100644 --- a/lib/model/exercise_tree.dart +++ b/lib/model/exercise_tree.dart @@ -18,7 +18,12 @@ class ExerciseTree { late String nameTranslation; /// sort - late int? sort; + int? sort; + + String? internalName; + + String? description; + String? descriptionTranslation; ExerciseTree(); @@ -29,7 +34,12 @@ class ExerciseTree { this.imageUrl = json['imageUrl']; this.active = json['active']; this.nameTranslation = json['translations'] != null && (json['translations']).length > 0 ? json['translations'][0]['name'] : this.name; + this.descriptionTranslation = + json['translations'] != null && (json['translations']).length > 0 && json['translations'][0]['description'] != null + ? json['translations'][0]['description'] + : this.description; this.sort = 99; + this.internalName = json['internalName']; } Map<String, dynamic> toJson() { @@ -37,13 +47,18 @@ class ExerciseTree { "treeId": treeId, "parentId": parentId, "name": name, + "description": description, "imageUrl": imageUrl, "active": active.toString(), "nameTranslation": nameTranslation, + "descriptionTranslation": descriptionTranslation, "sort": sort, }; } + @override + String toString() => this.toJson().toString(); + ExerciseTree copy(int parentId) { ExerciseTree newTree = ExerciseTree(); newTree.treeId = this.treeId; diff --git a/lib/model/training_plan.dart b/lib/model/training_plan.dart new file mode 100644 index 0000000..ed7619f --- /dev/null +++ b/lib/model/training_plan.dart @@ -0,0 +1,71 @@ +import 'dart:collection'; + +import 'package:aitrainer_app/model/training_plan_detail.dart'; + +class TrainingPlan { + late int trainingPlanId; + late String type; + late String name; + late String internalName; + late String description; + late bool free; + int? treeId; + + HashMap<String, String> nameTranslations = HashMap(); + HashMap<String, String> descriptionTranslations = HashMap(); + + List<TrainingPlanDetail>? details; + + TrainingPlan.fromJson(Map<String, dynamic> json) { + this.trainingPlanId = json['trainingPlanId']; + this.name = json['name']; + this.type = json['type']; + this.internalName = json['internalName']; + this.description = json['description']; + this.free = json['free']; + this.treeId = json['treeId']; + + nameTranslations['en'] = name; + descriptionTranslations['en'] = description; + if (json['translations'] != null && json['translations'].length > 0) { + json['translations'].forEach((translation) { + nameTranslations[translation['languageCode']] = translation['nameTranslation']; + descriptionTranslations[translation['languageCode']] = translation['descriptionTranslation']; + }); + } + + if (json['details'] != null && json['details'].length > 0) { + details = json['details'].map<TrainingPlanDetail>((detail) => TrainingPlanDetail.fromJson(detail)).toList(); + if (details != null && details!.isNotEmpty) { + details!.sort((a, b) { + if (a.sort == 0 || b.sort == 0) { + if (a.trainingPlanDetailId <= b.trainingPlanDetailId) { + return -1; + } else { + return 1; + } + } + if (a.sort <= b.sort) { + return -1; + } else { + return 1; + } + }); + } + } + } + + Map<String, dynamic> toJson() => { + "trainingPlanId": this.trainingPlanId, + "treeId": this.treeId, + "name": this.name, + "type": this.type, + "internalName": this.internalName, + "free": this.free, + "description": this.description, + "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 new file mode 100644 index 0000000..d97a24e --- /dev/null +++ b/lib/model/training_plan_detail.dart @@ -0,0 +1,40 @@ +class TrainingPlanDetail { + late int trainingPlanDetailId; + late 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; + + TrainingPlanDetail.fromJson(Map<String, dynamic> json) { + this.trainingPlanDetailId = json['trainingPlanDetailId']; + this.trainingPlanId = json['trainingPlanId']; + this.exerciseTypeId = json['exerciseTypeId']; + this.sort = json['sort']; + this.set = json['set']; + this.repeats = json['repeats']; + this.weight = json['weight']; + this.restingTime = json['restingTime']; + this.parallel = json['parallel']; + this.day = json['day']; + } + + Map<String, dynamic> toJson() => { + "trainingPlanDetailId": this.trainingPlanDetailId, + "trainingPlanId": this.trainingPlanId, + "exerciseType": this.exerciseTypeId, + "sort": this.sort, + "repeats": this.repeats, + "weight": this.weight, + "restingTime": this.restingTime, + "parallel": this.parallel, + "day": this.day, + }; + + @override + String toString() => this.toJson().toString(); +} diff --git a/lib/repository/training_plan_repository.dart b/lib/repository/training_plan_repository.dart new file mode 100644 index 0000000..1d9294d --- /dev/null +++ b/lib/repository/training_plan_repository.dart @@ -0,0 +1,141 @@ +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/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/service/training_plan_service.dart'; +import 'package:aitrainer_app/util/common.dart'; + +class TrainingPlanRepository { + ExerciseTree? parentTree; + List<TrainingPlan> getPlansByParent(String parent) { + final List<TrainingPlan> resultList = []; + final List<ExerciseTree>? exerciseTree = Cache().getExerciseTree(); + int? parentId; + if (exerciseTree != null) { + exerciseTree.forEach((element) { + if (element.internalName == parent) { + parentId = element.treeId; + parentTree = element; + } + }); + } + + final List<TrainingPlan>? plans = Cache().getTrainingPlans(); + if (plans != null && parentId != null) { + plans.forEach((element) { + if (element.treeId == parentId) { + resultList.add(element); + } + }); + } + return resultList; + } + + /// 1. deactivate old training plans - update all + + /// 2. calculate customer_training_plan_details weights / repleats + /// 3. create new customer_training_plan + + Future<CustomerTrainingPlan?> activateTrainingPlan(int trainingPlanId) async { + print(" **** Activate Plan: $trainingPlanId"); + // 1. deactivate + if (Cache().getCustomerTrainingPlans() != null) { + Cache().getCustomerTrainingPlans()!.forEach((plan) { + plan.active = false; + if (plan.customerTrainingPlanId != null) { + TrainingPlanApi().updateCustomerTrainingPlan(plan, plan.customerTrainingPlanId!); + } + }); + } + + CustomerTrainingPlan plan = CustomerTrainingPlan(); + plan.customerId = Cache().userLoggedIn!.customerId; + plan.trainingPlanId = trainingPlanId; + plan.active = true; + plan.status = "open"; + plan.dateAdd = DateTime.now(); + plan.name = getTrainingPlanById(trainingPlanId)!.nameTranslations["hu"]; + + TrainingPlan? trainingPlan = this.getTrainingPlanById(trainingPlanId); + if (trainingPlan == null || trainingPlan.details == null) { + return null; + } + + // 3 calculate weights + trainingPlan.details!.forEach((elem) { + CustomerTrainingPlanDetails detail = CustomerTrainingPlanDetails(); + detail.exerciseTypeId = elem.exerciseTypeId; + detail.repeats = elem.repeats; + detail.set = elem.set; + detail.day = elem.day; + detail.parallel = elem.parallel; + detail.restingTime = elem.restingTime; + detail.exerciseType = Cache().getExerciseTypeById(detail.exerciseTypeId!); + if (detail.exerciseType!.unitQuantityUnit != null) { + detail = getCalculatedWeightRepeats(elem.exerciseTypeId, detail); + } else { + detail.weight = 0; + } + //print("Detail $detail exerciseType: ${detail.exerciseType}"); + + detail.state = ExercisePlanDetailState.start; + plan.details.add(detail); + }); + + Cache().myTrainingPlan = plan; + + //TrainingPlanApi().saveCustomerTrainingPlan(plan); + return plan; + } + + TrainingPlan? getTrainingPlanById(int trainingPlanId) { + TrainingPlan? plan; + if (Cache().getTrainingPlans() == null) { + return plan; + } + + for (var trainingPlan in Cache().getTrainingPlans()!) { + if (trainingPlan.trainingPlanId == trainingPlanId) { + plan = trainingPlan; + break; + } + } + + return plan; + } + + CustomerTrainingPlanDetails getCalculatedWeightRepeats(int exerciseTypeId, CustomerTrainingPlanDetails detail) { + double weight = -1; + if (Cache().getExercises() == null) { + detail.weight = weight; + return detail; + } + + Exercise? lastExercise1RM; + Cache().getExercises()!.forEach((exercise) { + if (exercise.exercisePlanDetailId == 0 && exercise.exerciseTypeId == exerciseTypeId) { + detail.weight = weight; + lastExercise1RM = exercise; + } + }); + + if (lastExercise1RM == null || lastExercise1RM!.unitQuantity == null) { + detail.weight = weight; + return detail; + } + + double oneRepMax = Common.calculate1RM(lastExercise1RM!.unitQuantity!, lastExercise1RM!.quantity!); + //print("Exercise $exerciseTypeId - 1RM : $oneRepMax"); + weight = oneRepMax * Common.get1RMPercent(detail.repeats!); + //print("Exercise $exerciseTypeId - weight : $weight"); + weight = Common.roundWeight(weight); + + detail.repeats = Common.calculateQuantityByChangedWeight(oneRepMax, weight, detail.repeats!.toDouble()); + + detail.weight = weight; + return detail; + } +} diff --git a/lib/service/exercise_tree_service.dart b/lib/service/exercise_tree_service.dart index 406e35a..8a511cc 100644 --- a/lib/service/exercise_tree_service.dart +++ b/lib/service/exercise_tree_service.dart @@ -13,19 +13,19 @@ class ExerciseTreeApi with Logging { Future<List<ExerciseTree>> getExerciseTree() async { final String body = await _client.get("exercise_tree", ""); Iterable json = jsonDecode(body); - List<ExerciseTree>? exerciseTree = json.map((exerciseTree) => ExerciseTree.fromJson(exerciseTree)).toList(); + List<ExerciseTree>? exerciseTrees = json.map((exerciseTree) => ExerciseTree.fromJson(exerciseTree)).toList(); - exerciseTree = await getExerciseTreeParents(exerciseTree); + exerciseTrees = await getExerciseTreeParents(exerciseTrees); - await Future.forEach(exerciseTree, (element) async { + await Future.forEach(exerciseTrees, (element) async { ExerciseTree exerciseTree = element as ExerciseTree; exerciseTree.imageUrl = await buildImage(exerciseTree.imageUrl, exerciseTree.treeId); }); - exerciseTree = await getExerciseTreeParents(exerciseTree); - log("ExerciseTree downloaded"); - Cache().setExerciseTree(exerciseTree); + exerciseTrees = await getExerciseTreeParents(exerciseTrees); + log("ExerciseTree downloaded $exerciseTrees"); + Cache().setExerciseTree(exerciseTrees); - return exerciseTree; + return exerciseTrees; } Future<String> buildImage(String imageUrl, int treeId) async { diff --git a/lib/service/package_service.dart b/lib/service/package_service.dart index d193279..14e8918 100644 --- a/lib/service/package_service.dart +++ b/lib/service/package_service.dart @@ -18,6 +18,7 @@ import 'package:aitrainer_app/model/product.dart'; import 'package:aitrainer_app/model/product_test.dart'; import 'package:aitrainer_app/model/property.dart'; import 'package:aitrainer_app/model/purchase.dart'; +import 'package:aitrainer_app/model/training_plan.dart'; import 'package:aitrainer_app/model/tutorial.dart'; import 'package:aitrainer_app/service/api.dart'; import 'package:aitrainer_app/service/exercise_type_service.dart'; @@ -86,6 +87,11 @@ class PackageApi { final List<Faq>? faqs = json.map((faq) => Faq.fromJson(faq)).toList(); //print("Faq: $faqs"); Cache().setFaqs(faqs); + } else if (headRecord[0] == "TrainingPlan") { + final Iterable json = jsonDecode(headRecord[1]); + final List<TrainingPlan>? plans = json.map((plan) => TrainingPlan.fromJson(plan)).toList(); + + Cache().setTrainingPlans(plans); } }); @@ -95,6 +101,7 @@ class PackageApi { ExerciseTree tree = element as ExerciseTree; tree.imageUrl = await ExerciseTreeApi().buildImage(tree.imageUrl, tree.treeId); }); + //print("tree: $exerciseTree"); Cache().setExerciseTree(exerciseTree); return; diff --git a/lib/service/training_plan_service.dart b/lib/service/training_plan_service.dart new file mode 100644 index 0000000..e0c2667 --- /dev/null +++ b/lib/service/training_plan_service.dart @@ -0,0 +1,34 @@ +import 'dart:convert'; + +import 'package:aitrainer_app/model/customer_training_plan.dart'; +import 'package:aitrainer_app/model/customer_training_plan_exercise.dart'; +import 'package:aitrainer_app/service/api.dart'; +import 'package:aitrainer_app/service/logging.dart'; + +class TrainingPlanApi with Logging { + final APIClient _client = new APIClient(); + + Future<CustomerTrainingPlan> saveCustomerTrainingPlan(CustomerTrainingPlan plan) async { + String body = JsonEncoder().convert(plan.toJson()); + log(" ===== saving customer training plan:" + body); + final String response = await _client.post("customer_training_plan/", body); + final CustomerTrainingPlan saved = CustomerTrainingPlan.fromJson(jsonDecode(response)); + return saved; + } + + Future<CustomerTrainingPlanExercise> saveCustomerTrainingPlanExercise(CustomerTrainingPlanExercise planExercise) async { + String body = JsonEncoder().convert(planExercise.toJson()); + log(" ===== saving customer training plan exercise:" + body); + final String response = await _client.post("customer_training_plan_exercise/", body); + final CustomerTrainingPlanExercise saved = CustomerTrainingPlanExercise.fromJson(jsonDecode(response)); + return saved; + } + + Future<CustomerTrainingPlan> updateCustomerTrainingPlan(CustomerTrainingPlan plan, int customerTrainingPlanId) async { + String body = JsonEncoder().convert(plan.toJson()); + log(" ===== update customer training plan:" + body); + final String response = await _client.post("customer_training_plan/update/$customerTrainingPlanId", body); + final CustomerTrainingPlan saved = CustomerTrainingPlan.fromJson(jsonDecode(response)); + return saved; + } +} diff --git a/lib/util/common.dart b/lib/util/common.dart index 83f1457..76b7a18 100644 --- a/lib/util/common.dart +++ b/lib/util/common.dart @@ -1,5 +1,4 @@ import 'dart:convert'; - import 'package:aitrainer_app/util/app_language.dart'; import 'package:aitrainer_app/model/cache.dart'; import 'package:aitrainer_app/model/exercise_type.dart'; @@ -144,4 +143,62 @@ mixin Common { value = value.replaceAll(RegExp(r'[^0-9.]'), ""); return value; } + + static double calculate1RM(double weight, double repeat) { + if (weight == 0 || repeat == 0) { + return 0; + } + + double rmWendler = weight * repeat * 0.0333 + weight; + double rmOconner = weight * (1 + repeat / 40); + //print("Weight: $weight repeat: $repeat, $rmWendler, Oconner: $rmOconner"); + double average = (rmWendler + rmOconner) / 2; + + return average; + } + + static double get1RMPercent(int repeats) { + double percent = 1; + + if (repeats >= 35) { + percent = 0.50; + } else if (repeats > 12) { + percent = (100 - 2 * repeats) / 100; + } else { + percent = (100.0 - 2 * repeats) / 100; + } + //print("1RM Percent: $percent repeats: $repeats"); + return percent; + } + + static double roundWeight(double weight) { + double rounded = weight.round().toDouble(); + + if (weight > 35) { + final double remainder = weight % 5; + if (remainder < 1.25) { + rounded = ((weight / 5).floor() * 5).toDouble(); + } else if (remainder > 1.25 && remainder <= 2.5) { + rounded = (weight / 5).floor() * 5 + 2.5; + } else if (remainder > 2.5 && remainder < 3.75) { + rounded = (weight / 5).floor() * 5 + 2.5; + } else { + rounded = (((weight / 5).ceil() * 5) + 5).toDouble(); + } + } + + return rounded; + } + + static int calculateQuantityByChangedWeight(double initialRM, double weight, double repeat) { + final double rmWendler = weight * repeat * 0.0333 + weight; + final double rmOconner = weight * (1 + repeat / 40); + //print("Weight: $weight oneRepQuantity: $repeat, $rmWendler, Oconner: $rmOconner"); + + final double repeatWendler = (rmWendler - weight) / 0.0333 / weight; + final double repeatOconner = (rmOconner / weight - 1) * 40; + final newRepeat = ((repeatOconner + repeatWendler) / 2).ceil(); + //print("Initial 1RM: $initialRM Weight: $weight repeatWendler: $repeatWendler repeat Oconner: $repeatOconner. NEW REPEAT: $newRepeat"); + return newRepeat; + } } diff --git a/lib/view/account.dart b/lib/view/account.dart index 791316d..00976c0 100644 --- a/lib/view/account.dart +++ b/lib/view/account.dart @@ -151,7 +151,7 @@ class AccountPage extends StatelessWidget with Trans { ), devices(context, accountBloc), loginOut(context, accountBloc), - getMyTrainees(context, accountBloc), + //getMyTrainees(context, accountBloc), ]); } diff --git a/lib/view/exercise_new_page.dart b/lib/view/exercise_new_page.dart index 12b0c91..c373ad8 100644 --- a/lib/view/exercise_new_page.dart +++ b/lib/view/exercise_new_page.dart @@ -141,9 +141,9 @@ class _ExerciseNewPageState extends State<ExerciseNewPage> with Trans, Logging { unitQuantityUnit: exerciseBloc.exerciseRepository.exerciseType!.unitQuantityUnit, hasUnitQuantity: exerciseBloc.exerciseRepository.exerciseType!.unitQuantityUnit != null, onQuantityChanged: (value) { - exerciseBloc.add(ExerciseNewQuantityChange(quantity: double.parse(value))); + exerciseBloc.add(ExerciseNewQuantityChange(quantity: value)); }, - onUnitQuantityChanged: (value) => exerciseBloc.add(ExerciseNewQuantityUnitChange(quantity: double.parse(value))), + onUnitQuantityChanged: (value) => exerciseBloc.add(ExerciseNewQuantityUnitChange(quantity: value)), //onSubmit: () => confirmationDialog(exerciseBloc, menuBloc), exerciseTypeId: exerciseType.exerciseTypeId, )), diff --git a/lib/view/my_training_plans_page.dart b/lib/view/my_training_plans_page.dart new file mode 100644 index 0000000..44814e1 --- /dev/null +++ b/lib/view/my_training_plans_page.dart @@ -0,0 +1,125 @@ +import 'dart:collection'; + +import 'package:aitrainer_app/bloc/training_plan/training_plan_bloc.dart'; +import 'package:aitrainer_app/model/cache.dart'; +import 'package:aitrainer_app/service/logging.dart'; +import 'package:aitrainer_app/util/track.dart'; +import 'package:aitrainer_app/util/trans.dart'; +import 'package:aitrainer_app/widgets/app_bar.dart'; +import 'package:aitrainer_app/widgets/bottom_nav.dart'; +import 'package:aitrainer_app/widgets/dialog_common.dart'; +import 'package:aitrainer_app/widgets/image_button.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:modal_progress_hud_nsn/modal_progress_hud_nsn.dart'; + +// ignore: must_be_immutable +class MyTrainingPlans extends StatelessWidget with Trans, Logging { + @override + Widget build(BuildContext context) { + setContext(context); + + return Scaffold( + appBar: AppBarNav(depth: 0), + body: Container( + padding: EdgeInsets.all(10), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage('asset/image/WT_menu_dark.jpg'), + fit: BoxFit.cover, + alignment: Alignment.center, + ), + ), + child: BlocConsumer<TrainingPlanBloc, TrainingPlanState>( + listener: (context, state) { + if (state is TrainingPlanError) { + showDialog( + context: context, + builder: (BuildContext context) { + return DialogCommon( + warning: true, + title: t("Warning"), + descriptions: t(state.message), + text: "OK", + onTap: () => Navigator.of(context).pushNamed("login"), + onCancel: () => { + Navigator.of(context).pop(), + }, + ); + }); + } else if (state is TrainingPlanFinished) { + Navigator.of(context).pop(); + final TrainingPlanBloc bloc = BlocProvider.of<TrainingPlanBloc>(context); + Navigator.of(context).pushNamed("myTrainingPlanExecute", arguments: bloc); + } + }, + builder: (context, state) { + final TrainingPlanBloc bloc = BlocProvider.of<TrainingPlanBloc>(context); + return ModalProgressHUD( + child: getPrograms(bloc), + inAsyncCall: state is TrainingPlanLoading, + opacity: 0.5, + color: Colors.black54, + progressIndicator: CircularProgressIndicator(), + ); + }, + ), + ), + bottomNavigationBar: BottomNavigator(bottomNavIndex: 2)); + } + + Widget getPrograms(TrainingPlanBloc bloc) { + return CustomScrollView(scrollDirection: Axis.vertical, slivers: [ + SliverGrid( + delegate: SliverChildListDelegate([ + getTrainingPlan(t("My Active Training"), "asset/image/exercise_plan_execute.jpg", "", + color: Colors.yellow[400]!, route: "myTrainingPlanExecute"), + getTrainingPlan(t("My Custom Plan"), "asset/image/exercise_plan_custom.jpg", ""), + getTrainingPlan(t("Training Plans for Beginners"), "asset/menu/training_plans_q_beginner.jpg", "beginner"), + getTrainingPlan(t("Training Plans for Home"), "asset/menu/training_plans_q_home.jpg", "home"), + getTrainingPlan(t("Training Plans Advanced"), "asset/menu/training_plans_q_advanced.jpg", "advanced"), + 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"), + ]), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + mainAxisSpacing: 15.0, + crossAxisSpacing: 15.0, + childAspectRatio: 1.0, + ), + ) + ]); + } + + Widget getTrainingPlan(String name, String imageUrl, String parentName, + {Color color = Colors.white, String route = "myTrainingPlanActivate"}) { + double mediaWidth = MediaQuery.of(context).size.width; + double imageWidth = (mediaWidth - 45) / 2; + + return ImageButton( + width: imageWidth, + textAlignment: Alignment.topLeft, + text: name, + style: GoogleFonts.robotoMono( + textStyle: TextStyle(fontSize: 14, color: color, fontWeight: FontWeight.bold, backgroundColor: Colors.black54.withOpacity(0.4))), + image: imageUrl, + left: 5, + textColor: color, + onTap: () { + if (Cache().userLoggedIn != null) { + if (route == "myTrainingPlanActivate") { + HashMap<String, dynamic> args = HashMap(); + args['parentName'] = parentName; + Navigator.of(context).pushNamed("myTrainingPlanActivate", arguments: args); + } else { + Navigator.of(context).pushNamed(route); + } + } + }, + isLocked: false, + ); + } +} diff --git a/lib/view/test_set_new.dart b/lib/view/test_set_new.dart index 01a5ea1..a8b246b 100644 --- a/lib/view/test_set_new.dart +++ b/lib/view/test_set_new.dart @@ -99,9 +99,9 @@ class TestSetNew extends StatelessWidget with Trans { unitQuantityUnit: bloc.exerciseType.unitQuantityUnit, hasUnitQuantity: bloc.exerciseType.unitQuantityUnit != null, onQuantityChanged: (value) { - bloc.add(TestSetNewChangeQuantity(quantity: double.parse(value))); + bloc.add(TestSetNewChangeQuantity(quantity: value)); }, - onUnitQuantityChanged: (value) => bloc.add(TestSetNewChangeQuantityUnit(quantity: double.parse(value))), + onUnitQuantityChanged: (value) => bloc.add(TestSetNewChangeQuantityUnit(quantity: value)), exerciseTypeId: bloc.exerciseType.exerciseTypeId, /* onSubmit: () { Navigator.of(context).pop(); diff --git a/lib/view/training_plan_activate_page.dart b/lib/view/training_plan_activate_page.dart new file mode 100644 index 0000000..6ab8df5 --- /dev/null +++ b/lib/view/training_plan_activate_page.dart @@ -0,0 +1,483 @@ +import 'dart:collection'; + +import 'package:aitrainer_app/bloc/menu/menu_bloc.dart'; +import 'package:aitrainer_app/bloc/training_plan/training_plan_bloc.dart'; +import 'package:aitrainer_app/library/custom_icon_icons.dart'; +import 'package:aitrainer_app/library/tree_view.dart'; +import 'package:aitrainer_app/model/cache.dart'; +import 'package:aitrainer_app/model/training_plan.dart'; +import 'package:aitrainer_app/model/workout_menu_tree.dart'; +import 'package:aitrainer_app/util/app_language.dart'; +import 'package:aitrainer_app/util/trans.dart'; +import 'package:aitrainer_app/widgets/app_bar.dart'; +import 'package:aitrainer_app/widgets/dialog_common.dart'; +import 'package:aitrainer_app/widgets/menu_image.dart'; +import 'package:aitrainer_app/widgets/treeview_parent_widget.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_html/flutter_html.dart'; +import 'package:flutter_html/style.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:modal_progress_hud_nsn/modal_progress_hud_nsn.dart'; +import 'package:syncfusion_flutter_datagrid/datagrid.dart'; + +// ignore: must_be_immutable +class TrainingPlanActivatePage extends StatelessWidget with Trans { + late String parentName; + TrainingPlanActivatePage(); + + @override + Widget build(BuildContext context) { + setContext(context); + final HashMap<String, dynamic> args = ModalRoute.of(context)!.settings.arguments as HashMap<String, dynamic>; + parentName = args['parentName']; + + return Scaffold( + appBar: AppBarNav(depth: 1), + body: Container( + padding: EdgeInsets.all(20), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage('asset/image/WT_black_background.jpg'), + fit: BoxFit.cover, + alignment: Alignment.center, + ), + ), + child: BlocConsumer<TrainingPlanBloc, TrainingPlanState>( + listener: (context, state) { + if (state is TrainingPlanError) { + showDialog( + context: context, + builder: (BuildContext context) { + return DialogCommon( + warning: true, + title: t("Warning"), + descriptions: t(state.message), + text: "OK", + onTap: () => Navigator.of(context).pushNamed("login"), + onCancel: () => { + Navigator.of(context).pop(), + }, + ); + }); + } else if (state is TrainingPlanFinished) { + Navigator.of(context).pop(); + Navigator.of(context).pushNamed("myTrainingPlanExecute"); + } + }, + builder: (context, state) { + final TrainingPlanBloc bloc = BlocProvider.of<TrainingPlanBloc>(context); + return ModalProgressHUD( + child: getPlans(bloc), + inAsyncCall: state is TrainingPlanLoading, + opacity: 0.5, + color: Colors.black54, + progressIndicator: CircularProgressIndicator(), + ); + }, + ), + ), + ); + } + + Widget getPlans(TrainingPlanBloc bloc) { + return TreeView( + startExpanded: false, + children: _getTreeChildren(bloc), + ); + } + + List<Widget> _getTreeChildren(TrainingPlanBloc bloc) { + final List<TrainingPlan> plans = bloc.trainingPlanRepository.getPlansByParent(parentName); + final String parentTitle = + bloc.trainingPlanRepository.parentTree != null ? bloc.trainingPlanRepository.parentTree!.nameTranslation : ""; + final String parentDescription = + bloc.trainingPlanRepository.parentTree != null && bloc.trainingPlanRepository.parentTree!.descriptionTranslation != null + ? bloc.trainingPlanRepository.parentTree!.descriptionTranslation! + : ""; + List<Widget> listWidget = []; + + Card explanation = Card( + color: Colors.white60, + child: Container( + padding: EdgeInsets.only(left: 10, right: 5, top: 12, bottom: 8), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Row( + children: [ + Icon( + Icons.info, + color: Colors.orangeAccent, + ), + Text(" "), + Flexible( + child: Text( + parentTitle, + maxLines: 2, + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + )), + ], + ), + Divider( + color: Colors.transparent, + ), + Html( + data: parentDescription, + //Optional parameters: + style: { + "p": Style( + color: Colors.black, + fontSize: FontSize(12), + padding: const EdgeInsets.only(left: 20, right: 8, bottom: 4), + ), + "strong": Style( + color: Colors.indigo, + fontWeight: FontWeight.bold, + fontSize: FontSize(14), + ), + "h3": Style( + color: Colors.yellow[600], + fontSize: FontSize(16), + textAlign: TextAlign.center, + padding: const EdgeInsets.all(12), + ), + "li": Style( + color: Colors.white, + fontSize: FontSize(14), + padding: const EdgeInsets.only(left: 20, bottom: 10, right: 8), + //before: "*", + display: Display.LIST_ITEM), + "h2": Style( + color: Colors.yellow[600], + fontWeight: FontWeight.bold, + fontSize: FontSize(24), + textAlign: TextAlign.center, + //padding: const EdgeInsets.all(4), + ), + "h1": Style( + color: Colors.yellow[400], + fontWeight: FontWeight.bold, + fontSize: FontSize.larger, + alignment: Alignment.center, + padding: const EdgeInsets.all(4), + ), + }, + ), // + ], + ))); + listWidget.add(explanation); + + plans.forEach((element) { + listWidget.add(Container( + margin: const EdgeInsets.only(left: 4.0), + child: TreeViewChild( + startExpanded: false, + parent: TreeviewParentWidget( + text: element.nameTranslations[AppLanguage().appLocal.toString()] != null + ? element.nameTranslations[AppLanguage().appLocal.toString()]! + : element.name, + fontSize: 18, + icon: Icon(Icons.list_sharp), + color: Colors.blue[800], + ), + children: _getChildList(element, bloc), + ))); + }); + + return listWidget; + } + + List<Widget> _getChildList(TrainingPlan plan, TrainingPlanBloc bloc) { + List<Widget> list = []; + + list.add(Card( + margin: EdgeInsets.only(left: 10, top: 5), + color: Colors.white60, + child: Container( + padding: EdgeInsets.only(left: 10, right: 10), + child: Column(children: [ + Html( + data: plan.descriptionTranslations[AppLanguage().appLocal.toString()] != null + ? plan.descriptionTranslations[AppLanguage().appLocal.toString()]! + : plan.description, + //Optional parameters: + style: { + "p": Style( + color: Colors.black, + padding: const EdgeInsets.all(4), + ), + "li": Style( + color: Colors.white, + //padding: const EdgeInsets.all(4), + ), + "h2": Style( + color: Colors.black, + fontWeight: FontWeight.bold, + fontSize: FontSize.larger, + + //padding: const EdgeInsets.all(4), + ), + "h1": Style( + color: Colors.black, + fontWeight: FontWeight.bold, + fontSize: FontSize.larger, + alignment: Alignment.center, + padding: const EdgeInsets.all(4), + ), + }, + ), + getPlanDetails(plan, bloc), + ElevatedButton( + style: ElevatedButton.styleFrom( + onPrimary: Colors.white, + primary: Colors.orange, + ), + child: Text(t("Activate")), + onPressed: () { + if (Cache().myTrainingPlan != null) { + showCupertinoDialog( + useRootNavigator: true, + context: context, + builder: (_) => CupertinoAlertDialog( + title: Text(t("You have an active Training Plan!")), + content: Column(children: [ + Divider(), + Text( + t("Do you want to override it with "), + style: (TextStyle(color: Colors.blue)), + ), + Text( + plan.nameTranslations[AppLanguage().appLocal.toString()]! + "?", + style: (TextStyle(color: Colors.blue[800], fontWeight: FontWeight.bold)), + ), + ]), + actions: [ + TextButton( + child: Text(t("No")), + onPressed: () => Navigator.pop(context), + ), + TextButton( + child: Text(t("Yes")), + onPressed: () { + Navigator.pop(context); + bloc.add(TrainingPlanActivate(trainingPlanId: plan.trainingPlanId)); + }, + ) + ], + )); + } else { + bloc.add(TrainingPlanActivate(trainingPlanId: plan.trainingPlanId)); + } + }, + ) + ]), + ))); + + return list; + } + + Widget getPlanDetails(TrainingPlan plan, TrainingPlanBloc bloc) { + return SfDataGrid( + headerRowHeight: 30, + rowHeight: 45, + source: TrainingPlanDetailSource( + plan: plan, + menuBloc: bloc.menuBloc, + onWeightTap: () => { + showDialog( + context: context, + builder: (BuildContext context) { + return DialogCommon( + title: t("Calculated Weight"), + descriptions: t("The weight is based on your previuos tests - if they exist."), + description2: t("If it does not exist, your very first exercise will be a test."), + text: "OK", + onTap: () => { + Navigator.of(context).pop(), + }, + onCancel: () => { + Navigator.of(context).pop(), + }, + ); + }) + }, + onRepeatTap: () => { + print("Reps"), + }), + headerGridLinesVisibility: GridLinesVisibility.both, + gridLinesVisibility: GridLinesVisibility.both, + columns: [ + GridTextColumn( + //columnWidthMode: ColumnWidthMode.lastColumnFill, + maximumWidth: 120, + columnName: 'exerciseImage', + label: Container( + color: Colors.green[50], + padding: EdgeInsets.only(left: 8.0), + alignment: Alignment.centerLeft, + child: Text( + 'Exercise', + textAlign: TextAlign.start, + overflow: TextOverflow.ellipsis, + ))), + GridTextColumn( + visible: false, + columnName: 'exerciseName', + label: Container( + color: Colors.green[50], + padding: EdgeInsets.symmetric(horizontal: 8.0), + alignment: Alignment.centerLeft, + child: Text( + 'Exercise', + textAlign: TextAlign.start, + overflow: TextOverflow.ellipsis, + ))), + GridTextColumn( + maximumWidth: 40, + columnName: 'set', + label: Container( + color: Colors.green[50], + padding: EdgeInsets.symmetric(horizontal: 2.0), + alignment: Alignment.centerLeft, + child: Text( + 'Set', + overflow: TextOverflow.ellipsis, + ))), + GridTextColumn( + maximumWidth: 50, + columnName: 'repeats', + label: Container( + color: Colors.green[50], + padding: EdgeInsets.symmetric(horizontal: 2.0), + alignment: Alignment.centerLeft, + child: Text( + 'Reps', + overflow: TextOverflow.ellipsis, + ))), + GridTextColumn( + maximumWidth: 60, + columnName: 'weight', + label: Container( + color: Colors.green[50], + padding: EdgeInsets.symmetric(horizontal: 2.0), + alignment: Alignment.centerLeft, + child: Text( + 'Weight', + overflow: TextOverflow.ellipsis, + ))), + GridTextColumn( + maximumWidth: 50, + columnName: 'day', + label: Container( + color: Colors.green[50], + padding: EdgeInsets.symmetric(horizontal: 8.0), + alignment: Alignment.centerLeft, + child: Text( + 'Day', + overflow: TextOverflow.ellipsis, + ))), + ], + ); + } +} + +class TrainingPlanDetailSource extends DataGridSource { + final TrainingPlan plan; + final MenuBloc menuBloc; + final VoidCallback onWeightTap; + final VoidCallback onRepeatTap; + TrainingPlanDetailSource({ + required this.plan, + required this.menuBloc, + required this.onWeightTap, + required this.onRepeatTap, + }) { + if (plan.details != null) { + dataGridRows = plan.details!.map((dataGridRow) { + WorkoutMenuTree? menuTree = menuBloc.menuTreeRepository.getMenuItemByExerciseTypeId(dataGridRow.exerciseTypeId); + if (menuTree == null) { + return DataGridRow(cells: []); + } + return DataGridRow(cells: [ + DataGridCell<Widget>( + columnName: 'exerciseImage', + value: MenuImage( + imageName: menuTree.imageName, + workoutTreeId: menuTree.id, + radius: 8, + )), + DataGridCell<String>(columnName: 'exerciseName', value: menuTree.name), + DataGridCell<int>(columnName: 'set', value: dataGridRow.set), + DataGridCell<int>(columnName: 'reps', value: dataGridRow.repeats), + DataGridCell<double>(columnName: 'weight', value: dataGridRow.weight), + DataGridCell<String>(columnName: 'day', value: dataGridRow.day), + ]); + }).toList(); + } + } + + List<DataGridRow> dataGridRows = []; + + @override + List<DataGridRow> get rows => dataGridRows; + + @override + DataGridRowAdapter? buildRow(DataGridRow row) { + if (row.getCells().isEmpty) { + return null; + } + String name = row.getCells()[1].value; + return DataGridRowAdapter( + color: Colors.white60, + cells: row.getCells().map<Widget>((dataGridCell) { + return Container( + alignment: dataGridCell.columnName == "exerciseImage" ? Alignment.centerLeft : Alignment.centerLeft, + padding: EdgeInsets.only(top: 2, bottom: 2, left: 4, right: 4), + child: dataGridCell.columnName == "exerciseImage" + ? Stack(alignment: AlignmentDirectional.bottomStart, children: [ + dataGridCell.value, + Container( + padding: EdgeInsets.only(left: 4, right: 8, bottom: 3), + child: Text( + name, + maxLines: 3, + overflow: TextOverflow.ellipsis, + style: + GoogleFonts.inter(fontSize: 10, color: Colors.yellow[600], fontWeight: FontWeight.bold, shadows: <Shadow>[ + Shadow( + offset: Offset(2.0, 2.0), + blurRadius: 6.0, + color: Colors.black, + ), + ]), + )) + ]) + : dataGridCell.columnName == "weight" && dataGridCell.value == -1 + ? GestureDetector( + onTap: () { + onWeightTap(); + }, + child: Icon( + CustomIcon.question_circle, + color: Colors.indigo[300], + )) + : 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 new file mode 100644 index 0000000..a6e6bc4 --- /dev/null +++ b/lib/view/training_plan_execute_page.dart @@ -0,0 +1,510 @@ +import 'dart:collection'; + +import 'package:aitrainer_app/bloc/training_plan/training_plan_bloc.dart'; +import 'package:aitrainer_app/library/custom_icon_icons.dart'; +import 'package:aitrainer_app/model/customer_training_plan_details.dart'; +import 'package:aitrainer_app/model/exercise_plan_detail.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:ezanimation/ezanimation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:modal_progress_hud_nsn/modal_progress_hud_nsn.dart'; +import 'package:timeline_tile/timeline_tile.dart'; + +// ignore: must_be_immutable +class TrainingPlanExecutePage extends StatelessWidget with Trans { + @override + Widget build(BuildContext context) { + final TrainingPlanBloc bloc = BlocProvider.of<TrainingPlanBloc>(context); + setContext(context); + return Scaffold( + appBar: AppBarNav(depth: 0), + body: Container( + padding: EdgeInsets.all(20), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage('asset/image/WT_black_background.jpg'), + fit: BoxFit.cover, + alignment: Alignment.center, + ), + ), + child: BlocConsumer<TrainingPlanBloc, TrainingPlanState>(listener: (context, state) { + if (state is TrainingPlanError) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(backgroundColor: Colors.orange, content: Text(state.message, style: TextStyle(color: Colors.white)))); + } else if (state is TrainingPlanFinished) {} + }, builder: (context, state) { + return ModalProgressHUD( + child: getExercises(bloc), + inAsyncCall: state is TrainingPlanLoading, + opacity: 0.5, + color: Colors.black54, + progressIndicator: CircularProgressIndicator(), + ); + }), + ), + floatingActionButton: FloatingActionButton.extended( + onPressed: () => bloc.getNext() != null ? executeExercise(bloc, bloc.getNext()!) : Navigator.of(context).pushNamed('home'), + backgroundColor: Colors.orange[800], + icon: Icon(CustomIcon.weight_hanging), + label: Text( + t("Training!"), + style: GoogleFonts.inter(fontWeight: FontWeight.bold, fontSize: 16), + ), + ), + ); + } + + Widget getExercises(TrainingPlanBloc bloc) { + return CustomScrollView(slivers: [ + SliverList(delegate: SliverChildListDelegate(getTiles(bloc))), + ]); + } + + List<Widget> getTiles(TrainingPlanBloc bloc) { + List<Widget> tiles = []; + tiles.add(getStartTile(bloc)); + tiles.addAll(getExerciseTiles(bloc, context)); + if (bloc.myPlan != null) tiles.add(getEndTile()); + return tiles; + } + + Widget getStartTile(TrainingPlanBloc bloc) { + 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"; + } else { + startText = bloc.isStarted() ? "Continue your training" : "Start your training"; + explainingText = bloc.getMyPlan()!.name != null ? bloc.getMyPlan()!.name! : ""; + } + + return TimelineTile( + alignment: TimelineAlign.manual, + lineXY: 0.1, + isFirst: true, + afterLineStyle: const LineStyle( + color: Colors.orange, + thickness: 6, + ), + indicatorStyle: IndicatorStyle( + width: 40, + color: Colors.orange, + padding: const EdgeInsets.all(8), + iconStyle: IconStyle( + color: Colors.white, + iconData: Icons.emoji_flags_rounded, + ), + ), + endChild: Container( + padding: EdgeInsets.only(top: 30), + constraints: const BoxConstraints( + minHeight: 120, + ), + color: Colors.transparent, + child: RichText( + text: TextSpan( + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + children: [ + TextSpan( + text: startText, + style: GoogleFonts.inter( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.yellow[400], + shadows: <Shadow>[ + Shadow( + offset: Offset(5.0, 5.0), + blurRadius: 12.0, + color: Colors.black54, + ), + Shadow( + offset: Offset(-3.0, 3.0), + blurRadius: 12.0, + color: Colors.black54, + ), + ], + )), + TextSpan( + text: "\n", + style: GoogleFonts.inter( + fontSize: 16, + color: Colors.white, + )), + TextSpan( + text: explainingText, + style: GoogleFonts.inter( + fontSize: 16, + color: Colors.white, + )), + ])), + ), + ); + } + + Widget getEndTile() { + return Container( + color: Colors.transparent, + child: TimelineTile( + alignment: TimelineAlign.manual, + lineXY: 0.1, + isLast: true, + beforeLineStyle: const LineStyle( + color: Colors.orange, + thickness: 6, + ), + indicatorStyle: IndicatorStyle( + width: 40, + color: Colors.orange, + padding: const EdgeInsets.all(8), + iconStyle: IconStyle( + color: Colors.white, + iconData: Icons.thumb_up, + ), + ), + endChild: Container( + padding: EdgeInsets.only(top: 50), + constraints: const BoxConstraints( + minHeight: 120, + ), + color: Colors.transparent, + child: RichText( + text: TextSpan( + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + children: [ + TextSpan( + text: "Finish!", + style: GoogleFonts.inter( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.yellow[400], + shadows: <Shadow>[ + Shadow( + offset: Offset(5.0, 5.0), + blurRadius: 12.0, + color: Colors.black54, + ), + Shadow( + offset: Offset(-3.0, 3.0), + blurRadius: 12.0, + color: Colors.black54, + ), + ], + )), + ])), + ), + ), + ); + } + + List<Widget> getExerciseTiles(TrainingPlanBloc bloc, BuildContext context) { + List<Widget> tiles = []; + if (bloc.myPlan != null && bloc.myPlan!.details.isNotEmpty) { + bloc.myPlan!.details.forEach((element) { + tiles.add(GestureDetector( + onTap: () => {}, + child: ExerciseTile( + bloc: bloc, + detail: element, + ))); + }); + } + + return tiles; + } + + void executeExercise(TrainingPlanBloc bloc, CustomerTrainingPlanDetails detail) { + CustomerTrainingPlanDetails? next = bloc.getNext(); + + if (next != null) { + String title = ""; + 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"); + } else { + final HashMap args = HashMap(); + args['exerciseType'] = next.exerciseType; + args['customerTrainingPlanDetails'] = detail; + Navigator.of(context).pushNamed('myTrainingPlanExercise', arguments: args); + return; + } + + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return DialogCommon( + title: title, + descriptions: description, + description2: description2, + text: "OK", + onTap: () => {Navigator.of(context).pop()}, + onCancel: () => {Navigator.of(context).pop()}, + ); + }); + } else { + Navigator.of(context).pushNamed('home'); + } + } +} + +// ignore: must_be_immutable +class ExerciseTile extends StatefulWidget with Trans { + final TrainingPlanBloc bloc; + final CustomerTrainingPlanDetails detail; + + ExerciseTile({required this.bloc, required this.detail}); + + @override + _ExerciseTileState createState() => _ExerciseTileState(); +} + +class _ExerciseTileState extends State<ExerciseTile> with Trans { + final EzAnimation animation = EzAnimation(1.0, 30.0, Duration(seconds: 3), reverseCurve: Curves.easeIn); + + @override + void initState() { + animation.start(); + animation.addStatusListener((status) { + if (status == AnimationStatus.completed) {} + }); + + super.initState(); + } + + @override + bool didUpdateWidget(ExerciseTile oldWidget) { + super.didUpdateWidget(oldWidget); + Future.delayed(Duration(milliseconds: 400)).then((value) => animation.start()); + return true; + } + + Widget getIndicator(ExercisePlanDetailState state) { + CustomerTrainingPlanDetails? next = widget.bloc.getNext(); + bool actual = false; + if (next != null) { + if (next.exerciseTypeId == widget.detail.exerciseTypeId) { + actual = true; + } + } + if (state.equalsTo(ExercisePlanDetailState.inProgress)) { + return ClipRRect( + borderRadius: BorderRadius.circular(24.0), + child: Container( + color: actual ? Colors.green : Colors.orange, + child: Icon( + CustomIcon.calendar_2, + size: 28, + color: Colors.white, + ))); + } else if (state.equalsTo(ExercisePlanDetailState.finished)) { + return ClipRRect( + borderRadius: BorderRadius.circular(24.0), + child: Container( + color: Colors.white, + child: Icon( + CustomIcon.ok_circled, + size: 40, + color: Colors.green, + ))); + } else { + return Image.asset( + "asset/image/pict_reps_volumen_db.png", + ); + } + } + + @override + Widget build(BuildContext context) { + setContext(context); + final ExercisePlanDetailState state = widget.detail.state; + final bool done = state.equalsTo(ExercisePlanDetailState.finished); + final String countSerie = widget.detail.set.toString(); + final String step = (widget.detail.exercises.length).toString(); + String weight = widget.detail.weight!.toStringAsFixed(1); + String restingTime = widget.detail.restingTime == null ? "" : widget.detail.restingTime!.toStringAsFixed(0); + bool isTest = false; + if (widget.detail.weight! == -1) { + weight = t("TEST"); + isTest = true; + } + setContext(context); + return Container( + color: Colors.transparent, + child: TimelineTile( + alignment: TimelineAlign.manual, + lineXY: 0.1, + beforeLineStyle: const LineStyle( + color: Color(0xffb4f500), + thickness: 6, + ), + afterLineStyle: const LineStyle( + color: Color(0xffb4f500), + thickness: 6, + ), + indicatorStyle: IndicatorStyle( + width: 40, + height: 40, + indicator: getIndicator(state), + ), + 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)!, + )), + SizedBox( + width: 10, + ), + Expanded( + child: RichText( + text: TextSpan( + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.bold, + color: done ? Colors.grey[400] : Colors.white, + ), + children: [ + TextSpan( + text: widget.detail.exerciseType!.nameTranslation, + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.bold, + color: done ? Colors.grey[400] : Colors.orange[500], + shadows: <Shadow>[ + Shadow( + offset: Offset(5.0, 5.0), + blurRadius: 12.0, + color: Colors.black54, + ), + Shadow( + offset: Offset(-3.0, 3.0), + blurRadius: 12.0, + color: Colors.black54, + ), + ], + )), + widget.detail.exerciseType!.unitQuantityUnit != null + ? TextSpan( + text: "\n", + ) + : TextSpan(), + widget.detail.exerciseType!.unitQuantityUnit != null + ? 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 + ? TextSpan( + text: weight, + style: GoogleFonts.inter( + fontSize: 12, + )) + : TextSpan(), + 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: widget.detail.repeats!.toString(), + style: GoogleFonts.inter( + fontSize: 12, + )), + 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, + )), + 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)), + ]), + )), + done + ? AnimatedBuilder( + animation: animation, + builder: (context, snapshot) { + return Column(mainAxisAlignment: MainAxisAlignment.center, children: [ + Image.asset( + "asset/image/kupa.png", + width: animation.value, + ), + Text("Result", style: GoogleFonts.inter(fontSize: 10, color: Colors.white)), + ]); + }) + : Offstage(), + isTest + ? 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("Why Test?"), + descriptions: t("This is your first exercise after at least 3 weeks."), + description2: + t("The first exercise will be a test. The following sets will be recalculated base on your test."), + description3: t("This is the most optimal way for your development"), + text: "OK", + onTap: () => Navigator.of(context).pop(), + onCancel: () => { + Navigator.of(context).pop(), + }, + ); + }), + child: Icon( + CustomIcon.question_circle, + color: Colors.yellowAccent[700], + size: 16, + )), + ]); + }) + : Offstage() + ]), + ), + ), + ); + } +} diff --git a/lib/view/training_plan_exercise.dart b/lib/view/training_plan_exercise.dart new file mode 100644 index 0000000..93ed22f --- /dev/null +++ b/lib/view/training_plan_exercise.dart @@ -0,0 +1,221 @@ +import 'dart:collection'; + +import 'package:aitrainer_app/bloc/test_set_control/test_set_control_bloc.dart'; +import 'package:aitrainer_app/bloc/test_set_execute/test_set_execute_bloc.dart'; +import 'package:aitrainer_app/bloc/test_set_new/test_set_new_bloc.dart'; +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/cache.dart'; +import 'package:aitrainer_app/model/customer_training_plan_details.dart'; +import 'package:aitrainer_app/model/exercise_plan_detail.dart'; +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'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:modal_progress_hud_nsn/modal_progress_hud_nsn.dart'; + +// ignore: must_be_immutable +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<TrainingPlanBloc>(context); + + setContext(context); + return Scaffold( + appBar: AppBarNav(depth: 1), + body: Container( + height: double.infinity, + width: double.infinity, + padding: EdgeInsets.all(20), + decoration: BoxDecoration( + image: DecorationImage( + image: Cache().userLoggedIn!.sex == "m" + ? AssetImage("asset/image/WT_black_background.jpg") + : AssetImage("asset/image/WT_Results_for_female.jpg"), + fit: BoxFit.cover, + alignment: Alignment.center, + ), + ), + child: BlocConsumer<TrainingPlanBloc, TrainingPlanState>(listener: (context, state) { + if (state is TrainingPlanError) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(backgroundColor: Colors.orange, content: Text(state.message, style: TextStyle(color: Colors.white)))); + } + }, builder: (context, state) { + return ModalProgressHUD( + child: getExercises(bloc, detail), + inAsyncCall: state is TrainingPlanLoading, + opacity: 0.5, + color: Colors.black54, + progressIndicator: CircularProgressIndicator(), + ); + }), + ), + floatingActionButton: FloatingActionButton.extended( + onPressed: () => { + Navigator.of(context).pop(), + bloc.add(TrainingPlanSaveExercise(detail: detail)), + }, + backgroundColor: Colors.orange[800], + icon: Icon(CustomIcon.save), + label: Text( + t("Save"), + style: GoogleFonts.inter(fontWeight: FontWeight.bold, fontSize: 16), + ), + ), + /* bottomNavigationBar: BottomBarMultipleExercises( + //isSet: executeBloc.miniTestSet == true, + exerciseTypeId: exerciseType.exerciseTypeId, + ), */ + ); + } + + Widget getExercises(TrainingPlanBloc bloc, CustomerTrainingPlanDetails detail) { + final String noTestTextWithWeight = "Please try to execute this exercise with exact weight and repeats what is suggested"; + final String noTestTextNoWeight = "Please try to execute this exercise with exact repeats what is suggested"; + return ExerciseSave( + exerciseName: detail.exerciseType!.nameTranslation, + exerciseDescription: detail.exerciseType!.descriptionTranslation, + exerciseTask: detail.exerciseType!.unitQuantityUnit != null + ? detail.weight == -1 + ? t("Please take a relative bigger weight and repeat 12-20 times and do your best! MAXIMIZE it!") + : noTestTextWithWeight + : detail.repeats == -1 + ? t("Please repeat as much times as you can! MAXIMIZE it!") + : noTestTextNoWeight, + unit: detail.exerciseType!.unit, + unitQuantityUnit: detail.exerciseType!.unitQuantityUnit, + hasUnitQuantity: detail.exerciseType!.unitQuantityUnit != null, + weight: detail.weight == -1 ? 30 : detail.weight, + repeats: detail.repeats == -1 ? 12 : detail.repeats, + onUnitQuantityChanged: (value) => bloc.add(TrainingPlanWeightChange(weight: value, detail: detail)), + onQuantityChanged: (value) => bloc.add(TrainingPlanRepeatsChange(repeats: value.toInt(), detail: detail)), + exerciseTypeId: detail.exerciseType!.exerciseTypeId, + ); + } + + Widget getExerciseForm(TrainingPlanBloc bloc, CustomerTrainingPlanDetails detail) { + return Container( + padding: const EdgeInsets.only(top: 10, left: 25, right: 25), + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: Column( + children: [ + Text( + detail.exerciseType!.nameTranslation, + style: GoogleFonts.archivoBlack( + fontWeight: FontWeight.bold, + fontSize: 24, + color: Colors.white, + shadows: <Shadow>[ + Shadow( + offset: Offset(2.0, 2.0), + blurRadius: 6.0, + color: Colors.black54, + ), + Shadow( + offset: Offset(-3.0, 3.0), + blurRadius: 12.0, + color: Colors.black54, + ), + ], + ), + overflow: TextOverflow.fade, + textAlign: TextAlign.center, + maxLines: 2, + softWrap: true, + ), + Divider( + color: Colors.transparent, + ), + Divider(), + detail.weight != -1 ? numberPickForm(bloc, detail) : numberPickForm(bloc, detail), + ], + ))); + } + + Widget numberPickForm(TrainingPlanBloc bloc, CustomerTrainingPlanDetails detail) { + final String strTimes = detail.repeats!.toStringAsFixed(1); // : "maximum"; + + List<Widget> listWidgets = [ + GestureDetector( + onTap: () => {}, + child: RichText( + text: TextSpan( + style: GoogleFonts.inter( + fontSize: 16, + fontWeight: FontWeight.normal, + color: Colors.yellow[300], + ), + children: [ + TextSpan(text: t("Please repeat with ")), + TextSpan( + text: detail.weight!.toStringAsFixed(1) + " " + detail.exerciseType!.unitQuantityUnit!, + style: GoogleFonts.inter( + decoration: TextDecoration.underline, + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.yellow[100], + ), + ), + TextSpan(text: t("hu_with") + " "), + TextSpan( + text: strTimes + " ", + style: GoogleFonts.inter( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.yellow[100], + )), + TextSpan( + text: t( + "times!", + )), + ]), + )), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + NumberPickerWidget( + minValue: 0, + maxValue: 200, + initalValue: detail.repeats!, + unit: t("reps"), + color: Colors.yellow[50]!, + onChange: (value) => {}), + TextButton( + style: TextButton.styleFrom( + padding: EdgeInsets.all(0), + primary: Colors.white, + onSurface: Colors.blueAccent, + ), + onPressed: () => {}, + child: Stack( + alignment: Alignment.center, + children: [ + Image.asset('asset/icon/gomb_orange_c.png', width: 140, height: 60), + Text( + t("Save"), + style: TextStyle(fontSize: 16, color: Colors.white), + ), + ], + )), + ], + ), + ]; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: listWidgets, + ); + } +} diff --git a/lib/widgets/bottom_nav.dart b/lib/widgets/bottom_nav.dart index a639439..8617cd6 100644 --- a/lib/widgets/bottom_nav.dart +++ b/lib/widgets/bottom_nav.dart @@ -90,7 +90,7 @@ class _NawDrawerWidget extends State<BottomNavigator> with Trans, Logging { case 2: Navigator.of(context).pop(); Track().track(TrackingEvent.my_exerciseplan); - Navigator.of(context).pushNamed('myExercisePlan'); + Navigator.of(context).pushNamed('myTrainingPlans'); break; case 3: diff --git a/lib/widgets/exercise_save.dart b/lib/widgets/exercise_save.dart index bf1b4c1..060966a 100644 --- a/lib/widgets/exercise_save.dart +++ b/lib/widgets/exercise_save.dart @@ -12,8 +12,8 @@ import 'dialog_html.dart'; // ignore: must_be_immutable class ExerciseSave extends StatefulWidget { - final ValueChanged<dynamic> onQuantityChanged; - final ValueChanged<dynamic>? onUnitQuantityChanged; + final ValueChanged<double> onQuantityChanged; + final ValueChanged<double>? onUnitQuantityChanged; final VoidCallback? onSubmit; final bool hasUnitQuantity; final String? unitQuantityUnit; @@ -22,18 +22,23 @@ class ExerciseSave extends StatefulWidget { final String exerciseDescription; final String exerciseTask; final int exerciseTypeId; + final double? weight; + final int? 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}); + 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, + }); @override _ExerciseSaveState createState() => _ExerciseSaveState(); } @@ -53,11 +58,15 @@ class _ExerciseSaveState extends State<ExerciseSave> with Trans { return getExerciseWidget(); } - //@override + @override initState() { super.initState(); - _controller1.text = "30"; - _controller2.text = "12"; + _controller1.text = widget.weight == null + ? "30" + : widget.weight! % widget.weight!.round() == 0 + ? widget.weight!.toStringAsFixed(0) + : widget.weight!.toStringAsFixed(1); + _controller2.text = widget.repeats == null ? "12" : widget.repeats!.toStringAsFixed(0); _nodeText1.addListener(() { if (_nodeText1.hasFocus) { _controller1.selection = TextSelection(baseOffset: 0, extentOffset: _controller1.text.length); @@ -69,7 +78,7 @@ class _ExerciseSaveState extends State<ExerciseSave> with Trans { } }); if (widget.unit == "second") { - stopWatchTimer.rawTime.listen((value) => widget.onQuantityChanged((value / 1000).toString())); + stopWatchTimer.rawTime.listen((value) => widget.onQuantityChanged((value / 1000))); } } @@ -190,6 +199,18 @@ class _ExerciseSaveState extends State<ExerciseSave> with Trans { fontSize: 14, color: Colors.orange, fontWeight: FontWeight.bold, + shadows: <Shadow>[ + Shadow( + offset: Offset(2.0, 2.0), + blurRadius: 6.0, + color: Colors.black54, + ), + Shadow( + offset: Offset(-3.0, 3.0), + blurRadius: 12.0, + color: Colors.black54, + ), + ], ), maxLines: 3, textAlign: TextAlign.center, @@ -258,7 +279,7 @@ class _ExerciseSaveState extends State<ExerciseSave> with Trans { onChanged: (value) => { value = value.replaceFirst(",", "."), value = value.replaceAll(RegExp(r'[^0-9.]'), ""), - widget.onUnitQuantityChanged!(value), + widget.onUnitQuantityChanged!(double.parse(value)), }), ])); } @@ -273,7 +294,7 @@ class _ExerciseSaveState extends State<ExerciseSave> with Trans { //padding: const EdgeInsets.only(bottom: 0), child: StreamBuilder<int>( stream: stopWatchTimer.rawTime, - initialData: stopWatchTimer.rawTime.valueWrapper?.value, + initialData: stopWatchTimer.rawTime.value, builder: (context, snap) { final value = snap.data; final displayTime = StopWatchTimer.getDisplayTime(value!, hours: false); @@ -344,7 +365,7 @@ class _ExerciseSaveState extends State<ExerciseSave> with Trans { Divider(), Divider(), Text(t("Or type the time manually:"), style: GoogleFonts.inter(color: Colors.white)), - TimePickerWidget(onChange: (value) => widget.onQuantityChanged((value).toString())) + TimePickerWidget(onChange: (value) => widget.onQuantityChanged((value))) ]); } Widget row = Container( @@ -369,7 +390,9 @@ class _ExerciseSaveState extends State<ExerciseSave> with Trans { textInputAction: TextInputAction.next, style: GoogleFonts.archivoBlack(fontSize: 80, color: Colors.orange[200]), onChanged: (value) { - widget.onQuantityChanged(value); + value = value.replaceFirst(",", "."); + value = value.replaceAll(RegExp(r'[^0-9.]'), ""); + widget.onQuantityChanged(double.parse(value)); }, ), ])); diff --git a/lib/widgets/image_button.dart b/lib/widgets/image_button.dart index a377a6a..86e6b32 100644 --- a/lib/widgets/image_button.dart +++ b/lib/widgets/image_button.dart @@ -22,6 +22,7 @@ class ImageButton extends StatelessWidget { final bool? isLocked; bool? isMarked; int? buttonIndex; + Color? textColor; ImageButton( {required this.text, @@ -37,11 +38,13 @@ class ImageButton extends StatelessWidget { this.onTap, this.buttonIndex, this.isMarked, - required this.isLocked}) { + required this.isLocked, + this.textColor}) { width = width ?? 180; height = height ?? 180; isMarked = isMarked ?? false; isShape = isShape ?? false; + textColor = textColor ?? Colors.white; style = style ?? GoogleFonts.archivoBlack( fontSize: 14, @@ -54,10 +57,9 @@ class ImageButton extends StatelessWidget { top = height! - (style!.fontSize! - 5) * text.length - 2 * left < 0 ? height! - 2 * style!.fontSize! - 22 : height! - style!.fontSize! - 37; - //print("Top: " + top.toStringAsFixed(0) + " length: " + ((style.fontSize - 5) * text.length).toString()); } final double width = MediaQuery.of(context).size.width; - //print("Mediawidth: " + width.toStringAsFixed(0)); + return Stack(alignment: AlignmentDirectional.bottomStart, children: [ TextButton( style: TextButton.styleFrom( @@ -93,7 +95,7 @@ class ImageButton extends StatelessWidget { maxLines: 2, style: GoogleFonts.archivoBlack( fontSize: 16, - color: Colors.white, + color: textColor, shadows: <Shadow>[ Shadow( offset: Offset(2.0, 2.0), diff --git a/lib/widgets/menu_image.dart b/lib/widgets/menu_image.dart index 3df5113..40e53f9 100644 --- a/lib/widgets/menu_image.dart +++ b/lib/widgets/menu_image.dart @@ -7,7 +7,12 @@ import 'package:aitrainer_app/library/transparent_image.dart'; class MenuImage extends StatelessWidget { final int? workoutTreeId; final String imageName; - const MenuImage({required this.workoutTreeId, required this.imageName}); + double radius; + MenuImage({ + required this.workoutTreeId, + required this.imageName, + this.radius = 24, + }); @override Widget build(BuildContext context) { @@ -31,7 +36,7 @@ class MenuImage extends StatelessWidget { if (imageName.contains("https")) { if (!wt.ImageCache().existsImageInMap(workoutTreeId!, imageName)) { widget = ClipRRect( - borderRadius: BorderRadius.circular(24.0), + borderRadius: BorderRadius.circular(radius), child: Container( color: Colors.transparent, child: FadeInImage( @@ -43,7 +48,7 @@ class MenuImage extends StatelessWidget { } } else { widget = ClipRRect( - borderRadius: BorderRadius.circular(24.0), + borderRadius: BorderRadius.circular(radius), child: Container( color: Colors.transparent, child: Image.asset(imageName), diff --git a/lib/widgets/menu_page_widget.dart b/lib/widgets/menu_page_widget.dart index 8e3899e..3846a36 100644 --- a/lib/widgets/menu_page_widget.dart +++ b/lib/widgets/menu_page_widget.dart @@ -1,5 +1,6 @@ import 'dart:collection'; import 'dart:ui'; +import 'package:aitrainer_app/bloc/training_plan/training_plan_bloc.dart'; import 'package:aitrainer_app/bloc/tutorial/tutorial_bloc.dart'; import 'package:aitrainer_app/model/exercise_ability.dart'; import 'package:aitrainer_app/bloc/menu/menu_bloc.dart'; @@ -47,7 +48,7 @@ class _MenuPageWidgetState extends State<MenuPageWidget> with Trans, Logging { @override void initState() { - if (activeExercisePlan) { + if (activeExercisePlan || Cache().myTrainingPlan != null) { animation.start(); animation.addStatusListener((status) { if (status == AnimationStatus.completed || status == AnimationStatus.dismissed) { @@ -313,6 +314,46 @@ class _MenuPageWidgetState extends State<MenuPageWidget> with Trans, Logging { } }, ), + Cache().myTrainingPlan != null + ? GestureDetector( + onTap: () => showDialog( + context: context, + builder: (BuildContext context) { + return DialogCommon( + title: t("You have an active Training Plan"), + descriptions: Cache().myTrainingPlan!.name != null ? Cache().myTrainingPlan!.name! : "", + description2: t("Press OK to continue"), + text: "OK", + onTap: () { + Navigator.of(context).pop(); + if (Cache().myTrainingPlan != null) { + final TrainingPlanBloc bloc = BlocProvider.of<TrainingPlanBloc>(context); + bloc.myPlan = Cache().myTrainingPlan; + Navigator.of(context).pushNamed("myTrainingPlanExecute"); + } + }, + onCancel: () => { + Navigator.of(context).pop(), + }, + ); + }), + child: AnimatedBuilder( + animation: animation, + builder: (context, snapshot) { + return Center( + child: Container( + width: animation.value, + height: animation.value, + child: Image.asset("asset/image/pict_hypertrophy.png"), + ), + ); + })) + : Offstage(), + activeExercisePlan + ? SizedBox( + width: 10, + ) + : Offstage(), Cache().activeExercisePlan != null ? GestureDetector( onTap: () => showDialog( diff --git a/pubspec.lock b/pubspec.lock index 8e967b3..97b7a7e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -56,7 +56,7 @@ packages: name: badges url: "https://pub.dartlang.org" source: hosted - version: "2.0.0-nullsafety.1" + version: "2.0.1" bloc: dependency: transitive description: @@ -231,7 +231,7 @@ packages: name: coverage url: "https://pub.dartlang.org" source: hosted - version: "0.15.2" + version: "1.0.2" crypto: dependency: "direct main" description: @@ -239,13 +239,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.0.0" - css_colors: - dependency: transitive - description: - name: css_colors - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" csslib: dependency: transitive description: @@ -280,7 +273,7 @@ packages: name: equatable url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.2" ezanimation: dependency: "direct main" description: @@ -322,98 +315,98 @@ packages: name: firebase_analytics url: "https://pub.dartlang.org" source: hosted - version: "8.0.2" + version: "8.1.0" firebase_analytics_platform_interface: dependency: transitive description: name: firebase_analytics_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.1" firebase_analytics_web: dependency: transitive description: name: firebase_analytics_web url: "https://pub.dartlang.org" source: hosted - version: "0.3.0" + version: "0.3.0+1" firebase_auth: dependency: "direct main" description: name: firebase_auth url: "https://pub.dartlang.org" source: hosted - version: "1.1.2" + version: "1.2.0" firebase_auth_platform_interface: dependency: transitive description: name: firebase_auth_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "4.2.0" + version: "4.2.3" firebase_auth_web: dependency: transitive description: name: firebase_auth_web url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.3" firebase_core: dependency: "direct main" description: name: firebase_core url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0" firebase_core_platform_interface: dependency: transitive description: name: firebase_core_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "4.0.0" + version: "4.0.1" firebase_core_web: dependency: transitive description: name: firebase_core_web url: "https://pub.dartlang.org" source: hosted - version: "1.0.2" + version: "1.1.0" firebase_messaging: dependency: "direct main" description: name: firebase_messaging url: "https://pub.dartlang.org" source: hosted - version: "9.1.3" + version: "10.0.0" firebase_messaging_platform_interface: dependency: transitive description: name: firebase_messaging_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.1.3" + version: "3.0.0" firebase_messaging_web: dependency: transitive description: name: firebase_messaging_web url: "https://pub.dartlang.org" source: hosted - version: "1.0.6" + version: "2.0.0" firebase_remote_config: dependency: "direct main" description: name: firebase_remote_config url: "https://pub.dartlang.org" source: hosted - version: "0.10.0-dev.2" + version: "0.10.0" firebase_remote_config_platform_interface: dependency: transitive description: name: firebase_remote_config_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "0.3.0-dev.2" + version: "0.3.0" fixnum: dependency: transitive description: @@ -427,7 +420,7 @@ packages: name: fl_chart url: "https://pub.dartlang.org" source: hosted - version: "0.30.0" + version: "0.36.1" flurry: dependency: "direct main" description: @@ -460,21 +453,21 @@ packages: name: flutter_facebook_auth url: "https://pub.dartlang.org" source: hosted - version: "3.3.2+2" + version: "3.4.0" flutter_facebook_auth_platform_interface: dependency: transitive description: name: flutter_facebook_auth_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.4.1" + version: "2.6.0" flutter_facebook_auth_web: dependency: transitive description: name: flutter_facebook_auth_web url: "https://pub.dartlang.org" source: hosted - version: "2.4.1+1" + version: "2.6.0" flutter_fadein: dependency: "direct main" description: @@ -488,7 +481,7 @@ packages: name: flutter_html url: "https://pub.dartlang.org" source: hosted - version: "2.0.0-nullsafety.0" + version: "2.0.0" flutter_launcher_icons: dependency: "direct dev" description: @@ -509,7 +502,7 @@ packages: name: flutter_local_notifications url: "https://pub.dartlang.org" source: hosted - version: "5.0.0" + version: "5.0.0+4" flutter_local_notifications_platform_interface: dependency: transitive description: @@ -522,20 +515,27 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_math_fork: + dependency: transitive + description: + name: flutter_math_fork + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.3" flutter_secure_storage: dependency: "direct main" description: name: flutter_secure_storage url: "https://pub.dartlang.org" source: hosted - version: "4.1.0" + version: "4.2.0" flutter_svg: dependency: transitive description: name: flutter_svg url: "https://pub.dartlang.org" source: hosted - version: "0.21.0-nullsafety.0" + version: "0.22.0" flutter_test: dependency: "direct dev" description: flutter @@ -566,14 +566,14 @@ packages: name: google_fonts url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0" google_sign_in: dependency: "direct main" description: name: google_sign_in url: "https://pub.dartlang.org" source: hosted - version: "5.0.1" + version: "5.0.3" google_sign_in_platform_interface: dependency: transitive description: @@ -615,7 +615,7 @@ packages: name: http_multi_server url: "https://pub.dartlang.org" source: hosted - version: "2.2.0" + version: "3.0.1" http_parser: dependency: transitive description: @@ -650,7 +650,7 @@ packages: name: io url: "https://pub.dartlang.org" source: hosted - version: "0.3.4" + version: "1.0.0" js: dependency: transitive description: @@ -699,7 +699,7 @@ packages: name: mime url: "https://pub.dartlang.org" source: hosted - version: "0.9.7" + version: "1.0.0" mockito: dependency: "direct main" description: @@ -804,14 +804,14 @@ packages: name: path_drawing url: "https://pub.dartlang.org" source: hosted - version: "0.5.0-nullsafety.0" + version: "0.5.1" path_parsing: dependency: transitive description: name: path_parsing url: "https://pub.dartlang.org" source: hosted - version: "0.2.0-nullsafety.0" + version: "0.2.1" path_provider: dependency: transitive description: @@ -923,7 +923,7 @@ packages: name: purchases_flutter url: "https://pub.dartlang.org" source: hosted - version: "3.2.1" + version: "3.2.2" quiver: dependency: transitive description: @@ -958,21 +958,21 @@ packages: name: rxdart url: "https://pub.dartlang.org" source: hosted - version: "0.26.0" + version: "0.27.0" sentry: dependency: transitive description: name: sentry url: "https://pub.dartlang.org" source: hosted - version: "5.0.0" + version: "5.1.0-beta.1" sentry_flutter: dependency: "direct main" description: name: sentry_flutter url: "https://pub.dartlang.org" source: hosted - version: "5.0.0" + version: "5.1.0-beta.1" shared_preferences: dependency: "direct dev" description: @@ -1028,21 +1028,21 @@ packages: name: shelf_packages_handler url: "https://pub.dartlang.org" source: hosted - version: "2.0.1" + version: "3.0.0" shelf_static: dependency: transitive description: name: shelf_static url: "https://pub.dartlang.org" source: hosted - version: "0.2.9+2" + version: "1.0.0" shelf_web_socket: dependency: transitive description: name: shelf_web_socket url: "https://pub.dartlang.org" source: hosted - version: "0.2.4+1" + version: "1.0.1" sky_engine: dependency: transitive description: flutter @@ -1110,7 +1110,7 @@ packages: name: stop_watch_timer url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.2.0+1" stream_channel: dependency: transitive description: @@ -1138,7 +1138,14 @@ packages: name: syncfusion_flutter_core url: "https://pub.dartlang.org" source: hosted - version: "19.1.63" + version: "19.1.64" + syncfusion_flutter_datagrid: + dependency: "direct main" + description: + name: syncfusion_flutter_datagrid + url: "https://pub.dartlang.org" + source: hosted + version: "19.1.64-beta" syncfusion_flutter_gauges: dependency: "direct main" description: @@ -1194,7 +1201,7 @@ packages: name: timezone url: "https://pub.dartlang.org" source: hosted - version: "0.7.0-nullsafety.0" + version: "0.7.0" timing: dependency: transitive description: @@ -1209,6 +1216,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.1.9" + tuple: + dependency: transitive + description: + name: tuple + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" typed_data: dependency: transitive description: @@ -1278,7 +1292,7 @@ packages: name: video_player url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.1.1" video_player_platform_interface: dependency: transitive description: @@ -1299,7 +1313,7 @@ packages: name: vm_service url: "https://pub.dartlang.org" source: hosted - version: "5.5.0" + version: "6.2.0" wakelock: dependency: "direct main" description: @@ -1341,7 +1355,7 @@ packages: name: web_socket_channel url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "2.1.0" webkit_inspection_protocol: dependency: transitive description: @@ -1355,7 +1369,7 @@ packages: name: webview_flutter url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "2.0.7" win32: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index f76e5ce..63afae2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -25,28 +25,28 @@ dependencies: sdk: flutter cupertino_icons: ^1.0.0 - google_fonts: ^2.0.0 + google_fonts: ^2.1.0 devicelocale: ^0.4.1 - sentry_flutter: ^5.0.0 + sentry_flutter: ^5.1.0-beta.1 flutter_bloc: ^7.0.0 - equatable: ^2.0.0 + equatable: ^2.0.2 spider_chart: ^0.1.5 rainbow_color: ^2.0.1 percent_indicator: ^ 3.3.0-nullsafety.1 - fl_chart: ^0.30.0 + fl_chart: ^0.36.1 infinite_listview: ^1.1.0 toggle_switch: ^0.1.9 keyboard_actions: ^3.4.0 - badges: ^ 2.0.0-nullsafety.1 + badges: ^ 2.0.1 #health: ^3.0.0 - stop_watch_timer: ^1.0.0 + stop_watch_timer: ^1.2.0+1 #location: ^3.2.4 modal_progress_hud_nsn: ^0.1.0-nullsafety-1 - flutter_html: ^2.0.0-nullsafety.0 + flutter_html: ^2.0.0 wakelock: ^ 0.4.0 timeline_tile: ^2.0.0 - purchases_flutter: ^3.2.1 + purchases_flutter: ^3.2.2 package_info: ^2.0.0 ezanimation: ^0.5.0 flutter_fadein: ^2.0.0 @@ -59,18 +59,20 @@ dependencies: #super_tooltip: ^1.0.1 url_launcher: ^6.0.3 - firebase_core: ^1.1.0 - firebase_analytics: ^8.0.2 - firebase_messaging: ^9.1.3 + firebase_core: ^1.2.0 + firebase_analytics: ^8.1.0 + firebase_messaging: ^10.0.0 flutter_local_notifications: ^5.0.0 - firebase_auth: ^1.1.2 - firebase_remote_config: ^0.10.0-dev.2 + firebase_auth: ^1.2.0 + firebase_remote_config: ^0.10.0 syncfusion_flutter_gauges: ^19.1.63 + syncfusion_flutter_datagrid: ^19.1.63 - flutter_facebook_auth: ^3.3.2 - google_sign_in: ^5.0.1 + 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 #smartlook: ^1.0.7 flurry: ^0.0.4 @@ -80,7 +82,7 @@ dependencies: mockito: ^5.0.3 sqflite: ^2.0.0+3 - flutter_secure_storage: ^4.1.0 + flutter_secure_storage: ^4.2.0 #social_share: ^2.1.1 flutter_localizations: @@ -383,6 +385,19 @@ flutter: - asset/menu/test_center.jpg - asset/menu/test_on_machines.jpg - asset/menu/thigh_adductor.jpg + - asset/menu/training_plans_menu.jpg + - asset/menu/training_plans_woman.jpg + - asset/menu/training_plans_home.jpg + - asset/menu/training_plans_celebrities.jpg + - asset/menu/training_plans_beginner.jpg + - asset/menu/training_plans_advanced.jpg + - asset/menu/training_plans_strength_gain.jpg + - asset/menu/training_plans_q_woman.jpg + - asset/menu/training_plans_q_home.jpg + - asset/menu/training_plans_q_celebrities.jpg + - asset/menu/training_plans_q_beginner.jpg + - asset/menu/training_plans_q_advanced.jpg + - asset/menu/training_plans_q_gain_strength.jpg - asset/menu/triceps_extension_on_cable_with_rope.jpg - asset/menu/triceps_kickback.jpg - asset/menu/triceps_pushdown.jpg