From d137e1166d2d6b0fdc4bce201ccdd5c486728609 Mon Sep 17 00:00:00 2001 From: tahaalam Date: Mon, 9 Feb 2026 12:14:17 +0300 Subject: [PATCH] watch integration ui added. --- assets/images/svg/bluetooth.svg | 6 + assets/images/svg/watch_activity.svg | 3 + assets/images/svg/watch_activity_trailing.svg | 4 + assets/images/svg/watch_bmi.svg | 6 + assets/images/svg/watch_bmi_trailing.svg | 3 + assets/images/svg/watch_height.svg | 3 + assets/images/svg/watch_sleep.svg | 3 + assets/images/svg/watch_sleep_trailing.svg | 5 + assets/images/svg/watch_steps.svg | 5 + assets/images/svg/watch_steps_trailing.svg | 9 + assets/images/svg/watch_weight.svg | 3 + assets/images/svg/watch_weight_trailing.svg | 9 + assets/langs/ar-SA.json | 17 +- assets/langs/en-US.json | 17 +- ios/Runner.xcodeproj/project.pbxproj | 58 ---- ios/Runner/AppDelegate.swift | 10 +- lib/core/app_assets.dart | 15 ++ lib/core/common_models/smart_watch.dart | 18 ++ lib/generated/locale_keys.g.dart | 14 + .../smartwatches/activity_detail.dart | 247 ++++++++++++++++++ .../smartwatches/smart_watch_activity.dart | 147 +++++++++++ .../smartwatches/smartwatch_home_page.dart | 37 ++- .../smartwatch_instructions_page.dart | 61 ++++- lib/routes/app_routes.dart | 1 + lib/theme/colors.dart | 1 + lib/widgets/custom_tab_bar.dart | 7 + lib/widgets/graph/CustomBarGraph.dart | 240 +++++++++++++++++ 27 files changed, 881 insertions(+), 68 deletions(-) create mode 100644 assets/images/svg/bluetooth.svg create mode 100644 assets/images/svg/watch_activity.svg create mode 100644 assets/images/svg/watch_activity_trailing.svg create mode 100644 assets/images/svg/watch_bmi.svg create mode 100644 assets/images/svg/watch_bmi_trailing.svg create mode 100644 assets/images/svg/watch_height.svg create mode 100644 assets/images/svg/watch_sleep.svg create mode 100644 assets/images/svg/watch_sleep_trailing.svg create mode 100644 assets/images/svg/watch_steps.svg create mode 100644 assets/images/svg/watch_steps_trailing.svg create mode 100644 assets/images/svg/watch_weight.svg create mode 100644 assets/images/svg/watch_weight_trailing.svg create mode 100644 lib/core/common_models/smart_watch.dart create mode 100644 lib/presentation/smartwatches/activity_detail.dart create mode 100644 lib/presentation/smartwatches/smart_watch_activity.dart create mode 100644 lib/widgets/graph/CustomBarGraph.dart diff --git a/assets/images/svg/bluetooth.svg b/assets/images/svg/bluetooth.svg new file mode 100644 index 00000000..5fe2cf0c --- /dev/null +++ b/assets/images/svg/bluetooth.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/svg/watch_activity.svg b/assets/images/svg/watch_activity.svg new file mode 100644 index 00000000..8da915fa --- /dev/null +++ b/assets/images/svg/watch_activity.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/watch_activity_trailing.svg b/assets/images/svg/watch_activity_trailing.svg new file mode 100644 index 00000000..dc09c058 --- /dev/null +++ b/assets/images/svg/watch_activity_trailing.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/watch_bmi.svg b/assets/images/svg/watch_bmi.svg new file mode 100644 index 00000000..4d34d5d0 --- /dev/null +++ b/assets/images/svg/watch_bmi.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/svg/watch_bmi_trailing.svg b/assets/images/svg/watch_bmi_trailing.svg new file mode 100644 index 00000000..772d18a4 --- /dev/null +++ b/assets/images/svg/watch_bmi_trailing.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/watch_height.svg b/assets/images/svg/watch_height.svg new file mode 100644 index 00000000..e70b4b89 --- /dev/null +++ b/assets/images/svg/watch_height.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/watch_sleep.svg b/assets/images/svg/watch_sleep.svg new file mode 100644 index 00000000..dd21f628 --- /dev/null +++ b/assets/images/svg/watch_sleep.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/watch_sleep_trailing.svg b/assets/images/svg/watch_sleep_trailing.svg new file mode 100644 index 00000000..dff5ab36 --- /dev/null +++ b/assets/images/svg/watch_sleep_trailing.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/watch_steps.svg b/assets/images/svg/watch_steps.svg new file mode 100644 index 00000000..730c1892 --- /dev/null +++ b/assets/images/svg/watch_steps.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/watch_steps_trailing.svg b/assets/images/svg/watch_steps_trailing.svg new file mode 100644 index 00000000..2c1b2f20 --- /dev/null +++ b/assets/images/svg/watch_steps_trailing.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/images/svg/watch_weight.svg b/assets/images/svg/watch_weight.svg new file mode 100644 index 00000000..3acddca5 --- /dev/null +++ b/assets/images/svg/watch_weight.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/watch_weight_trailing.svg b/assets/images/svg/watch_weight_trailing.svg new file mode 100644 index 00000000..3e2b3dd8 --- /dev/null +++ b/assets/images/svg/watch_weight_trailing.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index cb2b71ab..dd576900 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -1523,5 +1523,20 @@ "bmi": "مؤشر كتلة الجسم", "yesPleaseINeedHelp": "نعم، من فضلك أريد المساعدة", "noThanksIKnowTheClinic": "لا شكراً، أعرف العيادة", - "unableToSendOTP": "تعذر إرسال رمز التحقق لمرة واحدة" + "unableToSendOTP": "تعذر إرسال رمز التحقق لمرة واحدة", + "applewatch": "ساعة آبل", + "applehealthapplicationshouldbeinstalledinyourphone": "يجب تثبيت تطبيق Apple Health على هاتفك", + "unabletodetectapplicationinstalledpleasecomebackonceinstalled": "لا يمكننا اكتشاف التطبيق المثبت على جهازك. يرجى العودة إلى هنا بمجرد تثبيت هذا التطبيق.", + "applewatchshouldbeconnected": "يجب توصيل ساعة آبل", + "samsungwatch": "ساعة سامسونج", + "samsunghealthapplicationshouldbeinstalledinyourphone": "يجب تثبيت تطبيق Samsung Health على هاتفك", + "samsungwatchshouldbeconnected": "يجب توصيل ساعة سامسونج", + "huaweiwatch": "ساعة هواوي", + "huaweihealthapplicationshouldbeinstalledinyourphone": "يجب تثبيت تطبيق Huawei Health على هاتفك", + "huaweiwatchshouldbeconnected": "يجب توصيل ساعة هواوي", + "whoopwatch": "ساعة Whoop", + "whoophealthapplicationshouldbeinstalledinyourphone": "يجب تثبيت تطبيق Whoop Health على هاتفك", + "whoopwatchshouldbeconnected": "يجب توصيل ساعة Whoop", + "updatetheinformation": "سيتيح ذلك جمع أحدث المعلومات من ساعة آبل الخاصة بك" + } diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index d937f6bc..ef7fcb9f 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -1516,5 +1516,20 @@ "bmi": "BMI", "yesPleaseINeedHelp": "Yes, Please I need help", "noThanksIKnowTheClinic": "No, Thanks. I Know the clinic", - "unableToSendOTP": "Unable to send Verification OTP" + "unableToSendOTP": "Unable to send Verification OTP", + "applewatch": "Apple Watch", + "applehealthapplicationshouldbeinstalledinyourphone": "Apple Health application should be installed in your phone", + "unabletodetectapplicationinstalledpleasecomebackonceinstalled": "We are unable to detect the application installed in your device. Please come back here once you have installed this application.", + "applewatchshouldbeconnected": "Apple Watch should be connected", + "samsungwatch": "Samsung Watch", + "samsunghealthapplicationshouldbeinstalledinyourphone": "Samsung Health application should be installed in your phone", + "samsungwatchshouldbeconnected": "Samsung Watch should be connected", + "huaweiwatch": "Huawei Watch", + "huaweihealthapplicationshouldbeinstalledinyourphone": "Huawei Health application should be installed in your phone", + "huaweiwatchshouldbeconnected": "Huawei Watch should be connected", + "whoopwatch": "Whoop Watch", + "whoophealthapplicationshouldbeinstalledinyourphone": "Whoop Health application should be installed in your phone", + "whoopwatchshouldbeconnected": "Whoop Watch should be connected", + "updatetheinformation": "This will allow to gather the most up to date information from your apple watch" + } diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 7a41ae2c..2a0fec9a 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -11,19 +11,7 @@ 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 478CFA942E638C8E0064F3D7 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 478CFA932E638C8E0064F3D7 /* GoogleService-Info.plist */; }; - 61243B492EC5FA3700D46FA0 /* PenguinModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61243B422EC5FA3700D46FA0 /* PenguinModel.swift */; }; - 61243B4C2EC5FA3700D46FA0 /* HMGPenguinInPlatformBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61243B3D2EC5FA3700D46FA0 /* HMGPenguinInPlatformBridge.swift */; }; - 61243B502EC5FA3700D46FA0 /* PenguinView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61243B452EC5FA3700D46FA0 /* PenguinView.swift */; }; - 61243B542EC5FA3700D46FA0 /* PenguinPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61243B442EC5FA3700D46FA0 /* PenguinPlugin.swift */; }; - 61243B562EC5FA3700D46FA0 /* PenguinNavigator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61243B432EC5FA3700D46FA0 /* PenguinNavigator.swift */; }; - 61243B572EC5FA3700D46FA0 /* PenguinViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61243B462EC5FA3700D46FA0 /* PenguinViewFactory.swift */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 766D8CB62EC60BE600D05E07 /* Penguin.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 766D8CB32EC60BE600D05E07 /* Penguin.xcframework */; }; - 766D8CB72EC60BE600D05E07 /* Penguin.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 766D8CB32EC60BE600D05E07 /* Penguin.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 766D8CB82EC60BE600D05E07 /* PenNavUI.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 766D8CB42EC60BE600D05E07 /* PenNavUI.xcframework */; }; - 766D8CB92EC60BE600D05E07 /* PenNavUI.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 766D8CB42EC60BE600D05E07 /* PenNavUI.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 766D8CBA2EC60BE600D05E07 /* PenguinINRenderer.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 766D8CB52EC60BE600D05E07 /* PenguinINRenderer.xcframework */; }; - 766D8CBB2EC60BE600D05E07 /* PenguinINRenderer.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 766D8CB52EC60BE600D05E07 /* PenguinINRenderer.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; @@ -47,9 +35,6 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - 766D8CB72EC60BE600D05E07 /* Penguin.xcframework in Embed Frameworks */, - 766D8CBB2EC60BE600D05E07 /* PenguinINRenderer.xcframework in Embed Frameworks */, - 766D8CB92EC60BE600D05E07 /* PenNavUI.xcframework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -64,18 +49,9 @@ 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 478CFA932E638C8E0064F3D7 /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 478CFA952E6E20A60064F3D7 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; - 61243B3D2EC5FA3700D46FA0 /* HMGPenguinInPlatformBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HMGPenguinInPlatformBridge.swift; sourceTree = ""; }; - 61243B422EC5FA3700D46FA0 /* PenguinModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PenguinModel.swift; sourceTree = ""; }; - 61243B432EC5FA3700D46FA0 /* PenguinNavigator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PenguinNavigator.swift; sourceTree = ""; }; - 61243B442EC5FA3700D46FA0 /* PenguinPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PenguinPlugin.swift; sourceTree = ""; }; - 61243B452EC5FA3700D46FA0 /* PenguinView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PenguinView.swift; sourceTree = ""; }; - 61243B462EC5FA3700D46FA0 /* PenguinViewFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PenguinViewFactory.swift; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7595037DD52211B91157B0F3 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; - 766D8CB32EC60BE600D05E07 /* Penguin.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Penguin.xcframework; path = Frameworks/Penguin.xcframework; sourceTree = ""; }; - 766D8CB42EC60BE600D05E07 /* PenNavUI.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = PenNavUI.xcframework; path = Frameworks/PenNavUI.xcframework; sourceTree = ""; }; - 766D8CB52EC60BE600D05E07 /* PenguinINRenderer.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = PenguinINRenderer.xcframework; path = Frameworks/PenguinINRenderer.xcframework; sourceTree = ""; }; 769C9BF82E6F106D009F68A9 /* RunnerDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerDebug.entitlements; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 8E12CEEB8E334EE22D5259D7 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; @@ -95,9 +71,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 766D8CB62EC60BE600D05E07 /* Penguin.xcframework in Frameworks */, - 766D8CBA2EC60BE600D05E07 /* PenguinINRenderer.xcframework in Frameworks */, - 766D8CB82EC60BE600D05E07 /* PenNavUI.xcframework in Frameworks */, DE1EF78253E64BE86845D2CC /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -113,32 +86,9 @@ path = RunnerTests; sourceTree = ""; }; - 61243B412EC5FA3700D46FA0 /* Helper */ = { - isa = PBXGroup; - children = ( - 61243B3D2EC5FA3700D46FA0 /* HMGPenguinInPlatformBridge.swift */, - ); - path = Helper; - sourceTree = ""; - }; - 61243B472EC5FA3700D46FA0 /* Penguin */ = { - isa = PBXGroup; - children = ( - 61243B422EC5FA3700D46FA0 /* PenguinModel.swift */, - 61243B432EC5FA3700D46FA0 /* PenguinNavigator.swift */, - 61243B442EC5FA3700D46FA0 /* PenguinPlugin.swift */, - 61243B452EC5FA3700D46FA0 /* PenguinView.swift */, - 61243B462EC5FA3700D46FA0 /* PenguinViewFactory.swift */, - ); - path = Penguin; - sourceTree = ""; - }; 766D8CB22EC60BE600D05E07 /* Frameworks */ = { isa = PBXGroup; children = ( - 766D8CB32EC60BE600D05E07 /* Penguin.xcframework */, - 766D8CB52EC60BE600D05E07 /* PenguinINRenderer.xcframework */, - 766D8CB42EC60BE600D05E07 /* PenNavUI.xcframework */, D562310E31D1DDEFA02A6C12 /* Pods_Runner.framework */, ); name = Frameworks; @@ -189,8 +139,6 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( - 61243B412EC5FA3700D46FA0 /* Helper */, - 61243B472EC5FA3700D46FA0 /* Penguin */, 769C9BF82E6F106D009F68A9 /* RunnerDebug.entitlements */, 478CFA952E6E20A60064F3D7 /* Runner.entitlements */, 478CFA932E638C8E0064F3D7 /* GoogleService-Info.plist */, @@ -414,12 +362,6 @@ buildActionMask = 2147483647; files = ( 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, - 61243B492EC5FA3700D46FA0 /* PenguinModel.swift in Sources */, - 61243B4C2EC5FA3700D46FA0 /* HMGPenguinInPlatformBridge.swift in Sources */, - 61243B502EC5FA3700D46FA0 /* PenguinView.swift in Sources */, - 61243B542EC5FA3700D46FA0 /* PenguinPlugin.swift in Sources */, - 61243B562EC5FA3700D46FA0 /* PenguinNavigator.swift in Sources */, - 61243B572EC5FA3700D46FA0 /* PenguinViewFactory.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 64d7428e..308891a7 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -16,11 +16,11 @@ import GoogleMaps return super.application(application, didFinishLaunchingWithOptions: launchOptions) } func initializePlatformChannels(){ - if let mainViewController = window?.rootViewController as? FlutterViewController{ // platform initialization suppose to be in foreground - - HMGPenguinInPlatformBridge.initialize(flutterViewController: mainViewController) - - } +// if let mainViewController = window?.rootViewController as? FlutterViewController{ // platform initialization suppose to be in foreground +// +//// HMGPenguinInPlatformBridge.initialize(flutterViewController: mainViewController) +// +// } } override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken:Data){ // Messaging.messaging().apnsToken = deviceToken diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 5b8d2ed5..019bd144 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -230,6 +230,21 @@ class AppAssets { static const String forward_top_nav_icon = '$svgBasePath/forward_top_nav_icon.svg'; static const String back_top_nav_icon = '$svgBasePath/back_top_nav_icon.svg'; + static const String bluetooth = '$svgBasePath/bluetooth.svg'; + + //smartwatch + static const String watchActivity = '$svgBasePath/watch_activity.svg'; + static const String watchActivityTrailing = '$svgBasePath/watch_activity_trailing.svg'; + static const String watchSteps= '$svgBasePath/watch_steps.svg'; + static const String watchStepsTrailing= '$svgBasePath/watch_steps_trailing.svg'; + static const String watchSleep= '$svgBasePath/watch_sleep.svg'; + static const String watchSleepTrailing= '$svgBasePath/watch_sleep_trailing.svg'; + static const String watchBmi= '$svgBasePath/watch_bmi.svg'; + static const String watchBmiTrailing= '$svgBasePath/watch_bmi_trailing.svg'; + static const String watchWeight= '$svgBasePath/watch_weight.svg'; + static const String watchWeightTrailing= '$svgBasePath/watch_weight_trailing.svg'; + static const String watchHeight= '$svgBasePath/watch_height.svg'; + //bottom navigation// static const String homeBottom = '$svgBasePath/home_bottom.svg'; diff --git a/lib/core/common_models/smart_watch.dart b/lib/core/common_models/smart_watch.dart new file mode 100644 index 00000000..b9259f5a --- /dev/null +++ b/lib/core/common_models/smart_watch.dart @@ -0,0 +1,18 @@ +enum SmartWatchTypes{ + apple, + samsung, + huawei, + whoop +} + + +class SmartwatchDetails { + final SmartWatchTypes watchType; + final String watchIcon; + final String smallIcon; + final String detailsTitle; + final String details; + final String secondTitle; + + SmartwatchDetails(this.watchType, this.watchIcon, this.smallIcon, this.detailsTitle, this.details, this.secondTitle); +} \ No newline at end of file diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 416d2a47..1ff22113 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -1515,5 +1515,19 @@ abstract class LocaleKeys { static const yesPleaseINeedHelp = 'yesPleaseINeedHelp'; static const noThanksIKnowTheClinic = 'noThanksIKnowTheClinic'; static const unableToSendOTP = 'unableToSendOTP'; + static const applewatch = 'applewatch'; + static const applehealthapplicationshouldbeinstalledinyourphone = 'applehealthapplicationshouldbeinstalledinyourphone'; + static const unabletodetectapplicationinstalledpleasecomebackonceinstalled = 'unabletodetectapplicationinstalledpleasecomebackonceinstalled'; + static const applewatchshouldbeconnected = 'applewatchshouldbeconnected'; + static const samsungwatch = 'samsungwatch'; + static const samsunghealthapplicationshouldbeinstalledinyourphone = 'samsunghealthapplicationshouldbeinstalledinyourphone'; + static const samsungwatchshouldbeconnected = 'samsungwatchshouldbeconnected'; + static const huaweiwatch = 'huaweiwatch'; + static const huaweihealthapplicationshouldbeinstalledinyourphone = 'huaweihealthapplicationshouldbeinstalledinyourphone'; + static const huaweiwatchshouldbeconnected = 'huaweiwatchshouldbeconnected'; + static const whoopwatch = 'whoopwatch'; + static const whoophealthapplicationshouldbeinstalledinyourphone = 'whoophealthapplicationshouldbeinstalledinyourphone'; + static const whoopwatchshouldbeconnected = 'whoopwatchshouldbeconnected'; + static const updatetheinformation = 'updatetheinformation'; } diff --git a/lib/presentation/smartwatches/activity_detail.dart b/lib/presentation/smartwatches/activity_detail.dart new file mode 100644 index 00000000..cc006a61 --- /dev/null +++ b/lib/presentation/smartwatches/activity_detail.dart @@ -0,0 +1,247 @@ +import 'dart:math'; + +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/common_models/data_points.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; +import 'package:hmg_patient_app_new/widgets/graph/CustomBarGraph.dart'; +import 'package:intl/intl.dart' show DateFormat; + +class ActivityDetails extends StatefulWidget { + final String selectedActivity; + + const ActivityDetails({super.key, required this.selectedActivity}); + + @override + State createState() => _ActivityDetailsState(); +} + +class _ActivityDetailsState extends State { + int index = 0; + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: "All Health Data".needTranslation, + child: Column( + spacing: 16.h, + children: [ + periodSelectorView((index) {}), + activityDetails(), + DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h), + child: + activityGraph().paddingOnly(left: 16.w, right: 16.w, top: 32.h, bottom: 16.h),) + ], + ).paddingSymmetrical(24.w, 24.h), + ), + ); + } + + Widget periodSelectorView(Function(int) onItemSelected) { + return DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h), + child: Row( + children: [ + Expanded( + child: CustomTabBar( + activeTextColor: Color(0xffED1C2B), + activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), + tabs: [ + CustomTabBarModel(null, "D"), + CustomTabBarModel(null, "W"), + CustomTabBarModel(null, "M"), + CustomTabBarModel(null, "6M"), + CustomTabBarModel(null, "Y"), + // CustomTabBarModel(null, "Completed".needTranslation), + ], + shouldTabExpanded: true, + onTabChange: (index) { + //todo handle the selection from viewmodel + setState(() { + this.index = index; + }); + }, + ), + ), + ], + ).paddingSymmetrical(4.w, 4.h)); + } + + Widget activityDetails() { + return DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h, hasShadow: true), + child: Column( + spacing: 8.h, + children: [ + Column( + children: [widget.selectedActivity.toText32(weight: FontWeight.w600, color: AppColors.textColor), "Average".toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor)], + ), + Row( + children: ["3232".toText24(color: AppColors.textGreenColor, fontWeight: FontWeight.w600), "(20-21)".toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500)], + ) + ], + ).paddingSymmetrical(16.w, 16.h)); + } + + + Widget activityGraph() { + final _random = Random(); + + int randomBP() => 100 + _random.nextInt(51); // 100–150 + final List data6Months = List.generate(6, (index) { + final date = DateTime.now().subtract(Duration(days: 30 * (5 - index))); + + final value = randomBP(); + + return DataPoint( + value: value.toDouble(), + label: value.toString(), + actualValue: value.toString(), + displayTime: DateFormat('MMM').format(date), + unitOfMeasurement: 'mmHg', + time: date, + ); + }); + final List data12Months = List.generate(12, (index) { + final date = DateTime.now().subtract(Duration(days: 30 * (11 - index))); + + final value = randomBP(); + + return DataPoint( + value: value.toDouble(), + label: value.toString(), + actualValue: value.toString(), + displayTime: DateFormat('MMM').format(date), + unitOfMeasurement: 'mmHg', + time: date, + ); + }); + + List data =[]; + if(index == 0){ + data = data6Months; + } else if(index == 1){ + data = data12Months; + } else + data = [ + DataPoint( + value: 128, + label: "128", + actualValue: '128', + displayTime: 'Sun', + unitOfMeasurement: 'mmHg', + time: DateTime.now().subtract(const Duration(days: 6)), + ), + DataPoint( + value: 135, + label: "135", + actualValue: '135', + displayTime: 'Mon', + unitOfMeasurement: 'mmHg', + time: DateTime.now().subtract(const Duration(days: 5)), + ), + DataPoint( + value: 122, + label: "122", + actualValue: '122', + displayTime: 'Tue', + unitOfMeasurement: 'mmHg', + time: DateTime.now().subtract(const Duration(days: 4)), + ), + DataPoint( + value: 140, + label: "140", + actualValue: '140', + displayTime: 'Wed', + unitOfMeasurement: 'mmHg', + time: DateTime.now().subtract(const Duration(days: 3)), + ), + DataPoint( + value: 118, + label: "118", + actualValue: '118', + displayTime: 'Thu', + unitOfMeasurement: 'mmHg', + time: DateTime.now().subtract(const Duration(days: 2)), + ), + DataPoint( + value: 125, + label: "125", + actualValue: '125', + displayTime: 'Fri', + unitOfMeasurement: 'mmHg', + time: DateTime.now().subtract(const Duration(days: 1)), + ), + DataPoint( + value: 130, + label: "130", + actualValue: '130', + displayTime: 'Sat', + unitOfMeasurement: 'mmHg', + time: DateTime.now(), + ), + ]; + + return CustomBarChart( + dataPoints: data, + height: 300.h, + maxY: 150, + barColor: AppColors.bgGreenColor, + barWidth: 26.w, + barRadius: BorderRadius.circular(8), + bottomLabelColor: Colors.black, + bottomLabelSize: 12, + leftLabelInterval: .1, + leftLabelReservedSize: 40, + // Left axis label formatter (Y-axis) + leftLabelFormatter: (value) { + var labelValue = double.tryParse(value.toStringAsFixed(0)); + + if (labelValue == null) return SizedBox.shrink(); + if (labelValue == 0 || labelValue == 150 / 2 || labelValue == 150) { + return Text( + labelValue.toStringAsFixed(0), + style: const TextStyle( + color: Colors.black26, + fontSize: 11, + ), + ); + } + + return SizedBox.shrink(); + }, + + // Bottom axis label formatter (X-axis - Days) + bottomLabelFormatter: (value, dataPoints) { + final index = value.toInt(); + if (index >= 0 && index < dataPoints.length) { + return Text( + dataPoints[index].displayTime, + style: const TextStyle( + color: Colors.grey, + fontSize: 11, + ), + ); + } + return const Text(''); + }, + verticalInterval: 1/data.length, + getDrawingVerticalLine: (value) { + return FlLine( + color: AppColors.greyColor, + strokeWidth: 1, + ); + }, + showGridLines: true, + ); + } +} diff --git a/lib/presentation/smartwatches/smart_watch_activity.dart b/lib/presentation/smartwatches/smart_watch_activity.dart new file mode 100644 index 00000000..531139b8 --- /dev/null +++ b/lib/presentation/smartwatches/smart_watch_activity.dart @@ -0,0 +1,147 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/presentation/smartwatches/activity_detail.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; + +class SmartWatchActivity extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: "All Health Data".needTranslation, + child: Column( + spacing: 16.h, + children: [ + resultItem( + leadingIcon: AppAssets.watchActivity, + title: "Activity".needTranslation, + description: "Activity rings give you a quick visual reference of how active you are each day. ".needTranslation, + trailingIcon: AppAssets.watchActivityTrailing, + result: "21", + unitsOfMeasure: "Cal" + ).onPress((){ + getIt.get().pushPage(page: ActivityDetails( selectedActivity: "Steps")); + }), + resultItem( + leadingIcon: AppAssets.watchSteps, + title: "Steps".needTranslation, + description: "Step count is the number of steps you take throughout the day.".needTranslation, + trailingIcon: AppAssets.watchStepsTrailing, + result: "21", + unitsOfMeasure: "Steps" + ), + resultItem( + leadingIcon: AppAssets.watchSteps, + title: "Walking + Running Distance".needTranslation, + description: "This will show you the total distance you covered in a day".needTranslation, + trailingIcon: AppAssets.watchStepsTrailing, + result: "21", + unitsOfMeasure: "km" + ), + resultItem( + leadingIcon: AppAssets.watchSleep, + title: "Sleep Score".needTranslation, + description: "This will keep track of how much hours you sleep in a day".needTranslation, + trailingIcon: AppAssets.watchSleepTrailing, + result: "21", + unitsOfMeasure: "hr", + resultSecondValue: "2", + unitOfSecondMeasure: "min" + ), + resultItem( + leadingIcon: AppAssets.watchBmi, + title: "Body Mass Index".needTranslation, + description: "This will calculate your weight and height ratio to analyze the ".needTranslation, + trailingIcon: AppAssets.watchBmiTrailing, + result: "21", + unitsOfMeasure: "BMI" + ), + resultItem( + leadingIcon: AppAssets.watchWeight, + title: "Weight".needTranslation, + description: "This will calculate your weight to keep track and update history".needTranslation, + trailingIcon: AppAssets.watchWeightTrailing, + result: "21", + unitsOfMeasure: "Kg" + ), + resultItem( + leadingIcon: AppAssets.watchWeight, + title: "Height".needTranslation, + description: "This will calculate your height to keep track and update history".needTranslation, + trailingIcon: AppAssets.watchWeightTrailing, + result: "21", + unitsOfMeasure: "ft" + ) + ], + ).paddingSymmetrical(24.w, 24.h), + )); + } + + Widget resultItem({ + required String leadingIcon, + required String title, + required String description, + required String trailingIcon, + required String result, + required String unitsOfMeasure, + String? resultSecondValue, + String? unitOfSecondMeasure + }) { + return DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h), + child: Row( + spacing: 16.w, + children: [ + Expanded( + child:Column( + spacing: 8.h, + children: [ + Row( + spacing: 8.w, + children: [ + Utils.buildSvgWithAssets(icon: leadingIcon, height: 16.h, width: 14.w), + title.toText16( weight: FontWeight.w600, color: AppColors.textColor), + ], + ), + description.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + Row( + crossAxisAlignment: CrossAxisAlignment.baseline, + textBaseline: TextBaseline.alphabetic, + spacing: 2.h, + children: [ + result.toText21(weight: FontWeight.w600, color: AppColors.textColor), + unitsOfMeasure.toText10(weight: FontWeight.w500, color:AppColors.greyTextColor ), + if(resultSecondValue != null) + Visibility( + visible: resultSecondValue != null , + child: Row( + crossAxisAlignment: CrossAxisAlignment.baseline, + textBaseline: TextBaseline.alphabetic, + spacing: 2.h, + children: [ + SizedBox(width: 2.w,), + resultSecondValue.toText21(weight: FontWeight.w600, color: AppColors.textColor), + unitOfSecondMeasure!.toText10(weight: FontWeight.w500, color:AppColors.greyTextColor ) + ], + ), + ) + ], + ), + + ], + ) , + ), + Utils.buildSvgWithAssets(icon: trailingIcon, width: 72.w, height: 72.h), + ], + ).paddingSymmetrical(16.w, 16.h) + ); + } +} diff --git a/lib/presentation/smartwatches/smartwatch_home_page.dart b/lib/presentation/smartwatches/smartwatch_home_page.dart index 8fd79098..050cbf7d 100644 --- a/lib/presentation/smartwatches/smartwatch_home_page.dart +++ b/lib/presentation/smartwatches/smartwatch_home_page.dart @@ -3,12 +3,16 @@ import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/common_models/smart_watch.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/smartwatch_health_data/health_provider.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/smartwatches/smartwatch_instructions_page.dart'; import 'package:hmg_patient_app_new/presentation/smartwatches/widgets/supported_watches_list.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -81,6 +85,14 @@ class SmartwatchHomePage extends StatelessWidget { text: LocaleKeys.select.tr(context: context), onPressed: () { context.read().setSelectedWatchType("apple", "assets/images/png/smartwatches/apple-watch-5.jpg"); + getIt.get().pushPage(page: SmartwatchInstructionsPage( + smartwatchDetails: SmartwatchDetails(SmartWatchTypes.apple, + "assets/images/png/smartwatches/apple-watch-5.jpg", + AppAssets.bluetooth, + LocaleKeys.applehealthapplicationshouldbeinstalledinyourphone.tr(context: context), + LocaleKeys.unabletodetectapplicationinstalledpleasecomebackonceinstalled.tr(context: context), + LocaleKeys.applewatchshouldbeconnected.tr(context: context)), + )); }, backgroundColor: AppColors.primaryRedColor.withAlpha(40), borderColor: AppColors.primaryRedColor.withAlpha(0), @@ -106,7 +118,14 @@ class SmartwatchHomePage extends StatelessWidget { text: LocaleKeys.select.tr(context: context), onPressed: () { context.read().setSelectedWatchType("samsung", "assets/images/png/smartwatches/galaxy_watch_8_classic.jpeg"); - }, + getIt.get().pushPage(page: SmartwatchInstructionsPage( + smartwatchDetails: SmartwatchDetails(SmartWatchTypes.samsung, + "assets/images/png/smartwatches/galaxy_watch_8_classic.jpeg", + AppAssets.bluetooth, + LocaleKeys.samsunghealthapplicationshouldbeinstalledinyourphone.tr(context: context), + LocaleKeys.unabletodetectapplicationinstalledpleasecomebackonceinstalled.tr(context: context), + LocaleKeys.samsungwatchshouldbeconnected.tr(context: context)), + )); }, backgroundColor: AppColors.primaryRedColor.withAlpha(40), borderColor: AppColors.primaryRedColor.withAlpha(0), textColor: AppColors.primaryRedColor, @@ -131,6 +150,14 @@ class SmartwatchHomePage extends StatelessWidget { text: LocaleKeys.select.tr(context: context), onPressed: () { context.read().setSelectedWatchType("huawei", "assets/images/png/smartwatches/Huawei_Watch.png"); + getIt.get().pushPage(page: SmartwatchInstructionsPage( + smartwatchDetails: SmartwatchDetails(SmartWatchTypes.huawei, + "assets/images/png/smartwatches/Huawei_Watch.png", + AppAssets.bluetooth, + LocaleKeys.huaweihealthapplicationshouldbeinstalledinyourphone.tr(context: context), + LocaleKeys.unabletodetectapplicationinstalledpleasecomebackonceinstalled.tr(context: context), + LocaleKeys.huaweiwatchshouldbeconnected.tr(context: context)), + )); }, backgroundColor: AppColors.primaryRedColor.withAlpha(40), borderColor: AppColors.primaryRedColor.withAlpha(0), @@ -156,6 +183,14 @@ class SmartwatchHomePage extends StatelessWidget { text: LocaleKeys.select.tr(context: context), onPressed: () { context.read().setSelectedWatchType("whoop", "assets/images/png/smartwatches/Whoop_Watch.png"); + getIt.get().pushPage(page: SmartwatchInstructionsPage( + smartwatchDetails: SmartwatchDetails(SmartWatchTypes.whoop, + "assets/images/png/smartwatches/Whoop_Watch.png", + AppAssets.bluetooth, + LocaleKeys.whoophealthapplicationshouldbeinstalledinyourphone.tr(context: context), + LocaleKeys.unabletodetectapplicationinstalledpleasecomebackonceinstalled.tr(context: context), + LocaleKeys.whoopwatchshouldbeconnected.tr(context: context)), + )); }, backgroundColor: AppColors.primaryRedColor.withAlpha(40), borderColor: AppColors.primaryRedColor.withAlpha(0), diff --git a/lib/presentation/smartwatches/smartwatch_instructions_page.dart b/lib/presentation/smartwatches/smartwatch_instructions_page.dart index 48683d50..949b460f 100644 --- a/lib/presentation/smartwatches/smartwatch_instructions_page.dart +++ b/lib/presentation/smartwatches/smartwatch_instructions_page.dart @@ -1,15 +1,24 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/common_models/smart_watch.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/smartwatches/smart_watch_activity.dart' show SmartWatchActivity; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import '../../core/utils/utils.dart'; + class SmartwatchInstructionsPage extends StatelessWidget { - const SmartwatchInstructionsPage({super.key}); + final SmartwatchDetails smartwatchDetails; + + const SmartwatchInstructionsPage({super.key, required this.smartwatchDetails}); @override Widget build(BuildContext context) { @@ -25,6 +34,7 @@ class SmartwatchInstructionsPage extends StatelessWidget { child: CustomButton( text: LocaleKeys.getStarted.tr(context: context), onPressed: () { + getIt.get().pushPage(page: SmartWatchActivity()); }, backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, @@ -35,8 +45,55 @@ class SmartwatchInstructionsPage extends StatelessWidget { height: 50.h, ).paddingSymmetrical(24.w, 30.h), ), - child: SingleChildScrollView(), + child: Column( + mainAxisSize: MainAxisSize.max, + spacing: 18.h, + children: [ + Image.asset(smartwatchDetails.watchIcon, fit: BoxFit.contain, height: 280.h,width: 280.w,), + DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h), + child: Column( + children: [ + watchContentDetails( + title: smartwatchDetails.detailsTitle, + description: smartwatchDetails.details, + icon: smartwatchDetails.smallIcon, + descriptionTextColor: AppColors.primaryRedColor + ), + Divider( + color: AppColors.dividerColor, + thickness: 1.h, + ).paddingOnly(top: 16.h, bottom: 16.h), + watchContentDetails( + title: smartwatchDetails.secondTitle, + description: LocaleKeys.updatetheinformation.tr(), + icon: AppAssets.bluetooth, + descriptionTextColor: AppColors.greyTextColor + ), + ], + ).paddingSymmetrical(16.w, 16.h), + ) + ], + ).paddingSymmetrical(24.w, 16.h), ), ); } + + + Widget watchContentDetails({required String title, required String description, required String icon, required Color descriptionTextColor}) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 8.h, + children: [ + DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h), + child: Utils.buildSvgWithAssets(icon: icon, width: 40.w, height: 40.h), + + ), + + title.toText16(weight: FontWeight.w600, color: AppColors.textColor), + description.toText12(fontWeight: FontWeight.w500, color: descriptionTextColor) + ], + ); + } } diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 9cf0ed91..8af9789d 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -39,6 +39,7 @@ import '../features/monthly_reports/monthly_reports_repo.dart'; import '../features/monthly_reports/monthly_reports_view_model.dart'; import '../features/qr_parking/qr_parking_view_model.dart'; import '../presentation/parking/paking_page.dart'; +import '../presentation/smartwatches/smartwatch_instructions_page.dart'; import '../services/error_handler_service.dart'; class AppRoutes { diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index d56178b7..f450aebb 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -113,4 +113,5 @@ class AppColors { static const Color covid29Color = Color(0xff2563EB); // #2563EB static const Color lightGreyTextColor = Color(0xFF959595); static const Color labelColorYellow = Color(0xFFFBCB6E); + static const Color tooltipColor = Color(0xFF1AACACAC); } diff --git a/lib/widgets/custom_tab_bar.dart b/lib/widgets/custom_tab_bar.dart index c30c0700..03f1b25a 100644 --- a/lib/widgets/custom_tab_bar.dart +++ b/lib/widgets/custom_tab_bar.dart @@ -21,6 +21,7 @@ class CustomTabBar extends StatefulWidget { final Color inActiveTextColor; final Color inActiveBackgroundColor; final Function(int)? onTabChange; + final bool shouldTabExpanded; const CustomTabBar({ super.key, @@ -31,6 +32,7 @@ class CustomTabBar extends StatefulWidget { this.activeBackgroundColor = const Color(0x142E3039), this.inActiveBackgroundColor = Colors.white, this.onTabChange, + this.shouldTabExpanded = false }); @override @@ -58,6 +60,11 @@ class CustomTabBarState extends State { @override Widget build(BuildContext context) { late Widget parentWidget; + if(widget.shouldTabExpanded){ + return Row( + children:List.generate(widget.tabs.length, (index)=>myTab(widget.tabs[index], index).expanded), + ); + } if (widget.tabs.length > 3) { parentWidget = ListView.separated( diff --git a/lib/widgets/graph/CustomBarGraph.dart b/lib/widgets/graph/CustomBarGraph.dart new file mode 100644 index 00000000..b90ddb00 --- /dev/null +++ b/lib/widgets/graph/CustomBarGraph.dart @@ -0,0 +1,240 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/common_models/data_points.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; + +/// A customizable bar chart widget using `fl_chart`. +/// +/// Displays a bar chart with configurable axis labels, colors, and data points. +/// Useful for visualizing comparative data, categories, or grouped values. +/// +/// **Parameters:** +/// - [dataPoints]: List of `DataPoint` objects to plot. +/// - [secondaryDataPoints]: Optional list for grouped bars (e.g., comparison data). +/// - [leftLabelFormatter]: Function to build left axis labels. +/// - [bottomLabelFormatter]: Function to build bottom axis labels. +/// - [width]: Optional width of the chart. +/// - [height]: Required height of the chart. +/// - [maxY], [maxX], [minX]: Axis bounds. +/// - [barColor]: Color of the bars. +/// - [secondaryBarColor]: Color of the secondary bars. +/// - [barRadius]: Border radius for bar corners. +/// - [barWidth]: Width of each bar. +/// - [bottomLabelColor]: Color of bottom axis labels. +/// - [bottomLabelSize]: Font size for bottom axis labels. +/// - [bottomLabelFontWeight]: Font weight for bottom axis labels. +/// - [leftLabelInterval]: Interval between left axis labels. +/// - [leftLabelReservedSize]: Reserved space for left axis labels. +/// - [showBottomTitleDates]: Whether to show bottom axis labels. +/// - [isFullScreeGraph]: Whether the graph is fullscreen. +/// - [makeGraphBasedOnActualValue]: Use `actualValue` for plotting. +/// +/// Example usage: +/// ```dart +/// CustomBarChart( +/// dataPoints: sampleData, +/// leftLabelFormatter: (value) => ..., +/// bottomLabelFormatter: (value, dataPoints) => ..., +/// height: 300, +/// maxY: 100, +/// ) +class CustomBarChart extends StatelessWidget { + final List dataPoints; + final List? secondaryDataPoints; // For grouped bar charts + final double? width; + final double height; + final double? maxY; + final double? maxX; + final double? minX; + final Color barColor; + final Color? secondaryBarColor; + final Color barGridColor; + final Color bottomLabelColor; + final double? bottomLabelSize; + final FontWeight? bottomLabelFontWeight; + final double? leftLabelInterval; + final double? leftLabelReservedSize; + final double? bottomLabelReservedSize; + final bool? showGridLines; + final GetDrawingGridLine? getDrawingVerticalLine; + final double? verticalInterval; + final double? minY; + final BorderRadius? barRadius; + final double barWidth; + + /// Creates the left label and provides it to the chart + final Widget Function(double) leftLabelFormatter; + final Widget Function(double, List) bottomLabelFormatter; + + final bool showBottomTitleDates; + final bool isFullScreeGraph; + final bool makeGraphBasedOnActualValue; + + const CustomBarChart( + {super.key, + required this.dataPoints, + this.secondaryDataPoints, + required this.leftLabelFormatter, + this.width, + required this.height, + this.maxY, + this.maxX, + this.showBottomTitleDates = true, + this.isFullScreeGraph = false, + this.barColor = AppColors.bgGreenColor, + this.secondaryBarColor, + this.barGridColor = AppColors.graphGridColor, + this.bottomLabelColor = AppColors.textColor, + this.bottomLabelFontWeight = FontWeight.w500, + this.bottomLabelSize, + this.leftLabelInterval, + this.leftLabelReservedSize, + this.bottomLabelReservedSize, + this.makeGraphBasedOnActualValue = false, + required this.bottomLabelFormatter, + this.minX, + this.showGridLines = false, + this.getDrawingVerticalLine, + this.verticalInterval, + this.minY, + this.barRadius, + this.barWidth = 16}); + + @override + Widget build(BuildContext context) { + return Material( + color: Colors.white, + child: SizedBox( + width: width, + height: height, + child: BarChart( + BarChartData( + minY: minY ?? 0, + maxY: maxY, + + barTouchData: BarTouchData( + handleBuiltInTouches: true, + touchCallback: (FlTouchEvent event, BarTouchResponse? touchResponse) { + // Let fl_chart handle the touch + }, + + touchTooltipData: BarTouchTooltipData( + getTooltipColor: (_)=>AppColors.tooltipColor, + getTooltipItem: (group, groupIndex, rod, rodIndex) { + final dataPoint = dataPoints[groupIndex]; + return BarTooltipItem( + '${dataPoint.actualValue} ${dataPoint.unitOfMeasurement ?? ""}\n${DateFormat('dd MMM, yyyy').format(dataPoint.time)}', + TextStyle( + color: Colors.black, + fontSize: 12.f, + fontWeight: FontWeight.w500, + ), + ); + }, + ), + enabled: true, + ), + titlesData: FlTitlesData( + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: leftLabelReservedSize ?? 80, + interval: leftLabelInterval ?? .1, + getTitlesWidget: (value, _) { + return leftLabelFormatter(value); + }, + ), + ), + bottomTitles: AxisTitles( + axisNameSize: 20, + sideTitles: SideTitles( + showTitles: showBottomTitleDates, + reservedSize: bottomLabelReservedSize ?? 30, + getTitlesWidget: (value, _) { + return bottomLabelFormatter(value, dataPoints); + }, + interval: 1, + ), + ), + topTitles: AxisTitles(), + rightTitles: AxisTitles(), + ), + borderData: FlBorderData( + show: true, + border: const Border( + bottom: BorderSide.none, + left: BorderSide(color: Colors.grey, width: .5), + right: BorderSide.none, + top: BorderSide.none, + ), + ), + barGroups: _buildBarGroups(dataPoints), + + gridData: FlGridData( + show: showGridLines ?? true, + drawHorizontalLine: false, + verticalInterval: verticalInterval, + getDrawingVerticalLine: getDrawingVerticalLine ?? + (value) { + return FlLine( + color: barGridColor, + strokeWidth: 1, + dashArray: [5, 5], + ); + }, + )), + ), + ), + ); + } + + /// Builds bar chart groups from data points + List _buildBarGroups(List dataPoints) { + return dataPoints.asMap().entries.map((entry) { + final index = entry.key; + final dataPoint = entry.value; + double value = (makeGraphBasedOnActualValue) + ? double.tryParse(dataPoint.actualValue) ?? 0.0 + : dataPoint.value; + + final barRods = [ + BarChartRodData( + toY: value, + color: barColor, + width: barWidth, + borderRadius: barRadius ?? BorderRadius.circular(6), + // backDrawRodData: BackgroundBarChartRodData( + // show: true, + // toY: maxY, + // color: Colors.grey[100], + // ), + ), + ]; + + // Add secondary bar if provided (for grouped bar charts) + if (secondaryDataPoints != null && index < secondaryDataPoints!.length) { + final secondaryDataPoint = secondaryDataPoints![index]; + double secondaryValue = (makeGraphBasedOnActualValue) + ? double.tryParse(secondaryDataPoint.actualValue) ?? 0.0 + : secondaryDataPoint.value; + + barRods.add( + BarChartRodData( + toY: secondaryValue, + color: secondaryBarColor ?? AppColors.blueColor, + width: barWidth, + borderRadius: barRadius ?? BorderRadius.circular(6), + ), + ); + } + + return BarChartGroupData( + x: index, + barRods: barRods, + barsSpace: 8.w + ); + }).toList(); + } +} \ No newline at end of file