From 780fa11e83740fa992a7d32e8eb537c90e4124e7 Mon Sep 17 00:00:00 2001 From: Alireza Hajebrahimi <6937697+iarata@users.noreply.github.com> Date: Wed, 4 Feb 2026 14:58:08 +0100 Subject: [PATCH] Health package moved to its own repo --- packages/health/.metadata | 10 - packages/health/CHANGELOG.md | 470 ----- packages/health/LICENSE | 9 - packages/health/README.md | 550 ------ packages/health/analysis_options.yaml | 19 - packages/health/android/build.gradle | 61 - packages/health/android/gradle.properties | 3 - .../gradle/wrapper/gradle-wrapper.properties | 6 - packages/health/android/settings.gradle | 1 - .../android/src/main/AndroidManifest.xml | 2 - .../cachet/plugins/health/HealthConstants.kt | 241 --- .../plugins/health/HealthDataConverter.kt | 258 --- .../plugins/health/HealthDataOperations.kt | 332 ---- .../cachet/plugins/health/HealthDataReader.kt | 522 ----- .../cachet/plugins/health/HealthDataWriter.kt | 867 --------- .../cachet/plugins/health/HealthPlugin.kt | 366 ---- .../plugins/health/HealthRecordingFilter.kt | 43 - .../.flutter-plugins-dependencies~master | 1 - packages/health/example/.gitignore | 45 - packages/health/example/.metadata | 45 - packages/health/example/README.md | 16 - packages/health/example/analysis_options.yaml | 28 - packages/health/example/android/.gitignore | 14 - .../example/android/app/build.gradle.kts | 44 - .../android/app/src/debug/AndroidManifest.xml | 7 - .../android/app/src/main/AndroidManifest.xml | 122 -- .../health/health_example/MainActivity.kt | 8 - .../res/drawable-v21/launch_background.xml | 12 - .../main/res/drawable/launch_background.xml | 12 - .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 544 -> 0 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 442 -> 0 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 721 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 1031 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 1443 -> 0 bytes .../app/src/main/res/values-night/styles.xml | 18 - .../app/src/main/res/values/styles.xml | 18 - .../app/src/profile/AndroidManifest.xml | 7 - .../health/example/android/build.gradle.kts | 21 - .../health/example/android/gradle.properties | 3 - .../gradle/wrapper/gradle-wrapper.properties | 5 - .../example/android/settings.gradle.kts | 25 - packages/health/example/ios/.gitignore | 34 - .../ios/Flutter/AppFrameworkInfo.plist | 26 - .../health/example/ios/Flutter/Debug.xcconfig | 2 - .../example/ios/Flutter/Release.xcconfig | 2 - packages/health/example/ios/Podfile | 43 - .../ios/Runner.xcodeproj/project.pbxproj | 794 -------- .../contents.xcworkspacedata | 7 - .../xcshareddata/IDEWorkspaceChecks.plist | 8 - .../xcshareddata/WorkspaceSettings.xcsettings | 8 - .../xcshareddata/xcschemes/Runner.xcscheme | 119 -- .../contents.xcworkspacedata | 10 - .../xcshareddata/IDEWorkspaceChecks.plist | 8 - .../xcshareddata/WorkspaceSettings.xcsettings | 8 - .../example/ios/Runner/AppDelegate.swift | 13 - .../AppIcon.appiconset/Contents.json | 122 -- .../Icon-App-1024x1024@1x.png | Bin 10932 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 295 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 406 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 450 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 282 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 462 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 704 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 406 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 586 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 862 -> 0 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 862 -> 0 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 1674 -> 0 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 762 -> 0 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 1226 -> 0 bytes .../Icon-App-83.5x83.5@2x.png | Bin 1418 -> 0 bytes .../LaunchImage.imageset/Contents.json | 23 - .../LaunchImage.imageset/LaunchImage.png | Bin 68 -> 0 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 68 -> 0 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 68 -> 0 bytes .../LaunchImage.imageset/README.md | 5 - .../Runner/Base.lproj/LaunchScreen.storyboard | 37 - .../ios/Runner/Base.lproj/Main.storyboard | 26 - packages/health/example/ios/Runner/Info.plist | 53 - .../ios/Runner/Runner-Bridging-Header.h | 1 - .../example/ios/Runner/Runner.entitlements | 8 - .../example/ios/RunnerTests/RunnerTests.swift | 12 - packages/health/example/lib/main.dart | 1003 ---------- packages/health/example/lib/util.dart | 109 -- packages/health/example/pubspec.yaml | 25 - packages/health/ios/Assets/.gitkeep | 0 .../health/ios/Classes/HealthConstants.swift | 216 --- .../ios/Classes/HealthDataOperations.swift | 323 ---- .../health/ios/Classes/HealthDataReader.swift | 925 --------- .../health/ios/Classes/HealthDataWriter.swift | 440 ----- packages/health/ios/Classes/HealthPlugin.h | 4 - packages/health/ios/Classes/HealthPlugin.m | 8 - .../health/ios/Classes/HealthUtilities.swift | 153 -- .../ios/Classes/SwiftHealthPlugin.swift | 635 ------ packages/health/ios/health.podspec | 23 - packages/health/lib/health.dart | 21 - packages/health/lib/health.g.dart | 631 ------ packages/health/lib/health.json.dart | 30 - packages/health/lib/src/functions.dart | 46 - .../health/lib/src/health_data_point.dart | 225 --- packages/health/lib/src/health_plugin.dart | 1709 ----------------- .../health/lib/src/health_value_types.dart | 976 ---------- packages/health/lib/src/heath_data_types.dart | 630 ------ packages/health/lib/src/workout_summary.dart | 52 - packages/health/pubspec.yaml | 39 - packages/health/test/health_test.dart | 337 ---- .../health/test/mocks/device_info_mock.dart | 60 - packages/health/test/swift_test.dart | 174 -- 108 files changed, 14374 deletions(-) delete mode 100644 packages/health/.metadata delete mode 100644 packages/health/CHANGELOG.md delete mode 100644 packages/health/LICENSE delete mode 100644 packages/health/README.md delete mode 100644 packages/health/analysis_options.yaml delete mode 100644 packages/health/android/build.gradle delete mode 100644 packages/health/android/gradle.properties delete mode 100644 packages/health/android/gradle/wrapper/gradle-wrapper.properties delete mode 100644 packages/health/android/settings.gradle delete mode 100644 packages/health/android/src/main/AndroidManifest.xml delete mode 100644 packages/health/android/src/main/kotlin/cachet/plugins/health/HealthConstants.kt delete mode 100644 packages/health/android/src/main/kotlin/cachet/plugins/health/HealthDataConverter.kt delete mode 100644 packages/health/android/src/main/kotlin/cachet/plugins/health/HealthDataOperations.kt delete mode 100644 packages/health/android/src/main/kotlin/cachet/plugins/health/HealthDataReader.kt delete mode 100644 packages/health/android/src/main/kotlin/cachet/plugins/health/HealthDataWriter.kt delete mode 100644 packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt delete mode 100644 packages/health/android/src/main/kotlin/cachet/plugins/health/HealthRecordingFilter.kt delete mode 100644 packages/health/example/.flutter-plugins-dependencies~master delete mode 100644 packages/health/example/.gitignore delete mode 100644 packages/health/example/.metadata delete mode 100644 packages/health/example/README.md delete mode 100644 packages/health/example/analysis_options.yaml delete mode 100644 packages/health/example/android/.gitignore delete mode 100644 packages/health/example/android/app/build.gradle.kts delete mode 100644 packages/health/example/android/app/src/debug/AndroidManifest.xml delete mode 100644 packages/health/example/android/app/src/main/AndroidManifest.xml delete mode 100644 packages/health/example/android/app/src/main/kotlin/cachet/plugins/health/health_example/MainActivity.kt delete mode 100644 packages/health/example/android/app/src/main/res/drawable-v21/launch_background.xml delete mode 100644 packages/health/example/android/app/src/main/res/drawable/launch_background.xml delete mode 100644 packages/health/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png delete mode 100644 packages/health/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png delete mode 100644 packages/health/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png delete mode 100644 packages/health/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png delete mode 100644 packages/health/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 packages/health/example/android/app/src/main/res/values-night/styles.xml delete mode 100644 packages/health/example/android/app/src/main/res/values/styles.xml delete mode 100644 packages/health/example/android/app/src/profile/AndroidManifest.xml delete mode 100644 packages/health/example/android/build.gradle.kts delete mode 100644 packages/health/example/android/gradle.properties delete mode 100644 packages/health/example/android/gradle/wrapper/gradle-wrapper.properties delete mode 100644 packages/health/example/android/settings.gradle.kts delete mode 100644 packages/health/example/ios/.gitignore delete mode 100644 packages/health/example/ios/Flutter/AppFrameworkInfo.plist delete mode 100644 packages/health/example/ios/Flutter/Debug.xcconfig delete mode 100644 packages/health/example/ios/Flutter/Release.xcconfig delete mode 100644 packages/health/example/ios/Podfile delete mode 100644 packages/health/example/ios/Runner.xcodeproj/project.pbxproj delete mode 100644 packages/health/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata delete mode 100644 packages/health/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 packages/health/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings delete mode 100644 packages/health/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme delete mode 100644 packages/health/example/ios/Runner.xcworkspace/contents.xcworkspacedata delete mode 100644 packages/health/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 packages/health/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings delete mode 100644 packages/health/example/ios/Runner/AppDelegate.swift delete mode 100644 packages/health/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 packages/health/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png delete mode 100644 packages/health/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png delete mode 100644 packages/health/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png delete mode 100644 packages/health/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png delete mode 100644 packages/health/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png delete mode 100644 packages/health/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png delete mode 100644 packages/health/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png delete mode 100644 packages/health/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png delete mode 100644 packages/health/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png delete mode 100644 packages/health/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png delete mode 100644 packages/health/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png delete mode 100644 packages/health/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png delete mode 100644 packages/health/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png delete mode 100644 packages/health/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png delete mode 100644 packages/health/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png delete mode 100644 packages/health/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json delete mode 100644 packages/health/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png delete mode 100644 packages/health/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png delete mode 100644 packages/health/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png delete mode 100644 packages/health/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md delete mode 100644 packages/health/example/ios/Runner/Base.lproj/LaunchScreen.storyboard delete mode 100644 packages/health/example/ios/Runner/Base.lproj/Main.storyboard delete mode 100644 packages/health/example/ios/Runner/Info.plist delete mode 100644 packages/health/example/ios/Runner/Runner-Bridging-Header.h delete mode 100644 packages/health/example/ios/Runner/Runner.entitlements delete mode 100644 packages/health/example/ios/RunnerTests/RunnerTests.swift delete mode 100644 packages/health/example/lib/main.dart delete mode 100644 packages/health/example/lib/util.dart delete mode 100644 packages/health/example/pubspec.yaml delete mode 100644 packages/health/ios/Assets/.gitkeep delete mode 100644 packages/health/ios/Classes/HealthConstants.swift delete mode 100644 packages/health/ios/Classes/HealthDataOperations.swift delete mode 100644 packages/health/ios/Classes/HealthDataReader.swift delete mode 100644 packages/health/ios/Classes/HealthDataWriter.swift delete mode 100644 packages/health/ios/Classes/HealthPlugin.h delete mode 100644 packages/health/ios/Classes/HealthPlugin.m delete mode 100644 packages/health/ios/Classes/HealthUtilities.swift delete mode 100644 packages/health/ios/Classes/SwiftHealthPlugin.swift delete mode 100644 packages/health/ios/health.podspec delete mode 100644 packages/health/lib/health.dart delete mode 100644 packages/health/lib/health.g.dart delete mode 100644 packages/health/lib/health.json.dart delete mode 100644 packages/health/lib/src/functions.dart delete mode 100644 packages/health/lib/src/health_data_point.dart delete mode 100644 packages/health/lib/src/health_plugin.dart delete mode 100644 packages/health/lib/src/health_value_types.dart delete mode 100644 packages/health/lib/src/heath_data_types.dart delete mode 100644 packages/health/lib/src/workout_summary.dart delete mode 100644 packages/health/pubspec.yaml delete mode 100644 packages/health/test/health_test.dart delete mode 100644 packages/health/test/mocks/device_info_mock.dart delete mode 100644 packages/health/test/swift_test.dart diff --git a/packages/health/.metadata b/packages/health/.metadata deleted file mode 100644 index 969bc2761..000000000 --- a/packages/health/.metadata +++ /dev/null @@ -1,10 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled and should not be manually edited. - -version: - revision: cc949a8e8b9cf394b9290a8e80f87af3e207dce5 - channel: beta - -project_type: plugin diff --git a/packages/health/CHANGELOG.md b/packages/health/CHANGELOG.md deleted file mode 100644 index 44de9d899..000000000 --- a/packages/health/CHANGELOG.md +++ /dev/null @@ -1,470 +0,0 @@ -## 13.2.0 - -* Add get health data by UUID (see `getHealthDataByUUID()`) - PR [#1193](https://github.com/carp-dk/flutter-plugins/pull/1193), [#1194](https://github.com/carp-dk/flutter-plugins/pull/1194) -* Add delete by UUID (`deleteByUUID()`) -* Add support for unit conversion in `WeightRecord`, `HeightRecord`, `BodyTemperatureRecord`, and `BloodGlucoseRecord` - PR [#1212](https://github.com/carp-dk/flutter-plugins/pull/1223) -* Update `compileSDK` to 36 - Fix [#1261](https://github.com/carp-dk/flutter-plugins/issues/1261) -* Update Gradle to 8.9.1 -* Update `org.jetbrains.kotlin.android` to 2.1.0 -* Update `androidx.health.connect:connect-client` to 1.1.0-rc03 -* Update `device_info_plus` to 12.1.0 - Fix [#1264](https://github.com/carp-dk/flutter-plugins/issues/1264) - -## 13.1.4 - -* Fix adding mindfulness resulted in crash in iOS -* Support SPM for iOS - -## 13.1.3 - -* Fix permissions issues with iOS -* Fix [#1231](https://github.com/cph-cachet/flutter-plugins/issues/1231) - -## 13.1.2 - -* Fix [#1250](https://github.com/cph-cachet/flutter-plugins/issues/1250) -* Fix [#1233](https://github.com/cph-cachet/flutter-plugins/issues/1233) - -## 13.1.1 - -* Fix [#1207](https://github.com/cph-cachet/flutter-plugins/issues/1207) - (**Important**: Some property names might have changed compared to before for `Nutrition`) -* Fix [#1201](https://github.com/cph-cachet/flutter-plugins/issues/1201) -* iOS: Add `APPLE_STAND_TIME`, `APPLE_STAND_HOUR`, and `APPLE_MOVE_TIME` health data types (READ ONLY) [#1190](https://github.com/cph-cachet/flutter-plugins/issues/1190) - -## 13.1.0 - -* Refactored Android native implementation (No Flutter API changes) -* Android: Add `SPEED` health data type - PR [#1183](https://github.com/cph-cachet/flutter-plugins/pull/1183) -* iOS: Add `WALKING_SPEED` health data type - PR [#1183](https://github.com/cph-cachet/flutter-plugins/pull/1183) -* Add `METER_PER_SECOND` health data unit - -## 13.0.1 - -* Refactored Swift native implementation - See PR [#1175](https://github.com/cph-cachet/flutter-plugins/pull/1175) and [#1208](https://github.com/cph-cachet/flutter-plugins/pull/1208) for more information: - -``` -SwiftHealthPlugin (Main Plugin Class) -├── HealthDataReader (Reading health data) -├── HealthDataWriter (Writing health data) -├── HealthDataOperations (Permissions and deletion) -├── HealthUtilities (Helper functions) -└── HealthConstants (Constants and enums) -``` - -## 13.0.0 - -* Refactored Swift native implementation - -## 12.2.1 - -* iOS: Add `swift_version` for add-to-app implementations - PR [#1205](https://github.com/cph-cachet/flutter-plugins/pull/1205) - -## 12.2.0 - -* iOS: Add `deviceModel` in returned Health data to identify the device that generated the data of the receiver. (in iOS `source_name` represents the revision of the source responsible for saving the receiver.) -* Android: Add read health data in background - PR [#1184](https://github.com/cph-cachet/flutter-plugins/pull/1184) -* Fix [#1169](https://github.com/cph-cachet/flutter-plugins/issues/1169) where `meal_type` property in `Nutrition` was null always -* iOS: Add `CARDIO_DANCE` HealthDataType - [#1146](https://github.com/cph-cachet/flutter-plugins/pull/1146) - -## 12.1.0 - -* Add delete record by UUID method. See function `deleteByUUID(required String uuid, HealthDataType? type)` -* iOS: Parse metadata to remove unsupported types - PR [#1120](https://github.com/cph-cachet/flutter-plugins/pull/1120) -* iOS: Add UV Index Types -* Android: Add request access to historic data [#1126](https://github.com/cph-cachet/flutter-plugins/issues/1126) - PR [#1127](https://github.com/cph-cachet/flutter-plugins/pull/1127) - -```XML - - -``` - -* Android: - * Update `androidx.compose:compose-bom` to `2025.02.00` - * Update `androidx.health.connect:connect-client` to `1.1.0-alpha11` - * Update `androidx.fragment:fragment-ktx` to `1.8.6` - * Update to Java 11 -* Update example apps - -## 12.0.1 - -* Update of API and README doc -* Fix [#1118](https://github.com/cph-cachet/flutter-plugins/issues/1118) - -## 12.0.0 - -* **BREAKING** This release introduces a significant architectural change to the `health` plugin by removing the `singleton` pattern. - * **Dependency Injection for `DeviceInfoPlugin`**: - * The `Health` class is no longer a singleton. - * The `Health()` factory constructor is removed. - * The `Health` class now accepts an (optional) `DeviceInfoPlugin` dependency through its constructor, this change was introduced to provide easy mocking of the `DeviceInfo` class during unit tests. - * This architectural change means that, for the application to work correctly, the `Health` class *MUST* be initialized correctly as a global instance. - * **Impact**: - * For most users, **no immediate code changes are required** but it is paramount to initialize the `Health` class as a global instance (i.e. do not call `Health()` every time but rather define an instance `final health = Health();`). -* **BREAKING** (Android) Remove automatic permission request of `DISTANCE_DELTA` and `TOTAL_CALORIES_BURNED` data types when requesting permission for `WORKOUT` health data type. - * For `WORKOUT`s that require above permissions, now those need to be requested manually. - * Fix [#984](https://github.com/cph-cachet/flutter-plugins/issues/984) - PR [#1055](https://github.com/cph-cachet/flutter-plugins/pull/1055) -* Add `LEAN_BODY_MASS` data type [#1078](https://github.com/cph-cachet/flutter-plugins/issues/1078) - PR [#1097](https://github.com/cph-cachet/flutter-plugins/pull/1097) - * The following AndroidManifest values are required to READ/WRITE `LEAN_BODY_MASS`: - - ```XML - - - ``` - -* iOS: Add `WATER_TEMPERATURE` and `UNDERWATER_DEPTH` health values [#1096](https://github.com/cph-cachet/flutter-plugins/issues/1096) -* iOS: Add support for `Underwater Diving` workout [#1096](https://github.com/cph-cachet/flutter-plugins/issues/1096) -* Fix [#1072](https://github.com/cph-cachet/flutter-plugins/issues/1072) and [#1074](https://github.com/cph-cachet/flutter-plugins/issues/1074) -* Fix issue where iOS delete not deleting own records - PR [#1104](https://github.com/cph-cachet/flutter-plugins/pull/1104) -* Fix [#950](https://github.com/cph-cachet/flutter-plugins/issues/950) - PR [#1103](https://github.com/cph-cachet/flutter-plugins/pull/1103) -* Fix [#1047](https://github.com/cph-cachet/flutter-plugins/issues/1047) and [#939](https://github.com/cph-cachet/flutter-plugins/issues/939) - PR [#1091](https://github.com/cph-cachet/flutter-plugins/pull/1091) -* Fix issue where `SLEEP_LIGHT` type was not aligned correctly - PR [#1086](https://github.com/cph-cachet/flutter-plugins/pull/1086) -* Fix [#1051](https://github.com/cph-cachet/flutter-plugins/issues/1051) - PR [#1052](https://github.com/cph-cachet/flutter-plugins/pull/1052) -* Updated `intl` to ^0.20.1 [#1092](https://github.com/cph-cachet/flutter-plugins/issues/1092) -* Updated `device_info_plus` to ^11.2.0 -* Example app: Updated `permission_handler` to ^11.3.1 - -## 11.1.1 - -* Fix of [#1059](https://github.com/cph-cachet/flutter-plugins/issues/1059) - -## 11.1.0 - -* Fix of [#1043](https://github.com/cph-cachet/flutter-plugins/issues/1043) -* Type-safe JSON deserialization using carp_serializable v. 2.0 - -## 11.0.0 - -* **BREAKING** Remove Google Fit support in the Android code, as well as Google FIt related dependencies and references throughout the documentation - * Remove `useHealthConnectIfAvailable` from the parameters of `Health().configure()` - * Remove the `disconnect` method which was previously used to disconnect from Google Fit. - * Remove the `flowRate` value from `writeBloodOxygen` as this is not supported by Health Connect. - * Remove support for various `HealthWorkoutActivityType`s which were supported by Google Fit. Some of these do not have suitable alternatives in Google Health Connect (and are not supported on iOS). The list of removed types can be found in PR [#1014](https://github.com/cph-cachet/flutter-plugins/pull/1014) -* **BREAKING** introduce a new `RecordingMethod` enum - * This can be used to filter records by automatic or manual entries when fetching data - * You can also specify the recording method to write in the metadata - * Remove `isManualEntry` from `HealthDataPoint` in favor of `recordingMethod`, of which the value is an enum `RecordingMethod` - * Remove `includeManualEntry` (previously a boolean) from some of the querying methods in favor of `recordingMethodsToFilter`. - * For complete details on relevant changes, see the description of PR [#1023](https://github.com/cph-cachet/flutter-plugins/pull/1023) -* Add support for all sleep stages across iOS and Android - * Clean up relevant documentation - * Remove undocumented sleep stages - * **BREAKING** certain sleep stages were removed/combined into other related stages see PR [#1026](https://github.com/cph-cachet/flutter-plugins/pull/1026) for the complete list of changes and a discussion of the motivation in issue [#985](https://github.com/cph-cachet/flutter-plugins/issues/985) -* Android: Add support for `OTHER` workout type -* Cleaned up workout activity types for consistency across iOS and Android, see PR [#1020](https://github.com/cph-cachet/flutter-plugins/pull/1020) for a complete list of changes -* iOS: add support for menstruation flow, PR [#1008](https://github.com/cph-cachet/flutter-plugins/pull/1008) -* Android: Add support for heart rate variability, PR [#1009](https://github.com/cph-cachet/flutter-plugins/pull/1009) -* iOS: add support for atrial fibrillation burden, PR [#1031](https://github.com/cph-cachet/flutter-plugins/pull/1031) -* Add support for UUIDs in health records for both HealthKit and Health Connect, PR [#1019](https://github.com/cph-cachet/flutter-plugins/pull/1019) -* Fix an issue when querying workouts, the native code could respond with an activity that is not supported in the Health package, causing an error - this will fallback to `HealthWorkoutActivityType.other` - PR [#1016](https://github.com/cph-cachet/flutter-plugins/pull/1016) -* Remove deprecated Android v1 embeddings, PR [#1021](https://github.com/cph-cachet/flutter-plugins/pull/1021) - -## 10.2.0 - -* Using named parameters in most methods for consistency. -* Added a `HealthPlatformType` to save which health platform the data originates from (Apple Health, Google Fit, or Google Health Connect). -* Android: Improved support for Google Health Connect - * getHealthConnectSdkStatus, PR [#941](https://github.com/cph-cachet/flutter-plugins/pull/941) - * installHealthConnect, PR [#943](https://github.com/cph-cachet/flutter-plugins/pull/943) - * workout title, PR [#938](https://github.com/cph-cachet/flutter-plugins/pull/938) -* iOS: Add support for saving blood pressure as a correlation, PR [#919](https://github.com/cph-cachet/flutter-plugins/pull/919) - -## 10.1.1 - -* Fix of error in `WorkoutSummary` JSON serialization. -* Fix of [#934](https://github.com/cph-cachet/flutter-plugins/issues/934) -* Empty value check for calories nutrition, PR [#926](https://github.com/cph-cachet/flutter-plugins/pull/926) - -## 10.0.0 - -* **BREAKING** The plugin now works as a singleton using `Health()` to access it (instead of creating an instance of `HealthFactory`). - * This entails that the plugin now need to be configured using the `configure()` method before use. - * The example app has been update to demonstrate this new singleton model. -* Support for new data types: - * body water mass, PR [#917](https://github.com/cph-cachet/flutter-plugins/pull/917) - * caffeine, PR [#924](https://github.com/cph-cachet/flutter-plugins/pull/924) - * workout summary, manual entry and new health data types, PR [#920](https://github.com/cph-cachet/flutter-plugins/pull/920) -* Fixed `SleepSessionRecord`, PR [#928](https://github.com/cph-cachet/flutter-plugins/pull/928) -* Update to API and README docs -* Upgrade to Dart 3.2 and Flutter 3. -* Added Dart linter and fixed a series of type casting issues. -* Using carp_serializable for consistent camel_case and type-safe generation of JSON serialization methods for polymorphic health data type classes. - -## 9.0.0 - -* Updated HC to comply with Android 14, PR [#834](https://github.com/cph-cachet/flutter-plugins/pull/834) and [#882](https://github.com/cph-cachet/flutter-plugins/pull/882) -* Added checks for NullPointerException, closes issue [#878](https://github.com/cph-cachet/flutter-plugins/issues/878) -* Updated intl to ^0.19.0 -* Upgrade to AGP 8, PR [#868](https://github.com/cph-cachet/flutter-plugins/pull/868) -* Added missing google fit workout types, PR [#836](https://github.com/cph-cachet/flutter-plugins/pull/836) -* Added pagination in HC, PR [#862](https://github.com/cph-cachet/flutter-plugins/pull/862) -* Fix of permission in example app + improvements to doc, PR [#875](https://github.com/cph-cachet/flutter-plugins/pull/875) - -## 8.1.0 - -* Fixed sleep stages on iOS, Issue [#803](https://github.com/cph-cachet/flutter-plugins/issues/803) -* Added Nutrition data type, includes PR [#679](https://github.com/cph-cachet/flutter-plugins/pull/679) -* Lowered minSDK, Issue [#809](https://github.com/cph-cachet/flutter-plugins/issues/809) - -## 8.0.0 - -* Fixed issue [#774](https://github.com/cph-cachet/flutter-plugins/issues/774), [#779](https://github.com/cph-cachet/flutter-plugins/issues/779) -* Merged PR [#579](https://github.com/cph-cachet/flutter-plugins/pull/579), [#717](https://github.com/cph-cachet/flutter-plugins/pull/717), [#770](https://github.com/cph-cachet/flutter-plugins/pull/770) -* Upgraded to mavenCentral, upgraded minSDK, compileSDK, targetSDK -* Updated health connect client to 1.1.0 -* Added respiratory rate and peripheral perfusion index to HealthConnect -* Minor fixes to requestAuthorization, sleep stage filtering - -## 7.0.1 - -* Updated dart doc - -## 7.0.0 - -* Merged PR #722 -* Added deep, light, REM, and out of bed sleep to iOS and Android HealthConnect - -## 6.0.0 - -* Fixed issues #[694](https://github.com/cph-cachet/flutter-plugins/issues/694), #[696](https://github.com/cph-cachet/flutter-plugins/issues/696), #[697](https://github.com/cph-cachet/flutter-plugins/issues/697), #[698](https://github.com/cph-cachet/flutter-plugins/issues/698) -* added totalSteps for HealthConnect -* added supplemental oxygen flow rate for blood oxygen saturation on Android - -## 5.0.0 - -* Added initial support for the new Health Connect API, as Google Fit is being deprecated. - * Does not yet support `revokePermissions`, `getTotalStepsInInterval`. -* Changed Intl package version dependency to `^0.17.0` to work with flutter stable version. -* Updated the example app to handle more buttons. - -## 4.6.0 - -* Added method for revoking permissions. On Android it uses `disableFit()` to remove access to Google Fit - `revokePermissions`. Documented lack of methods for iOS. - -## 4.5.0 - -* Updated android sdk, gradle -* Updated `enumToString` to native `.name` -* Update and fixed JSON serialization of HealthDataPoints -* Removed auth request in `writeWorkoutData` to avoid bug when denying the auth. -* Merged pull requests [#653](https://github.com/cph-cachet/flutter-plugins/pull/653), [#652](https://github.com/cph-cachet/flutter-plugins/pull/652), [#639](https://github.com/cph-cachet/flutter-plugins/pull/639), [#644](https://github.com/cph-cachet/flutter-plugins/pull/644), [#668](https://github.com/cph-cachet/flutter-plugins/pull/668) -* Further developed [#644](https://github.com/cph-cachet/flutter-plugins/pull/644) on android to accommodate having the `writeBloodPressure` api. -* Small bug fixes - -## 4.4.0 - -* Merged pull request #[566](https://github.com/cph-cachet/flutter-plugins/pull/566), [#578](https://github.com/cph-cachet/flutter-plugins/pull/578), [#596](https://github.com/cph-cachet/flutter-plugins/pull/596), [#623](https://github.com/cph-cachet/flutter-plugins/pull/623), [#632](https://github.com/cph-cachet/flutter-plugins/pull/632) -* ECG added as part of [#566](https://github.com/cph-cachet/flutter-plugins/pull/566) -* Small fixes - -## 4.3.0 - -* upgrade to `device_info_plus: ^8.0.0` - -## 4.2.0 - -* upgrade to `device_info_plus: ^7.0.0` - -## 4.1.1 - -* fix of [#572](https://github.com/cph-cachet/flutter-plugins/issues/572). - -## 4.1.0 - -* update of `device_info_plus: ^4.0.0` -* upgraded to Dart 2.17 and Flutter 3.0 - -## 4.0.0 - -* Large refactor of the `HealthDataPoint` value into generic `HealthValue` and added `NumericHealthValue`, `AudiogramHealthValue` and `WorkoutHealthValue` -* Added support for Audiograms with `writeAudiogram` and in `getHealthDataFromTypes` -* Added support for Workouts with `writeWorkout` and in `getHealthDataFromTypes` -* Added all `HealthWorkoutActivityType`s -* Added more `HealthDataUnit` types -* Fix of [#432](https://github.com/cph-cachet/flutter-plugins/issues/532) -* updated documentation in code -* updated documentation in README.md -* updated example app -* cleaned up code -* removed `requestPermissions` as it was essentially a duplicate of `requestAuthorization` - -## 3.4.4 - -* Fix of [#500](https://github.com/cph-cachet/flutter-plugins/issues/500). -* Added Headache-types to HealthDataTypes on iOS - -## 3.4.3 - -* fix of [#401](https://github.com/cph-cachet/flutter-plugins/issues/401). - -## 3.4.2 - -* Resolved concurrent issues with native threads [PR#483](https://github.com/cph-cachet/flutter-plugins/pull/483). -* HealthKit CategorySample [PR#485](https://github.com/cph-cachet/flutter-plugins/pull/485). -* update of API documentation. - -## 3.4.0 - -* Add sleep in bed to android [PR#457](https://github.com/cph-cachet/flutter-plugins/pull/457). -* Add the android.permission.ACTIVITY_RECOGNITION setup to the README [PR#458](https://github.com/cph-cachet/flutter-plugins/pull/458). -* Fixed (regression) issues with metric and permissions [PR#462](https://github.com/cph-cachet/flutter-plugins/pull/462). -* Get total steps [PR#471](https://github.com/cph-cachet/flutter-plugins/pull/471). -* update of example app to reflect new features. -* update of API documentation. - -## 3.3.1 - -* DISTANCE_DELTA is for Android, not iOS [PR#428](https://github.com/cph-cachet/flutter-plugins/pull/428). -* added missing READ_ACCESS [PR#454](https://github.com/cph-cachet/flutter-plugins/pull/454). - -## 3.3.0 - -* Write support on Google Fit and HealthKit [PR#430](https://github.com/cph-cachet/flutter-plugins/pull/430). - -## 3.2.1 - -* Updated `device_info_plus` version dependency - -## 3.2.0 - -* added simple `HKWorkout` and `ExerciseTime` support [PR#421](https://github.com/cph-cachet/flutter-plugins/pull/421). - -## 3.1.1+1 - -* added functions to request authorization [PR#394](https://github.com/cph-cachet/flutter-plugins/pull/394) - -## 3.1.0 - -* added sleep data to Android + fix of permissions and initialization [PR#372](https://github.com/cph-cachet/flutter-plugins/pull/372) -* testability of HealthDataPoint [PR#388](https://github.com/cph-cachet/flutter-plugins/pull/388). -* update to using the `device_info_plus` plugin - -## 3.0.6 - -* Added two new fields to the `HealthDataPoint` - `SourceId` and `SourceName` and populate when data is read. This allows data points to be disambiguous and in some cases allows us to get more accurate data. For example the number of steps can be reported from Apple Health and Watch and without source data they are aggregated into just "steps" producing an inaccurate result [PR#281](https://github.com/cph-cachet/flutter-plugins/pull/281). - -## 3.0.5 - -* Null safety in Dart has been implemented -* The plugin supports the Android v2 embedding - -## 3.0.4 - -* Upgrade to `device_info` version 2.0.0 - -## 3.0.3 - -* Merged various PRs, mostly smaller fixes - -## 3.0.2 - -* Upgrade to `device_info` version 1.0.0 - -## 3.0.1+1 - -* Bugfix regarding BMI from - -## 3.0.0 - -* Changed the flow for requesting access and reading data - * Access must be requested manually before reading - * This simplifies the data flow and makes it easier to reason about when debugging -* Data read access is no longer checked for each individual type, but rather on the set of types specified. - -## 2.0.9 - -* Now handles the case when asking for BMI on Android when no height data has been collected. - -## 2.0.8 - -* Fixed a merge issue which had deleted the data types added in 2.0.4. - -## 2.0.7 - -* Fixed a Google sign-in issue, and a type issue on Android () - -## 2.0.6 - -* Fixed a Google sign-in issue. () - -## 2.0.5 - -* Now uses 'device_info' rather than 'device_id' for getting device information - -## 2.0.4+1 - -* Static analysis, formatting etc. - -## 2.0.4 - -* Added Sleep data, Water, and Mindfulness. - -## 2.0.3 - -* The method `requestAuthorization` is now public again. - -## 2.0.2 - -* Updated the API to take a list of types rather than a single type, when requesting health data. - -## 2.0.1+1 - -* Removed the need for try-catch on the programmer's end - -## 2.0.1 - -* Removed UUID and instead introduced a comparison operator - -## 2.0.0 - -* Changed the API substantially to allow for granular Data Type permissions - -## 1.1.6 - -Added the following Health Types as per PR #147 - -* DISTANCE_WALKING_RUNNING -* FLIGHTS_CLIMBED -* MOVE_MINUTES -* DISTANCE_DELTA - -## 1.1.5 - -* Fixed an issue with google authorization -* See - -## 1.1.4 - -* Corrected table of units - -## 1.1.3 - -* Updated table with units - -## 1.1.2 - -* Now supports the data type `HEART_RATE_VARIABILITY_SDNN` on iOS - -## 1.1.1 - -* Fixed issue #88 () - -## 1.1.0 - -* Introduced UUID to the HealthDataPoint class -* Re-did the example application - -## 1.0.6 - -* Fixed a null-check warning in the obj-c code (issue #87) - -## 1.0.5 - -* Updated gradle-wrapper distribution url `gradle-5.4.1-all.zip` -* Updated docs - -## 1.0.2 - -* Updated documentation for Android and Google Fit. - -## 1.0.1 - -* Streamlined DataType units in Flutter. diff --git a/packages/health/LICENSE b/packages/health/LICENSE deleted file mode 100644 index 0edc55d82..000000000 --- a/packages/health/LICENSE +++ /dev/null @@ -1,9 +0,0 @@ -MIT License. - -Copyright 2020 the Technical University of Denmark (DTU). - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ”Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED ”AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/health/README.md b/packages/health/README.md deleted file mode 100644 index f090b744e..000000000 --- a/packages/health/README.md +++ /dev/null @@ -1,550 +0,0 @@ -# Health - -Enables reading and writing health data from/to [Apple Health](https://www.apple.com/health/) and [Google Health Connect](https://health.google/health-connect-android/). - -> **NOTE:** Google has deprecated the Google Fit API. According to the [documentation](https://developers.google.com/fit/android), as of **May 1st 2024** developers cannot sign up for using the API. As such, this package has removed support for Google Fit as of version 11.0.0 and users are urged to upgrade as soon as possible. - -The plugin supports: - -- handling permissions to access health data using the `hasPermissions`, `requestAuthorization`, `revokePermissions` methods. -- reading health data using the `getHealthDataFromTypes` method. -- writing health data using the `writeHealthData` method. -- writing workouts using the `writeWorkout` method. -- writing meals on iOS (Apple Health) & Android using the `writeMeal` method. -- writing audiograms on iOS using the `writeAudiogram` method. -- writing blood pressure data using the `writeBloodPressure` method. -- accessing total step counts using the `getTotalStepsInInterval` method. -- cleaning up duplicate data points via the `removeDuplicates` method. -- removing data of a given type in a selected period of time using the `delete` method. - -Note that for Android, the target phone **needs** to have the [Health Connect](https://play.google.com/store/apps/details?id=com.google.android.apps.healthdata&hl=en) app installed (which is currently in beta) and have access to the internet. - -See the tables below for supported health and workout data types. - -## Setup - -### Apple Health (iOS) - -First, add the following 2 entries to the `Info.plist`: - -```xml -NSHealthShareUsageDescription -We will sync your data with the Apple Health app to give you better insights -NSHealthUpdateUsageDescription -We will sync your data with the Apple Health app to give you better insights -``` - -Then, open your Flutter project in Xcode by right clicking on the "ios" folder and selecting "Open in Xcode". Next, enable "HealthKit" by adding a capability inside the "Signing & Capabilities" tab of the Runner target's settings. - -### Google Health Connect (Android) - -Health Connect requires the following lines in the `AndroidManifest.xml` file (see also the example app): - -```xml - - - - - - - -``` - -In the Health Connect permissions activity there is a link to your privacy policy. You need to grant the Health Connect app access in order to link back to your privacy policy. In the example below, you should either replace `.MainActivity` with an activity that presents the privacy policy or have the Main Activity route the user to the policy. This step may be required to pass Google app review when requesting access to sensitive permissions. - -```xml - - - - - - -``` - -For each data type you want to access, the READ and WRITE permissions need to be added to the `AndroidManifest.xml` file. The list of [permissions](https://developer.android.com/health-and-fitness/guides/health-connect/plan/data-types#permissions) can be found here on the [data types](https://developer.android.com/health-and-fitness/guides/health-connect/plan/data-types) page. - -An example of asking for permission to read and write heart rate data is shown below and more examples can also be found in the example app. - -```xml - - -``` - -By default, Health Connect restricts read data to 30 days from when permission has been granted. - -You can check and request access to historical data using the `isHealthDataHistoryAuthorized` and `requestHealthDataHistoryAuthorization` methods, respectively. - -The above methods require the following permission to be declared: - -```xml - -``` - -Accessing fitness data (e.g. Steps) requires permission to access the "Activity Recognition" API. To set it add the following line to your `AndroidManifest.xml` file. - -```xml - -``` - -Additionally, for workouts, if the distance of a workout is requested then the location permissions below are needed. - -```xml - - -``` - -Because this is labeled as a `dangerous` protection level, the permission system will not grant it automatically and it requires the user's action. -You can prompt the user for it using the [permission_handler](https://pub.dev/packages/permission_handler) plugin. -Follow the plugin setup instructions and add the following line before requesting the data: - -```dart -await Permission.activityRecognition.request(); -await Permission.location.request(); -``` - -Finally, an `intent-filter` needs to be added to the `.MainActivity` activity. - -```xml - - - - - -``` - -There's a `debug`, `main` and `profile` version which are chosen depending on how you start your app. In general, it's sufficient to add permission only to the `main` version. - -#### Android 14 - -This plugin uses the new `registerForActivityResult` when requesting permissions from Health Connect. -In order for that to work, the Main app's activity should extend `FlutterFragmentActivity` instead of `FlutterActivity`. -This adjustment allows casting from `Activity` to `ComponentActivity` for accessing `registerForActivityResult`. - -In your MainActivity.kt file, update the `MainActivity` class so that it extends `FlutterFragmentActivity` instead of the default `FlutterActivity`: - -```kotlin -... -import io.flutter.embedding.android.FlutterFragmentActivity -... - -class MainActivity: FlutterFragmentActivity() { -... -} -``` - -#### Android X - -Replace the content of the `android/gradle.properties` file with the following lines: - -```bash -org.gradle.jvmargs=-Xmx1536M -android.enableJetifier=true -android.useAndroidX=true -``` - -## Usage - -See the example app for detailed examples of how to use the Health API. - -A instance of the Health plugin is create using the `Health()` constructor and is subsequently configured calling the `configure` method. Once configured, the plugin can be used for handling permissions and getting and adding data to Apple Health or Google Health Connect. -Below is a simplified flow of how to use the plugin. - -```dart - - // Global Health instance - final health = Health(); - - // configure the health plugin before use. - await health.configure(); - - - // define the types to get - var types = [ - HealthDataType.STEPS, - HealthDataType.BLOOD_GLUCOSE, - ]; - - // requesting access to the data types before reading them - bool requested = await health.requestAuthorization(types); - - var now = DateTime.now(); - - // fetch health data from the last 24 hours - List healthData = await health.getHealthDataFromTypes( - now.subtract(Duration(days: 1)), now, types); - - // request permissions to write steps and blood glucose - types = [HealthDataType.STEPS, HealthDataType.BLOOD_GLUCOSE]; - var permissions = [ - HealthDataAccess.READ_WRITE, - HealthDataAccess.READ_WRITE - ]; - await health.requestAuthorization(types, permissions: permissions); - - // write steps and blood glucose - bool success = await health.writeHealthData(10, HealthDataType.STEPS, now, now); - success = await health.writeHealthData(3.1, HealthDataType.BLOOD_GLUCOSE, now, now); - - // you can also specify the recording method to store in the metadata (default is RecordingMethod.automatic) - // on iOS only `RecordingMethod.automatic` and `RecordingMethod.manual` are supported - // Android additionally supports `RecordingMethod.active` and `RecordingMethod.unknown` - success &= await health.writeHealthData(10, HealthDataType.STEPS, now, now, recordingMethod: RecordingMethod.manual); - - // get the number of steps for today - var midnight = DateTime(now.year, now.month, now.day); - int? steps = await health.getTotalStepsInInterval(midnight, now); -``` - -### Health Data - -A [`HealthDataPoint`](https://pub.dev/documentation/health/latest/health/HealthDataPoint-class.html) object contains the following data fields: - -```dart -String uuid; -HealthValue value; -HealthDataType type; -HealthDataUnit unit; -DateTime dateFrom; -DateTime dateTo; -HealthPlatformType sourcePlatform; -String sourceDeviceId; -String sourceId; -String sourceName; -RecordingMethod recordingMethod; -WorkoutSummary? workoutSummary; -``` - -where a [`HealthValue`](https://pub.dev/documentation/health/latest/health/HealthValue-class.html) can be any type of `AudiogramHealthValue`, `ElectrocardiogramHealthValue`, `ElectrocardiogramVoltageValue`, `NumericHealthValue`, `NutritionHealthValue`, or `WorkoutHealthValue`. - -A `HealthDataPoint` object can be serialized to and from JSON using the `toJson()` and `fromJson()` methods. JSON serialization is using camel_case notation. Null values are not serialized. For example; - -```json -{ - "value": { - "__type": "NumericHealthValue", - "numeric_value": 141.0 - }, - "type": "STEPS", - "unit": "COUNT", - "date_from": "2024-04-03T10:06:57.736", - "date_to": "2024-04-03T10:12:51.724", - "source_platform": "appleHealth", - "source_device_id": "F74938B9-C011-4DE4-AA5E-CF41B60B96E7", - "source_id": "com.apple.health.81AE7156-EC05-47E3-AC93-2D6F65C717DF", - "source_name": "iPhone12.bardram.net", - "recording_method": 3 - "value": { - "__type": "NumericHealthValue", - "numeric_value": 141.0 - }, - "type": "STEPS", - "unit": "COUNT", - "date_from": "2024-04-03T10:06:57.736", - "date_to": "2024-04-03T10:12:51.724", - "source_platform": "appleHealth", - "source_device_id": "F74938B9-C011-4DE4-AA5E-CF41B60B96E7", - "source_id": "com.apple.health.81AE7156-EC05-47E3-AC93-2D6F65C717DF", - "source_name": "iPhone12.bardram.net", - "recording_method": 2 -} -``` - -### Fetch health data - -See the example app for a showcasing of how it's done. - -**Note** On iOS the device must be unlocked before health data can be requested. Otherwise an error will be thrown: - -```bash -flutter: Health Plugin Error: -flutter: PlatformException(FlutterHealth, Results are null, Optional(Error Domain=com.apple.healthkit Code=6 "Protected health data is inaccessible" UserInfo={NSLocalizedDescription=Protected health data is inaccessible})) -``` - -### Fetch single health data by UUID - -In order to retrieve a single record, it is required to provide `String uuid` and `HealthDataType type`. - -Please see example below: -```dart -HealthDataPoint? healthPoint = await health.getHealthDataByUUID( - uuid: 'random-uuid-string', - type: HealthDataType.STEPS, -); -``` -``` -I/FLUTTER_HEALTH( 9161): Success: {uuid=random-uuid-string, value=12, date_from=1742259061009, date_to=1742259092888, source_id=, source_name=com.google.android.apps.fitness, recording_method=0} -``` -> Assuming that the `uuid` and `type` are coming from your database. - -### Filtering by recording method - -Google Health Connect and Apple HealthKit both provide ways to distinguish samples collected "automatically" and manually entered data by the user. - -- Android provides an enum with 4 variations: -- iOS has a boolean value: - -As such, when fetching data you have the option to filter the fetched data by recording method as such: - -```dart -List healthData = await health.getHealthDataFromTypes( - types: types, - startTime: yesterday, - endTime: now, - recordingMethodsToFilter: [RecordingMethod.manual, RecordingMethod.unknown], -); -``` - -**Note that for this to work, the information needs to have been provided when writing the data to Health Connect or Apple Health**. For example, steps added manually through the Apple Health App will set `HKWasUserEntered` to true (corresponding to `RecordingMethod.manual`), however it seems that adding steps manually to Google Fit does not write the data with the `RecordingMethod.manual` in the metadata, instead it shows up as `RecordingMethod.unknown`. This is an open issue, and as such filtering manual entries when querying step count on Android with `getTotalStepsInInterval(includeManualEntries: false)` does not necessarily filter out manual steps. - -**NOTE**: On iOS, you can only filter by `RecordingMethod.automatic` and `RecordingMethod.manual` as it is stored `HKMetadataKeyWasUserEntered` is a boolean value in the metadata. - -### Filtering out duplicates - -If the same data is requested multiple times and saved in the same array duplicates will occur. - -A single data point can be compared to each other with the == operator, i.e. - -```dart -HealthDataPoint p1 = ...; -HealthDataPoint p2 = ...; -bool same = p1 == p2; -``` - -If you have a list of data points, duplicates can be removed with: - -```dart -List points = ...; -points = health.removeDuplicates(points); -``` - -### Android: Reading Health Data in Background -Currently health connect allows apps to read health data in the background. In order to achieve this add the following permission to your `AndroidManifest.XML`: -```XML - - -``` -Furthermore, the plugin now exposes three new functions to help you check and request access to read data in the background: -1. `isHealthDataInBackgroundAvailable()`: Checks if the Health Data in Background feature is available -2. `isHealthDataInBackgroundAuthorized()`: Checks the current status of the Health Data in Background permission -3. `requestHealthDataInBackgroundAuthorization()`: Requests the Health Data in Background permission. - -### Fetch single health data by UUID - -In order to retrieve a single record, it is required to provide `String uuid` and `HealthDataType type`. - -Please see example below: -```dart -HealthDataPoint? healthPoint = await health.getHealthDataByUUID( - uuid: 'E9F2EEAD-8FC5-4CE5-9FF5-7C4E535FB8B8', - type: HealthDataType.WORKOUT, -); -``` -``` -data by UUID: HealthDataPoint - - uuid: E9F2EEAD-8FC5-4CE5-9FF5-7C4E535FB8B8, - value: WorkoutHealthValue - workoutActivityType: RUNNING, - totalEnergyBurned: null, - totalEnergyBurnedUnit: KILOCALORIE, - totalDistance: 2400, - totalDistanceUnit: METER - totalSteps: null, - totalStepsUnit: null, - unit: NO_UNIT, - dateFrom: 2025-05-02 07:31:00.000, - dateTo: 2025-05-02 08:25:00.000, - dataType: WORKOUT, - platform: HealthPlatformType.appleHealth, - deviceId: unknown, - sourceId: com.apple.Health, - sourceName: Health - recordingMethod: RecordingMethod.manual - workoutSummary: WorkoutSummary - workoutType: runningtotalDistance: 2400, totalEnergyBurned: 0, totalSteps: 0 - metadata: null - deviceModel: null -``` -> Assuming that the `uuid` and `type` are coming from your database. - - -## Data Types - -The plugin supports the following [`HealthDataType`](https://pub.dev/documentation/health/latest/health/HealthDataType.html). - -| **Data Type** | **Unit** | **Apple Health** | **Google Health Connect** | **Comments** | -| ---------------------------- | ----------------------- | ---------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | -| ACTIVE_ENERGY_BURNED | CALORIES | yes | yes | | -| ATRIAL_FIBRILLATION_BURDEN | PERCENTAGE | yes | | | -| BASAL_ENERGY_BURNED | CALORIES | yes | yes | | -| BLOOD_GLUCOSE | MILLIGRAM_PER_DECILITER | yes | yes | | -| BLOOD_OXYGEN | PERCENTAGE | yes | yes | | -| BLOOD_PRESSURE_DIASTOLIC | MILLIMETER_OF_MERCURY | yes | yes | | -| BLOOD_PRESSURE_SYSTOLIC | MILLIMETER_OF_MERCURY | yes | yes | | -| BODY_FAT_PERCENTAGE | PERCENTAGE | yes | yes | | -| BODY_MASS_INDEX | NO_UNIT | yes | yes | | -| BODY_TEMPERATURE | DEGREE_CELSIUS | yes | yes | | -| BODY_WATER_MASS | KILOGRAMS | | yes | | -| ELECTRODERMAL_ACTIVITY | SIEMENS | yes | | | -| HEART_RATE | BEATS_PER_MINUTE | yes | yes | | -| HEIGHT | METERS | yes | yes | | -| RESTING_HEART_RATE | BEATS_PER_MINUTE | yes | yes | | -| RESPIRATORY_RATE | RESPIRATIONS_PER_MINUTE | yes | yes | | -| PERIPHERAL_PERFUSION_INDEX | PERCENTAGE | yes | | | -| STEPS | COUNT | yes | yes | | -| WAIST_CIRCUMFERENCE | METERS | yes | | | -| WALKING_HEART_RATE | BEATS_PER_MINUTE | yes | | | -| WEIGHT | KILOGRAMS | yes | yes | | -| DISTANCE_WALKING_RUNNING | METERS | yes | | | -| FLIGHTS_CLIMBED | COUNT | yes | yes | | -| DISTANCE_DELTA | METERS | | yes | | -| MINDFULNESS | MINUTES | yes | | | -| SLEEP_ASLEEP | MINUTES | yes | yes | on iOS, this refers to asleepUnspecified, and on Android this refers to STAGE_TYPE_SLEEPING (asleep but specific stage is unknown) | -| SLEEP_AWAKE | MINUTES | yes | yes | | -| SLEEP_AWAKE_IN_BED | MINUTES | | yes | | -| SLEEP_DEEP | MINUTES | yes | yes | | -| SLEEP_IN_BED | MINUTES | yes | | | -| SLEEP_LIGHT | MINUTES | yes | yes | on iOS, this refers to asleepCore | -| SLEEP_OUT_OF_BED | MINUTES | | yes | | -| SLEEP_REM | MINUTES | yes | yes | | -| SLEEP_UNKNOWN | MINUTES | | yes | | -| SLEEP_SESSION | MINUTES | | yes | | -| WATER | LITER | yes | yes | | -| EXERCISE_TIME | MINUTES | yes | | | -| WORKOUT | NO_UNIT | yes | yes | See table below | -| HIGH_HEART_RATE_EVENT | NO_UNIT | yes | | Requires Apple Watch to write the data | -| LOW_HEART_RATE_EVENT | NO_UNIT | yes | | Requires Apple Watch to write the data | -| IRREGULAR_HEART_RATE_EVENT | NO_UNIT | yes | | Requires Apple Watch to write the data | -| HEART_RATE_VARIABILITY_RMSSD | MILLISECONDS | | yes | | -| HEART_RATE_VARIABILITY_SDNN | MILLISECONDS | yes | | Requires Apple Watch to write the data | -| HEADACHE_NOT_PRESENT | MINUTES | yes | | | -| HEADACHE_MILD | MINUTES | yes | | | -| HEADACHE_MODERATE | MINUTES | yes | | | -| HEADACHE_SEVERE | MINUTES | yes | | | -| HEADACHE_UNSPECIFIED | MINUTES | yes | | | -| AUDIOGRAM | DECIBEL_HEARING_LEVEL | yes | | | -| ELECTROCARDIOGRAM | VOLT | yes | | Requires Apple Watch to write the data | -| NUTRITION | NO_UNIT | yes | yes | | -| INSULIN_DELIVERY | INTERNATIONAL_UNIT | yes | | | -| MENSTRUATION_FLOW | NO_UNIT | yes | yes | | -| WATER_TEMPERATURE | DEGREE_CELSIUS | yes | | Related to/Requires Apple Watch Ultra's Underwater Diving Workout | -| UNDERWATER_DEPTH | METER | yes | | Related to/Requires Apple Watch Ultra's Underwater Diving Workout | -| UV_INDEX | COUNT | yes | | | -| LEAN_BODY_MASS | KILOGRAMS | yes | yes | | -| WALKING_SPEED | METER_PER_SECOND | yes | (yes) | On Android this will be recorded as `SPEED` with similar unit | -| APPLE_MOVE_TIME | SECOND | yes | | READ Only | -| APPLE_STAND_HOUR | HOUR | yes | | READ Only | -| APPLE_MOVE_TIME | SECOND | yes | | READ Only | - -## Workout Types - -The plugin supports the following [`HealthWorkoutActivityType`](https://pub.dev/documentation/health/latest/health/HealthWorkoutActivityType.html). - -| **Workout Type** | **Apple Health** | **Google Health Connect** | **Comments** | -| -------------------------------- | ---------------- | ------------------------- | ----------------------------------------------------------------------------------------------- | -| AMERICAN_FOOTBALL | yes | yes | | -| ARCHERY | yes | | | -| AUSTRALIAN_FOOTBALL | yes | yes | | -| BADMINTON | yes | yes | | -| BARRE | yes | | | -| BASEBALL | yes | yes | | -| BASKETBALL | yes | yes | | -| BIKING | yes | yes | on iOS this is CYCLING, but name changed here to fit with Android | -| BOWLING | yes | | | -| BOXING | yes | yes | | -| CALISTHENICS | | yes | | -| CARDIO_DANCE | yes | (yes) | on Android this will be stored as DANCING | -| CLIMBING | yes | | | -| COOLDOWN | yes | | | -| CORE_TRAINING | yes | | | -| CRICKET | yes | yes | | -| CROSS_COUNTRY_SKIING | yes | (yes) | on Android this will be stored as SKIING | -| CROSS_TRAINING | yes | | | -| CURLING | yes | | | -| DANCING | yes | yes | on iOS this is DANCE, but name changed here to fit with Android | -| DISC_SPORTS | yes | | | -| DOWNHILL_SKIING | yes | (yes) | on Android this will be stored as SKIING | -| ELLIPTICAL | yes | yes | | -| EQUESTRIAN_SPORTS | yes | | | -| FENCING | yes | yes | | -| FISHING | yes | | | -| FITNESS_GAMING | yes | | | -| FLEXIBILITY | yes | | | -| FRISBEE_DISC | | yes | | -| FUNCTIONAL_STRENGTH_TRAINING | yes | (yes) | on Android this will be stored as STRENGTH_TRAINING | -| GOLF | yes | yes | | -| GUIDED_BREATHING | | yes | | -| GYMNASTICS | yes | yes | | -| HAND_CYCLING | yes | | | -| HANDBALL | yes | yes | | -| HIGH_INTENSITY_INTERVAL_TRAINING | yes | yes | | -| HIKING | yes | yes | | -| HOCKEY | yes | | | -| HUNTING | yes | | | -| JUMP_ROPE | yes | | | -| KICKBOXING | yes | | | -| LACROSSE | yes | | | -| MARTIAL_ARTS | yes | yes | | -| MIND_AND_BODY | yes | | | -| MIXED_CARDIO | yes | | | -| PADDLE_SPORTS | yes | | | -| PARAGLIDING | | yes | | -| PICKLEBALL | yes | | | -| PILATES | yes | yes | | -| PLAY | yes | | | -| PREPARATION_AND_RECOVERY | yes | | | -| RACQUETBALL | yes | yes | | -| ROCK_CLIMBING | (yes) | yes | on iOS this will be stored as CLIMBING | -| ROWING | yes | yes | | -| RUGBY | yes | yes | | -| RUNNING | yes | yes | | -| RUNNING_TREADMILL | (yes) | yes | on iOS this will be stored as RUNNING | -| SAILING | yes | yes | | -| SCUBA_DIVING | | yes | | -| SKATING | yes | yes | On iOS this will be stored as SKATING_SPORTS | -| SKIING | (yes) | yes | on iOS you have to choose between CROSS_COUNTRY_SKIING and DOWNHILL_SKIING | -| SNOW_SPORTS | yes | | | -| SNOWBOARDING | yes | yes | | -| SOCCER | yes | | | -| SOCIAL_DANCE | yes | (yes) | on Android this will be stored as DANCING | -| SOFTBALL | yes | yes | | -| SQUASH | yes | yes | | -| STAIR_CLIMBING | yes | yes | | -| STAIR_CLIMBING_MACHINE | | yes | | -| STAIRS | yes | | | -| STEP_TRAINING | yes | | | -| STRENGTH_TRAINING | (yes) | yes | on iOS you have to choose between FUNCTIONAL_STRENGTH_TRAINING or TRADITIONAL_STRENGTH_TRAINING | -| SURFING | yes | yes | on iOS this is SURFING_SPORTS, but name changed here to fit with Android | -| SWIMMING | yes | (yes) | on Android you have to choose between SWIMMING_OPEN_WATER and SWIMMING_POOL | -| SWIMMING_OPEN_WATER | (yes) | yes | on iOS this will be stored as SWIMMING | -| SWIMMING_POOL | (yes) | yes | on iOS this will be stored as SWIMMING | -| TABLE_TENNIS | yes | yes | | -| TAI_CHI | yes | | | -| TENNIS | yes | yes | | -| TRACK_AND_FIELD | yes | | | -| TRADITIONAL_STRENGTH_TRAINING | yes | (yes) | on Android this will be stored as STRENGTH_TRAINING | -| UNDERWATER_DIVING | yes | | | -| VOLLEYBALL | yes | yes | | -| WALKING | yes | yes | | -| WATER_FITNESS | yes | | | -| WATER_POLO | yes | yes | | -| WATER_SPORTS | yes | | | -| WEIGHTLIFTING | | yes | | -| WHEELCHAIR | (yes) | yes | on iOS you have to choose between WHEELCHAIR_RUN_PACE or WHEELCHAIR_WALK_PACE | -| WHEELCHAIR_RUN_PACE | yes | (yes) | on Android this will be stored as WHEELCHAIR | -| WHEELCHAIR_WALK_PACE | yes | (yes) | on Android this will be stored as WHEELCHAIR | -| WRESTLING | yes | | | -| YOGA | yes | yes | | -| OTHER | yes | yes | | - -## License - -This software is copyright (c) the [Technical University of Denmark (DTU)](https://www.dtu.dk) and is part of the [Copenhagen Research Platform](https://carp.cachet.dk/). -This software is available 'as-is' under a [MIT license](LICENSE). diff --git a/packages/health/analysis_options.yaml b/packages/health/analysis_options.yaml deleted file mode 100644 index 9565468ca..000000000 --- a/packages/health/analysis_options.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options - -include: package:flutter_lints/flutter.yaml - -analyzer: - exclude: [build/**] - language: - strict-casts: true - strict-inference: true - strict-raw-types: true - -linter: - rules: - cancel_subscriptions: true - constant_identifier_names: false - depend_on_referenced_packages: true - avoid_print: true - use_string_in_part_of_directives: true diff --git a/packages/health/android/build.gradle b/packages/health/android/build.gradle deleted file mode 100644 index b4194b9dc..000000000 --- a/packages/health/android/build.gradle +++ /dev/null @@ -1,61 +0,0 @@ -group 'cachet.plugins.health' -version '1.2' - -buildscript { - ext.kotlin_version = '1.9.22' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:8.13.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - -rootProject.allprojects { - repositories { - google() - mavenCentral() - } -} - -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' - -android { - compileSdk 36 - - compileOptions { - sourceCompatibility JavaVersion.VERSION_11 - targetCompatibility JavaVersion.VERSION_11 - } - - kotlinOptions { - jvmTarget = '11' - } - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - } - defaultConfig { - minSdkVersion 26 - targetSdkVersion 36 - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - lintOptions { - disable 'InvalidPackage' - } - namespace "cachet.plugins.health" -} - -dependencies { - def composeBom = platform('androidx.compose:compose-bom:2025.09.00') - implementation(composeBom) - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.2.10" - - implementation("androidx.health.connect:connect-client:1.1.0-rc03") - implementation "androidx.fragment:fragment-ktx:1.8.9" - -} diff --git a/packages/health/android/gradle.properties b/packages/health/android/gradle.properties deleted file mode 100644 index 678cd6244..000000000 --- a/packages/health/android/gradle.properties +++ /dev/null @@ -1,3 +0,0 @@ -org.gradle.jvmargs=-Xmx1536M -android.enableJetifier=true -android.useAndroidX=true \ No newline at end of file diff --git a/packages/health/android/gradle/wrapper/gradle-wrapper.properties b/packages/health/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index cb3cdef9c..000000000 --- a/packages/health/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Tue Sep 16 09:25:00 CEST 2025 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/packages/health/android/settings.gradle b/packages/health/android/settings.gradle deleted file mode 100644 index aa2aa6bd5..000000000 --- a/packages/health/android/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = 'health' diff --git a/packages/health/android/src/main/AndroidManifest.xml b/packages/health/android/src/main/AndroidManifest.xml deleted file mode 100644 index a2f47b605..000000000 --- a/packages/health/android/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthConstants.kt b/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthConstants.kt deleted file mode 100644 index 697f33ae5..000000000 --- a/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthConstants.kt +++ /dev/null @@ -1,241 +0,0 @@ -package cachet.plugins.health - -import kotlin.reflect.KClass -import androidx.health.connect.client.records.* -import androidx.health.connect.client.records.MealType - -/** - * Contains all data type mappings, health record classifications, and type conversions - * used throughout the Health Connect integration. - */ -object HealthConstants { - // Channel name - const val CHANNEL_NAME = "flutter_health" - - // Data type constants - const val ACTIVE_ENERGY_BURNED = "ACTIVE_ENERGY_BURNED" - const val AGGREGATE_STEP_COUNT = "AGGREGATE_STEP_COUNT" - const val BASAL_ENERGY_BURNED = "BASAL_ENERGY_BURNED" - const val BLOOD_GLUCOSE = "BLOOD_GLUCOSE" - const val BLOOD_OXYGEN = "BLOOD_OXYGEN" - const val BLOOD_PRESSURE_DIASTOLIC = "BLOOD_PRESSURE_DIASTOLIC" - const val BLOOD_PRESSURE_SYSTOLIC = "BLOOD_PRESSURE_SYSTOLIC" - const val BODY_FAT_PERCENTAGE = "BODY_FAT_PERCENTAGE" - const val LEAN_BODY_MASS = "LEAN_BODY_MASS" - const val BODY_TEMPERATURE = "BODY_TEMPERATURE" - const val BODY_WATER_MASS = "BODY_WATER_MASS" - const val DISTANCE_DELTA = "DISTANCE_DELTA" - const val FLIGHTS_CLIMBED = "FLIGHTS_CLIMBED" - const val HEART_RATE = "HEART_RATE" - const val HEART_RATE_VARIABILITY_RMSSD = "HEART_RATE_VARIABILITY_RMSSD" - const val HEIGHT = "HEIGHT" - const val MENSTRUATION_FLOW = "MENSTRUATION_FLOW" - const val RESPIRATORY_RATE = "RESPIRATORY_RATE" - const val RESTING_HEART_RATE = "RESTING_HEART_RATE" - const val STEPS = "STEPS" - const val WATER = "WATER" - const val WEIGHT = "WEIGHT" - const val TOTAL_CALORIES_BURNED = "TOTAL_CALORIES_BURNED" - const val SPEED = "SPEED" - - // Meal types - const val BREAKFAST = "BREAKFAST" - const val DINNER = "DINNER" - const val LUNCH = "LUNCH" - const val MEAL_UNKNOWN = "UNKNOWN" - const val NUTRITION = "NUTRITION" - const val SNACK = "SNACK" - - // Sleep types - const val SLEEP_ASLEEP = "SLEEP_ASLEEP" - const val SLEEP_AWAKE = "SLEEP_AWAKE" - const val SLEEP_AWAKE_IN_BED = "SLEEP_AWAKE_IN_BED" - const val SLEEP_DEEP = "SLEEP_DEEP" - const val SLEEP_IN_BED = "SLEEP_IN_BED" - const val SLEEP_LIGHT = "SLEEP_LIGHT" - const val SLEEP_OUT_OF_BED = "SLEEP_OUT_OF_BED" - const val SLEEP_REM = "SLEEP_REM" - const val SLEEP_SESSION = "SLEEP_SESSION" - const val SLEEP_UNKNOWN = "SLEEP_UNKNOWN" - - // Activity type - const val WORKOUT = "WORKOUT" - - /** - * Maps Flutter health data type strings to their corresponding Health Connect Record classes. - * This mapping enables dynamic type resolution for reading and writing health data. - * - * @return Map> Mapping of type strings to Health Connect record classes - */ - val mapToType: Map> = hashMapOf( - BODY_FAT_PERCENTAGE to BodyFatRecord::class, - LEAN_BODY_MASS to LeanBodyMassRecord::class, - HEIGHT to HeightRecord::class, - WEIGHT to WeightRecord::class, - STEPS to StepsRecord::class, - AGGREGATE_STEP_COUNT to StepsRecord::class, - ACTIVE_ENERGY_BURNED to ActiveCaloriesBurnedRecord::class, - HEART_RATE to HeartRateRecord::class, - BODY_TEMPERATURE to BodyTemperatureRecord::class, - BODY_WATER_MASS to BodyWaterMassRecord::class, - BLOOD_PRESSURE_SYSTOLIC to BloodPressureRecord::class, - BLOOD_PRESSURE_DIASTOLIC to BloodPressureRecord::class, - BLOOD_OXYGEN to OxygenSaturationRecord::class, - BLOOD_GLUCOSE to BloodGlucoseRecord::class, - HEART_RATE_VARIABILITY_RMSSD to HeartRateVariabilityRmssdRecord::class, - DISTANCE_DELTA to DistanceRecord::class, - WATER to HydrationRecord::class, - SLEEP_ASLEEP to SleepSessionRecord::class, - SLEEP_AWAKE to SleepSessionRecord::class, - SLEEP_AWAKE_IN_BED to SleepSessionRecord::class, - SLEEP_LIGHT to SleepSessionRecord::class, - SLEEP_DEEP to SleepSessionRecord::class, - SLEEP_REM to SleepSessionRecord::class, - SLEEP_OUT_OF_BED to SleepSessionRecord::class, - SLEEP_SESSION to SleepSessionRecord::class, - SLEEP_UNKNOWN to SleepSessionRecord::class, - WORKOUT to ExerciseSessionRecord::class, - NUTRITION to NutritionRecord::class, - RESTING_HEART_RATE to RestingHeartRateRecord::class, - BASAL_ENERGY_BURNED to BasalMetabolicRateRecord::class, - FLIGHTS_CLIMBED to FloorsClimbedRecord::class, - RESPIRATORY_RATE to RespiratoryRateRecord::class, - TOTAL_CALORIES_BURNED to TotalCaloriesBurnedRecord::class, - MENSTRUATION_FLOW to MenstruationFlowRecord::class, - SPEED to SpeedRecord::class, - ) - - /** - * Maps health data types to their corresponding aggregate metric types for batch operations. - * Used when requesting aggregated data over time periods. - * - * @return Map Mapping for aggregate data queries - */ - val mapToAggregateMetric = hashMapOf( - HEIGHT to HeightRecord.HEIGHT_AVG, - WEIGHT to WeightRecord.WEIGHT_AVG, - STEPS to StepsRecord.COUNT_TOTAL, - AGGREGATE_STEP_COUNT to StepsRecord.COUNT_TOTAL, - ACTIVE_ENERGY_BURNED to ActiveCaloriesBurnedRecord.ACTIVE_CALORIES_TOTAL, - HEART_RATE to HeartRateRecord.MEASUREMENTS_COUNT, - DISTANCE_DELTA to DistanceRecord.DISTANCE_TOTAL, - WATER to HydrationRecord.VOLUME_TOTAL, - SLEEP_ASLEEP to SleepSessionRecord.SLEEP_DURATION_TOTAL, - SLEEP_AWAKE to SleepSessionRecord.SLEEP_DURATION_TOTAL, - SLEEP_IN_BED to SleepSessionRecord.SLEEP_DURATION_TOTAL, - TOTAL_CALORIES_BURNED to TotalCaloriesBurnedRecord.ENERGY_TOTAL - ) - - /** - * Maps integer sleep stage values to their corresponding data type strings. - * Converts Health Connect sleep stage enumeration to Flutter-friendly string constants. - * - * @return Map Sleep stage integer to type string mapping - */ - val mapSleepStageToType = hashMapOf( - 0 to SLEEP_UNKNOWN, - 1 to SLEEP_AWAKE, - 2 to SLEEP_ASLEEP, - 3 to SLEEP_OUT_OF_BED, - 4 to SLEEP_LIGHT, - 5 to SLEEP_DEEP, - 6 to SLEEP_REM, - 7 to SLEEP_AWAKE_IN_BED - ) - - /** - * Maps meal type strings to Health Connect MealType enumeration values. - * Enables conversion from Flutter meal type strings to Health Connect types. - * - * @return Map Meal type string to enum mapping - */ - val mapMealTypeToType = hashMapOf( - BREAKFAST to MealType.MEAL_TYPE_BREAKFAST, - LUNCH to MealType.MEAL_TYPE_LUNCH, - DINNER to MealType.MEAL_TYPE_DINNER, - SNACK to MealType.MEAL_TYPE_SNACK, - MEAL_UNKNOWN to MealType.MEAL_TYPE_UNKNOWN, - ) - - /** - * Maps Health Connect MealType enumeration values back to Flutter string constants. - * Used for converting Health Connect meal types to Flutter-compatible format. - * - * @return Map Enum to string mapping for meal types - */ - val mapTypeToMealType = hashMapOf( - MealType.MEAL_TYPE_BREAKFAST to BREAKFAST, - MealType.MEAL_TYPE_LUNCH to LUNCH, - MealType.MEAL_TYPE_DINNER to DINNER, - MealType.MEAL_TYPE_SNACK to SNACK, - MealType.MEAL_TYPE_UNKNOWN to MEAL_UNKNOWN, - ) - - /** - * Maps workout/exercise type strings to Health Connect ExerciseSessionRecord types. - * Comprehensive mapping of all supported exercise activities for workout tracking. - * - * @return Map Workout type strings to Health Connect exercise type constants - */ - val workoutTypeMap = mapOf( - "AMERICAN_FOOTBALL" to ExerciseSessionRecord.EXERCISE_TYPE_FOOTBALL_AMERICAN, - "AUSTRALIAN_FOOTBALL" to ExerciseSessionRecord.EXERCISE_TYPE_FOOTBALL_AUSTRALIAN, - "BADMINTON" to ExerciseSessionRecord.EXERCISE_TYPE_BADMINTON, - "BASEBALL" to ExerciseSessionRecord.EXERCISE_TYPE_BASEBALL, - "BASKETBALL" to ExerciseSessionRecord.EXERCISE_TYPE_BASKETBALL, - "BIKING" to ExerciseSessionRecord.EXERCISE_TYPE_BIKING, - "BOXING" to ExerciseSessionRecord.EXERCISE_TYPE_BOXING, - "CALISTHENICS" to ExerciseSessionRecord.EXERCISE_TYPE_CALISTHENICS, - "CARDIO_DANCE" to ExerciseSessionRecord.EXERCISE_TYPE_DANCING, - "CRICKET" to ExerciseSessionRecord.EXERCISE_TYPE_CRICKET, - "CROSS_COUNTRY_SKIING" to ExerciseSessionRecord.EXERCISE_TYPE_SKIING, - "DANCING" to ExerciseSessionRecord.EXERCISE_TYPE_DANCING, - "DOWNHILL_SKIING" to ExerciseSessionRecord.EXERCISE_TYPE_SKIING, - "ELLIPTICAL" to ExerciseSessionRecord.EXERCISE_TYPE_ELLIPTICAL, - "FENCING" to ExerciseSessionRecord.EXERCISE_TYPE_FENCING, - "FRISBEE_DISC" to ExerciseSessionRecord.EXERCISE_TYPE_FRISBEE_DISC, - "GOLF" to ExerciseSessionRecord.EXERCISE_TYPE_GOLF, - "GUIDED_BREATHING" to ExerciseSessionRecord.EXERCISE_TYPE_GUIDED_BREATHING, - "GYMNASTICS" to ExerciseSessionRecord.EXERCISE_TYPE_GYMNASTICS, - "HANDBALL" to ExerciseSessionRecord.EXERCISE_TYPE_HANDBALL, - "HIGH_INTENSITY_INTERVAL_TRAINING" to ExerciseSessionRecord.EXERCISE_TYPE_HIGH_INTENSITY_INTERVAL_TRAINING, - "HIKING" to ExerciseSessionRecord.EXERCISE_TYPE_HIKING, - "ICE_SKATING" to ExerciseSessionRecord.EXERCISE_TYPE_ICE_SKATING, - "MARTIAL_ARTS" to ExerciseSessionRecord.EXERCISE_TYPE_MARTIAL_ARTS, - "PARAGLIDING" to ExerciseSessionRecord.EXERCISE_TYPE_PARAGLIDING, - "PILATES" to ExerciseSessionRecord.EXERCISE_TYPE_PILATES, - "RACQUETBALL" to ExerciseSessionRecord.EXERCISE_TYPE_RACQUETBALL, - "ROCK_CLIMBING" to ExerciseSessionRecord.EXERCISE_TYPE_ROCK_CLIMBING, - "ROWING" to ExerciseSessionRecord.EXERCISE_TYPE_ROWING, - "ROWING_MACHINE" to ExerciseSessionRecord.EXERCISE_TYPE_ROWING_MACHINE, - "RUGBY" to ExerciseSessionRecord.EXERCISE_TYPE_RUGBY, - "RUNNING_TREADMILL" to ExerciseSessionRecord.EXERCISE_TYPE_RUNNING_TREADMILL, - "RUNNING" to ExerciseSessionRecord.EXERCISE_TYPE_RUNNING, - "SAILING" to ExerciseSessionRecord.EXERCISE_TYPE_SAILING, - "SCUBA_DIVING" to ExerciseSessionRecord.EXERCISE_TYPE_SCUBA_DIVING, - "SKATING" to ExerciseSessionRecord.EXERCISE_TYPE_SKATING, - "SKIING" to ExerciseSessionRecord.EXERCISE_TYPE_SKIING, - "SNOWBOARDING" to ExerciseSessionRecord.EXERCISE_TYPE_SNOWBOARDING, - "SNOWSHOEING" to ExerciseSessionRecord.EXERCISE_TYPE_SNOWSHOEING, - "SOCIAL_DANCE" to ExerciseSessionRecord.EXERCISE_TYPE_DANCING, - "SOFTBALL" to ExerciseSessionRecord.EXERCISE_TYPE_SOFTBALL, - "SQUASH" to ExerciseSessionRecord.EXERCISE_TYPE_SQUASH, - "STAIR_CLIMBING_MACHINE" to ExerciseSessionRecord.EXERCISE_TYPE_STAIR_CLIMBING_MACHINE, - "STAIR_CLIMBING" to ExerciseSessionRecord.EXERCISE_TYPE_STAIR_CLIMBING, - "STRENGTH_TRAINING" to ExerciseSessionRecord.EXERCISE_TYPE_STRENGTH_TRAINING, - "SURFING" to ExerciseSessionRecord.EXERCISE_TYPE_SURFING, - "SWIMMING_OPEN_WATER" to ExerciseSessionRecord.EXERCISE_TYPE_SWIMMING_OPEN_WATER, - "SWIMMING_POOL" to ExerciseSessionRecord.EXERCISE_TYPE_SWIMMING_POOL, - "TABLE_TENNIS" to ExerciseSessionRecord.EXERCISE_TYPE_TABLE_TENNIS, - "TENNIS" to ExerciseSessionRecord.EXERCISE_TYPE_TENNIS, - "VOLLEYBALL" to ExerciseSessionRecord.EXERCISE_TYPE_VOLLEYBALL, - "WALKING" to ExerciseSessionRecord.EXERCISE_TYPE_WALKING, - "WATER_POLO" to ExerciseSessionRecord.EXERCISE_TYPE_WATER_POLO, - "WEIGHTLIFTING" to ExerciseSessionRecord.EXERCISE_TYPE_WEIGHTLIFTING, - "WHEELCHAIR" to ExerciseSessionRecord.EXERCISE_TYPE_WHEELCHAIR, - "WHEELCHAIR_RUN_PACE" to ExerciseSessionRecord.EXERCISE_TYPE_WHEELCHAIR, - "WHEELCHAIR_WALK_PACE" to ExerciseSessionRecord.EXERCISE_TYPE_WHEELCHAIR, - "YOGA" to ExerciseSessionRecord.EXERCISE_TYPE_YOGA, - "OTHER" to ExerciseSessionRecord.EXERCISE_TYPE_OTHER_WORKOUT, - ) -} \ No newline at end of file diff --git a/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthDataConverter.kt b/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthDataConverter.kt deleted file mode 100644 index 6d288170b..000000000 --- a/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthDataConverter.kt +++ /dev/null @@ -1,258 +0,0 @@ -package cachet.plugins.health - -import java.time.Instant -import java.time.temporal.ChronoUnit -import androidx.health.connect.client.records.* -import androidx.health.connect.client.records.metadata.Metadata - -/** - * Converts Health Connect records to Flutter-compatible data structures. - * Handles the transformation of strongly-typed Health Connect data into generic maps - * that can be serialized and passed to the Flutter layer. - */ -class HealthDataConverter { - - /** - * Converts a Health Connect record to a list of Flutter-compatible maps. - * Handles various record types including instant records, interval records, and complex types. - * - * @param record The Health Connect record to convert - * @param dataType The string identifier for the data type being converted - * @return List> List of converted records (some records may split into multiple entries) - * @throws IllegalArgumentException If the record type is not supported - */ - fun convertRecord(record: Any, dataType: String, dataUnit: String? = null): List> { - val metadata = (record as Record).metadata - - return when (record) { - // Single-value instant records - is WeightRecord -> listOf(createInstantRecord(metadata, record.time, when (dataUnit) { - "POUND" -> record.weight.inPounds - else -> record.weight.inKilograms - })) - is HeightRecord -> listOf(createInstantRecord(metadata, record.time, when (dataUnit) { - "CENTIMETER" -> (record.height.inMeters * 100) - "INCH" -> record.height.inInches - else -> record.height.inMeters - })) - is BodyFatRecord -> listOf(createInstantRecord(metadata, record.time, record.percentage.value)) - is LeanBodyMassRecord -> listOf(createInstantRecord(metadata, record.time, record.mass.inKilograms)) - is HeartRateVariabilityRmssdRecord -> listOf(createInstantRecord(metadata, record.time, record.heartRateVariabilityMillis)) - is BodyTemperatureRecord -> listOf(createInstantRecord(metadata, record.time, when (dataUnit) { - "DEGREE_FAHRENHEIT" -> record.temperature.inFahrenheit - "KELVIN" -> record.temperature.inCelsius + 273.15 - else -> record.temperature.inCelsius - })) - is BodyWaterMassRecord -> listOf(createInstantRecord(metadata, record.time, record.mass.inKilograms)) - is OxygenSaturationRecord -> listOf(createInstantRecord(metadata, record.time, record.percentage.value)) - is BloodGlucoseRecord -> listOf(createInstantRecord(metadata, record.time, when (dataUnit) { - "MILLIMOLES_PER_LITER" -> record.level.inMillimolesPerLiter - else -> record.level.inMilligramsPerDeciliter - })) - is BasalMetabolicRateRecord -> listOf(createInstantRecord(metadata, record.time, record.basalMetabolicRate.inKilocaloriesPerDay)) - is RestingHeartRateRecord -> listOf(createInstantRecord(metadata, record.time, record.beatsPerMinute)) - is RespiratoryRateRecord -> listOf(createInstantRecord(metadata, record.time, record.rate)) - - // Interval records - is StepsRecord -> listOf(createIntervalRecord(metadata, record.startTime, record.endTime, record.count)) - is ActiveCaloriesBurnedRecord -> listOf(createIntervalRecord(metadata, record.startTime, record.endTime, record.energy.inKilocalories)) - is DistanceRecord -> listOf(createIntervalRecord(metadata, record.startTime, record.endTime, record.distance.inMeters)) - is HydrationRecord -> listOf(createIntervalRecord(metadata, record.startTime, record.endTime, record.volume.inLiters)) - is TotalCaloriesBurnedRecord -> listOf(createIntervalRecord(metadata, record.startTime, record.endTime, record.energy.inKilocalories)) - is FloorsClimbedRecord -> listOf(createIntervalRecord(metadata, record.startTime, record.endTime, record.floors)) - - // Special cases - is BloodPressureRecord -> listOf( - createInstantRecord( - metadata, - record.time, - if (dataType == BLOOD_PRESSURE_DIASTOLIC) - record.diastolic.inMillimetersOfMercury - else - record.systolic.inMillimetersOfMercury - ) - ) - - is HeartRateRecord -> record.samples.map { sample -> - createInstantRecord(metadata, sample.time, sample.beatsPerMinute) - } - - is SpeedRecord -> record.samples.map { sample -> - createInstantRecord(metadata, sample.time, sample.speed.inMetersPerSecond) - } - - is SleepSessionRecord -> listOf( - createIntervalRecord( - metadata, - record.startTime, - record.endTime, - ChronoUnit.MINUTES.between(record.startTime, record.endTime) - ) - ) - - is MenstruationFlowRecord -> listOf( - createInstantRecord(metadata, record.time, record.flow) - ) - - is NutritionRecord -> listOf(createNutritionRecord(record, metadata)) - - else -> throw IllegalArgumentException("Health data type not supported") - } - } - - /** - * Creates a standardized instant record map for point-in-time health measurements. - * Used for data that represents a single moment measurement (weight, height, etc.). - * - * @param metadata Record metadata containing source and recording information - * @param time The timestamp when the measurement was taken - * @param value The measured value - * @return Map Standardized instant record structure - */ - private fun createInstantRecord( - metadata: Metadata, - time: Instant, - value: Any - ): Map = createBaseRecord(metadata).apply { - put("value", value) - put("date_from", time.toEpochMilli()) - put("date_to", time.toEpochMilli()) - } - - /** - * Creates a standardized interval record map for time-range health measurements. - * Used for data that spans a time period (steps, distance, calories burned, etc.). - * - * @param metadata Record metadata containing source and recording information - * @param startTime Beginning of the measurement period - * @param endTime End of the measurement period - * @param value The measured value over the time period - * @return Map Standardized interval record structure - */ - private fun createIntervalRecord( - metadata: Metadata, - startTime: Instant, - endTime: Instant, - value: Any - ): Map = createBaseRecord(metadata).apply { - put("value", value) - put("date_from", startTime.toEpochMilli()) - put("date_to", endTime.toEpochMilli()) - } - - /** - * Creates the base record structure with common fields shared by all health records. - * Includes metadata like UUID, source information, and recording method. - * - * @param metadata Record metadata from Health Connect - * @return MutableMap Base record structure with common fields - */ - private fun createBaseRecord(metadata: Metadata): MutableMap = mutableMapOf( - "uuid" to metadata.id, - "source_id" to "", - "source_name" to metadata.dataOrigin.packageName, - "recording_method" to metadata.recordingMethod - ) - - /** - * Creates a specialized nutrition record with comprehensive nutrient information. - * Handles the complex nutrition data structure with multiple nutrient fields, - * meal type classification, and optional food name. - * - * @param record The NutritionRecord from Health Connect - * @param metadata Record metadata - * @return Map Comprehensive nutrition record with all nutrient fields - */ - private fun createNutritionRecord( - record: NutritionRecord, - metadata: Metadata - ): Map = createIntervalRecord( - metadata, - record.startTime, - record.endTime, - 0 // Placeholder value since nutrition doesn't have a single value - ).toMutableMap().apply { - remove("value") // Remove the placeholder - - // Add all nutrition-specific fields - putAll(mapOf( - "calories" to record.energy?.inKilocalories, - "protein" to record.protein?.inGrams, - "carbs" to record.totalCarbohydrate?.inGrams, - "fat" to record.totalFat?.inGrams, - "caffeine" to record.caffeine?.inGrams, - "vitamin_a" to record.vitaminA?.inGrams, - "b1_thiamine" to record.thiamin?.inGrams, - "b2_riboflavin" to record.riboflavin?.inGrams, - "b3_niacin" to record.niacin?.inGrams, - "b5_pantothenic_acid" to record.pantothenicAcid?.inGrams, - "b6_pyridoxine" to record.vitaminB6?.inGrams, - "b7_biotin" to record.biotin?.inGrams, - "b9_folate" to record.folate?.inGrams, - "b12_cobalamin" to record.vitaminB12?.inGrams, - "vitamin_c" to record.vitaminC?.inGrams, - "vitamin_d" to record.vitaminD?.inGrams, - "vitamin_e" to record.vitaminE?.inGrams, - "vitamin_k" to record.vitaminK?.inGrams, - "calcium" to record.calcium?.inGrams, - "chloride" to record.chloride?.inGrams, - "cholesterol" to record.cholesterol?.inGrams, - "choline" to null, // Not supported by Health Connect - "chromium" to record.chromium?.inGrams, - "copper" to record.copper?.inGrams, - "fat_unsaturated" to record.unsaturatedFat?.inGrams, - "fat_monounsaturated" to record.monounsaturatedFat?.inGrams, - "fat_polyunsaturated" to record.polyunsaturatedFat?.inGrams, - "fat_saturated" to record.saturatedFat?.inGrams, - "fat_trans_monoenoic" to record.transFat?.inGrams, - "fiber" to record.dietaryFiber?.inGrams, - "iodine" to record.iodine?.inGrams, - "iron" to record.iron?.inGrams, - "magnesium" to record.magnesium?.inGrams, - "manganese" to record.manganese?.inGrams, - "molybdenum" to record.molybdenum?.inGrams, - "phosphorus" to record.phosphorus?.inGrams, - "potassium" to record.potassium?.inGrams, - "selenium" to record.selenium?.inGrams, - "sodium" to record.sodium?.inGrams, - "sugar" to record.sugar?.inGrams, - "water" to null, // Not supported by Health Connect - "zinc" to record.zinc?.inGrams, - "name" to (record.name ?: ""), - "meal_type" to (HealthConstants.mapTypeToMealType[record.mealType] ?: MEAL_UNKNOWN) - )) - } - - /** - * Converts a sleep stage to a Flutter-compatible map structure. - * Transforms individual sleep stage data including duration and stage type - * into a standardized format for Flutter consumption. - * - * @param stage The sleep stage record from Health Connect - * @param dataType The specific sleep data type being requested - * @param metadata Parent sleep session metadata - * @return List> Sleep stage data in Flutter format - */ - fun convertRecordStage( - stage: SleepSessionRecord.Stage, - dataType: String, - metadata: Metadata - ): List> { - return listOf( - mapOf( - "uuid" to metadata.id, - "stage" to stage.stage, - "value" to ChronoUnit.MINUTES.between(stage.startTime, stage.endTime), - "date_from" to stage.startTime.toEpochMilli(), - "date_to" to stage.endTime.toEpochMilli(), - "source_id" to "", - "source_name" to metadata.dataOrigin.packageName, - ) - ) - } - - companion object { - private const val BLOOD_PRESSURE_DIASTOLIC = "BLOOD_PRESSURE_DIASTOLIC" - private const val MEAL_UNKNOWN = "UNKNOWN" - } -} diff --git a/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthDataOperations.kt b/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthDataOperations.kt deleted file mode 100644 index 78561516b..000000000 --- a/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthDataOperations.kt +++ /dev/null @@ -1,332 +0,0 @@ -package cachet.plugins.health - -import android.util.Log -import androidx.health.connect.client.HealthConnectClient -import androidx.health.connect.client.HealthConnectFeatures -import androidx.health.connect.client.permission.HealthPermission -import androidx.health.connect.client.permission.HealthPermission.Companion.PERMISSION_READ_HEALTH_DATA_HISTORY -import androidx.health.connect.client.permission.HealthPermission.Companion.PERMISSION_READ_HEALTH_DATA_IN_BACKGROUND -import androidx.health.connect.client.time.TimeRangeFilter -import io.flutter.plugin.common.MethodCall -import io.flutter.plugin.common.MethodChannel.Result -import java.time.Instant -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch - -/** - * Handles Health Connect operational tasks including permissions, SDK status, and data deletion - * operations. Manages the administrative aspects of Health Connect integration. - */ -class HealthDataOperations( - private val healthConnectClient: HealthConnectClient, - private val scope: CoroutineScope, - private val healthConnectStatus: Int, - private val healthConnectAvailable: Boolean -) { - - /** - * Retrieves the current Health Connect SDK availability status. Returns status codes indicating - * whether Health Connect is available, needs installation, etc. - * - * @param call Method call from Flutter (unused) - * @param result Flutter result callback to return SDK status integer - */ - fun getHealthConnectSdkStatus(call: MethodCall, result: Result) { - result.success(healthConnectStatus) - } - - /** - * Checks if the application has been granted the requested health data permissions. Verifies - * permission status without triggering permission request dialogs. - * - * @param call Method call containing 'types' (data types) and 'permissions' (access levels) - * @param result Flutter result callback returning boolean permission status - */ - fun hasPermissions(call: MethodCall, result: Result) { - val args = call.arguments as HashMap<*, *> - val types = (args["types"] as? ArrayList<*>)?.filterIsInstance()!! - val permissions = (args["permissions"] as? ArrayList<*>)?.filterIsInstance()!! - - val permList = preparePermissionsListInternal(types, permissions) - if (permList == null) { - result.success(false) - return - } - - scope.launch { - result.success( - healthConnectClient - .permissionController - .getGrantedPermissions() - .containsAll(permList), - ) - } - } - - /** - * Prepares a list of Health Connect permission strings for authorization requests. Converts - * Flutter data types and permission levels into Health Connect permission format. - * - * @param call Method call containing 'types' and 'permissions' arrays - * @return List? List of permission strings, or null if invalid types provided - */ - fun preparePermissionsList(call: MethodCall): List? { - Log.i("FLUTTER_HEALTH", "preparePermissionsList") - Log.i("FLUTTER_HEALTH", "call: $call") - val args = call.arguments as HashMap<*, *> - val types = (args["types"] as? ArrayList<*>)?.filterIsInstance()!! - val permissions = (args["permissions"] as? ArrayList<*>)?.filterIsInstance()!! - - return preparePermissionsListInternal(types, permissions) - } - - /** - * Revokes all previously granted Health Connect permissions for this application. Completely - * removes app access to Health Connect data. - * - * @param call Method call from Flutter (unused) - * @param result Flutter result callback returning success status - */ - fun revokePermissions(call: MethodCall, result: Result) { - scope.launch { - Log.i("FLUTTER_HEALTH", "Revoking all Health Connect permissions") - healthConnectClient.permissionController.revokeAllPermissions() - } - result.success(true) - } - - /** - * Checks if the health data history feature is available on the current device. History feature - * allows access to data from before the app was installed. - * - * @param call Method call from Flutter (unused) - * @param result Flutter result callback returning boolean availability status - */ - fun isHealthDataHistoryAvailable(call: MethodCall, result: Result) { - scope.launch { - result.success( - healthConnectClient.features.getFeatureStatus( - HealthConnectFeatures.FEATURE_READ_HEALTH_DATA_HISTORY - ) == HealthConnectFeatures.FEATURE_STATUS_AVAILABLE - ) - } - } - - /** - * Checks if the health data history permission has been granted. Verifies if app can access - * historical health data. - * - * @param call Method call from Flutter (unused) - * @param result Flutter result callback returning boolean authorization status - */ - fun isHealthDataHistoryAuthorized(call: MethodCall, result: Result) { - scope.launch { - result.success( - healthConnectClient - .permissionController - .getGrantedPermissions() - .containsAll(listOf(PERMISSION_READ_HEALTH_DATA_HISTORY)), - ) - } - } - - /** - * Checks if background health data reading feature is available on device. Background feature - * allows data access when app is not in foreground. - * - * @param call Method call from Flutter (unused) - * @param result Flutter result callback returning boolean availability status - */ - fun isHealthDataInBackgroundAvailable(call: MethodCall, result: Result) { - scope.launch { - result.success( - healthConnectClient.features.getFeatureStatus( - HealthConnectFeatures.FEATURE_READ_HEALTH_DATA_IN_BACKGROUND - ) == HealthConnectFeatures.FEATURE_STATUS_AVAILABLE - ) - } - } - - /** - * Checks if background health data reading permission has been granted. Verifies if app can - * access health data in background mode. - * - * @param call Method call from Flutter (unused) - * @param result Flutter result callback returning boolean authorization status - */ - fun isHealthDataInBackgroundAuthorized(call: MethodCall, result: Result) { - scope.launch { - result.success( - healthConnectClient - .permissionController - .getGrantedPermissions() - .containsAll(listOf(PERMISSION_READ_HEALTH_DATA_IN_BACKGROUND)), - ) - } - } - - /** - * Deletes all health records of a specified type within a given time range. Performs bulk - * deletion based on data type and time window. - * - * @param call Method call containing 'dataTypeKey', 'startTime', and 'endTime' - * @param result Flutter result callback returning boolean success status - */ - fun deleteData(call: MethodCall, result: Result) { - val type = call.argument("dataTypeKey")!! - val startTime = Instant.ofEpochMilli(call.argument("startTime")!!) - val endTime = Instant.ofEpochMilli(call.argument("endTime")!!) - - if (!HealthConstants.mapToType.containsKey(type)) { - Log.w("FLUTTER_HEALTH::ERROR", "Datatype $type not found in HC") - result.success(false) - return - } - - val classType = HealthConstants.mapToType[type]!! - - scope.launch { - try { - healthConnectClient.deleteRecords( - recordType = classType, - timeRangeFilter = TimeRangeFilter.between(startTime, endTime), - ) - result.success(true) - Log.i( - "FLUTTER_HEALTH::SUCCESS", - "Successfully deleted $type records between $startTime and $endTime" - ) - } catch (e: Exception) { - Log.e("FLUTTER_HEALTH::ERROR", "Error deleting $type records: ${e.message}") - result.success(false) - } - } - } - - /** - * Deletes a specific health record by its unique identifier and data type. Allows precise - * deletion of individual health records. - * - * @param call Method call containing 'dataTypeKey' and 'uuid' - * @param result Flutter result callback returning boolean success status - */ - fun deleteByUUID(call: MethodCall, result: Result) { - val arguments = call.arguments as? HashMap<*, *> - val dataTypeKey = (arguments?.get("dataTypeKey") as? String)!! - val uuid = (arguments?.get("uuid") as? String)!! - - if (!HealthConstants.mapToType.containsKey(dataTypeKey)) { - Log.w("FLUTTER_HEALTH::ERROR", "Datatype $dataTypeKey not found in HC") - result.success(false) - return - } - - val classType = HealthConstants.mapToType[dataTypeKey]!! - - scope.launch { - try { - healthConnectClient.deleteRecords( - recordType = classType, - recordIdsList = listOf(uuid), - clientRecordIdsList = emptyList() - ) - result.success(true) - Log.i( - "FLUTTER_HEALTH::SUCCESS", - "[Health Connect] Record with UUID $uuid was successfully deleted!" - ) - } catch (e: Exception) { - Log.e("FLUTTER_HEALTH::ERROR", "Error deleting record with UUID: $uuid") - Log.e("FLUTTER_HEALTH::ERROR", e.message ?: "unknown error") - Log.e("FLUTTER_HEALTH::ERROR", e.stackTraceToString()) - result.success(false) - } - } - } - - /** - * Deletes a specific health record by its client record ID and data type. Allows precise - * deletion of individual health records using client-side IDs. - * - * @param call Method call containing 'dataTypeKey', 'recordId', and 'clientRecordId' - * @param result Flutter result callback returning boolean success status - */ - fun deleteByClientRecordId(call: MethodCall, result: Result) { - val arguments = call.arguments as? HashMap<*, *> - val dataTypeKey = (arguments?.get("dataTypeKey") as? String)!! - val recordId = listOfNotNull(arguments["recordId"] as? String) - val clientRecordId = listOfNotNull(arguments["clientRecordId"] as? String) - if (!HealthConstants.mapToType.containsKey(dataTypeKey)) { - Log.w("FLUTTER_HEALTH::ERROR", "Datatype $dataTypeKey not found in HC") - result.success(false) - return - } - val classType = HealthConstants.mapToType[dataTypeKey]!! - - scope.launch { - try { - healthConnectClient.deleteRecords( - classType, - recordId, - clientRecordId - ) - result.success(true) - } catch (e: Exception) { - Log.e( - "FLUTTER_HEALTH::ERROR", - "Error deleting record with ClientRecordId: $clientRecordId" - ) - Log.e("FLUTTER_HEALTH::ERROR", e.message ?: "unknown error") - Log.e("FLUTTER_HEALTH::ERROR", e.stackTraceToString()) - result.success(false) - } - } - } - - /** - * Internal helper method to prepare Health Connect permission strings. Converts data type names - * and access levels into proper permission format. - * - * @param types List of health data type strings - * @param permissions List of permission level integers (0=read, 1=read+write) - * @return List? Formatted permission strings, or null if invalid input - */ - private fun preparePermissionsListInternal( - types: List, - permissions: List - ): List? { - val permList = mutableListOf() - - for ((i, typeKey) in types.withIndex()) { - if (!HealthConstants.mapToType.containsKey(typeKey)) { - Log.w("FLUTTER_HEALTH::ERROR", "Datatype $typeKey not found in HC") - return null - } - - val access = permissions[i] - val dataType = HealthConstants.mapToType[typeKey]!! - - if (access == 0) { - // Read permission only - permList.add( - HealthPermission.getReadPermission(dataType), - ) - } else if (access == 1) { - // Write permission only - permList.add( - HealthPermission.getWritePermission(dataType), - ) - } else { - // Read and write permissions - permList.addAll( - listOf( - HealthPermission.getReadPermission(dataType), - HealthPermission.getWritePermission(dataType), - ), - ) - } - } - - return permList - } -} diff --git a/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthDataReader.kt b/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthDataReader.kt deleted file mode 100644 index 37eab3837..000000000 --- a/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthDataReader.kt +++ /dev/null @@ -1,522 +0,0 @@ -package cachet.plugins.health - -import android.content.Context -import android.os.Handler -import android.util.Log -import androidx.health.connect.client.HealthConnectClient -import androidx.health.connect.client.permission.HealthPermission -import androidx.health.connect.client.records.* -import androidx.health.connect.client.request.AggregateGroupByDurationRequest -import androidx.health.connect.client.request.AggregateRequest -import androidx.health.connect.client.request.ReadRecordsRequest -import androidx.health.connect.client.time.TimeRangeFilter -import androidx.health.connect.client.units.* -import io.flutter.plugin.common.MethodCall -import io.flutter.plugin.common.MethodChannel.Result -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch -import java.time.Duration -import java.time.Instant -import java.time.temporal.ChronoUnit - -/** - * Handles reading and querying health data from Health Connect. - * Manages data retrieval, filtering, aggregation, and format conversion for Flutter consumption. - */ -class HealthDataReader( - private val healthConnectClient: HealthConnectClient, - private val scope: CoroutineScope, - private val context: Context, - private val dataConverter: HealthDataConverter -) { - private val recordingFilter = HealthRecordingFilter() - - /** - * Retrieves all health data points of a specified type within a given time range. - * Handles pagination for large datasets and applies recording method filtering. - * Supports special processing for workout and sleep data. - * - * @param call Method call containing 'dataTypeKey', 'startTime', 'endTime', 'recordingMethodsToFilter' - * @param result Flutter result callback returning list of health data maps - */ - fun getData(call: MethodCall, result: Result) { - val dataType = call.argument("dataTypeKey")!! - val dataUnit: String? = call.argument("dataUnitKey") - val startTime = Instant.ofEpochMilli(call.argument("startTime")!!) - val endTime = Instant.ofEpochMilli(call.argument("endTime")!!) - val healthConnectData = mutableListOf>() - val recordingMethodsToFilter = call.argument>("recordingMethodsToFilter")!! - - Log.i( - "FLUTTER_HEALTH", - "Getting data for $dataType with unit $dataUnit between $startTime and $endTime, filtering by $recordingMethodsToFilter" - ) - - scope.launch { - try { - val grantedPermissions = healthConnectClient.permissionController.getGrantedPermissions() - - val authorizedTypeMap = HealthConstants.mapToType.filter { (typeKey, classType) -> - val requiredPermission = HealthPermission.getReadPermission(classType) - grantedPermissions.contains(requiredPermission) - } - - authorizedTypeMap[dataType]?.let { classType -> - val records = mutableListOf() - - // Set up the initial request to read health records - var request = ReadRecordsRequest( - recordType = classType, - timeRangeFilter = TimeRangeFilter.between(startTime, endTime), - ) - - var response = healthConnectClient.readRecords(request) - var pageToken = response.pageToken - - // Add the records from the initial response - records.addAll(response.records) - - // Continue making requests while there is a page token - while (!pageToken.isNullOrEmpty()) { - request = ReadRecordsRequest( - recordType = classType, - timeRangeFilter = TimeRangeFilter.between(startTime, endTime), - pageToken = pageToken - ) - response = healthConnectClient.readRecords(request) - pageToken = response.pageToken - records.addAll(response.records) - } - - // Handle special cases - when (dataType) { - WORKOUT -> handleWorkoutData(records, recordingMethodsToFilter, healthConnectData) - SLEEP_SESSION, SLEEP_ASLEEP, SLEEP_AWAKE, SLEEP_AWAKE_IN_BED, - SLEEP_LIGHT, SLEEP_DEEP, SLEEP_REM, SLEEP_OUT_OF_BED, SLEEP_UNKNOWN -> - handleSleepData(records, recordingMethodsToFilter, dataType, healthConnectData) - else -> { - val filteredRecords = recordingFilter.filterRecordsByRecordingMethods( - recordingMethodsToFilter, - records - ) - for (rec in filteredRecords) { - healthConnectData.addAll( - dataConverter.convertRecord(rec, dataType, dataUnit) - ) - } - } - } - } - Handler(context.mainLooper).run { result.success(healthConnectData) } - } catch (e: Exception) { - Log.i( - "FLUTTER_HEALTH::ERROR", - "Unable to return $dataType due to the following exception:" - ) - Log.e("FLUTTER_HEALTH::ERROR", Log.getStackTraceString(e)) - result.success(emptyList>()) // Return empty list instead of null - } - } - } - - /** - * Retrieves single health data point by given UUID and type. - * - * @param call Method call containing 'UUID' and 'dataTypeKey' - * @param result Flutter result callback returning list of health data maps - */ - fun getDataByUUID(call: MethodCall, result: Result) { - val dataType = call.argument("dataTypeKey")!! - val uuid = call.argument("uuid")!! - var healthPoint = mapOf() - - if (!HealthConstants.mapToType.containsKey(dataType)) { - Log.w("FLUTTER_HEALTH::ERROR", "Datatype $dataType not found in HC") - result.success(null) - return - } - - val classType = HealthConstants.mapToType[dataType]!! - - scope.launch { - try { - - Log.i("FLUTTER_HEALTH", "Getting $uuid with $classType") - - // Execute the request - val response = healthConnectClient.readRecord(classType, uuid) - - // Find the record with the matching UUID - val matchingRecord = response.record - - if (matchingRecord != null) { - // Handle special cases using shared logic - when (dataType) { - WORKOUT -> { - val tempData = mutableListOf>() - handleWorkoutData(listOf(matchingRecord), emptyList(), tempData) - healthPoint = if (tempData.isNotEmpty()) tempData[0] else mapOf() - } - SLEEP_SESSION, SLEEP_ASLEEP, SLEEP_AWAKE, SLEEP_AWAKE_IN_BED, - SLEEP_LIGHT, SLEEP_DEEP, SLEEP_REM, SLEEP_OUT_OF_BED, SLEEP_UNKNOWN -> { - if (matchingRecord is SleepSessionRecord) { - val tempData = mutableListOf>() - handleSleepData(listOf(matchingRecord), emptyList(), dataType, tempData) - healthPoint = if (tempData.isNotEmpty()) tempData[0] else mapOf() - } - } - else -> { - healthPoint = dataConverter.convertRecord(matchingRecord, dataType)[0] - } - } - - Log.i( - "FLUTTER_HEALTH", - "Success: $healthPoint" - ) - - Handler(context.mainLooper).run { result.success(healthPoint) } - } else { - Log.e("FLUTTER_HEALTH::ERROR", "Record not found for UUID: $uuid") - result.success(null) - } - } catch (e: Exception) { - Log.e("FLUTTER_HEALTH::ERROR", "Error fetching record with UUID: $uuid") - Log.e("FLUTTER_HEALTH::ERROR", e.message ?: "unknown error") - Log.e("FLUTTER_HEALTH::ERROR", e.stackTraceToString()) - result.success(null) - } - } - } - - /** - * Retrieves aggregated health data grouped by time intervals. - * Calculates totals, averages, or counts over specified time periods. - * - * @param call Method call containing 'dataTypeKey', 'interval', 'startTime', 'endTime' - * @param result Flutter result callback returning list of aggregated data maps - */ - fun getAggregateData(call: MethodCall, result: Result) { - val dataType = call.argument("dataTypeKey")!! - val interval = call.argument("interval")!! - val startTime = Instant.ofEpochMilli(call.argument("startTime")!!) - val endTime = Instant.ofEpochMilli(call.argument("endTime")!!) - val healthConnectData = mutableListOf>() - - scope.launch { - try { - HealthConstants.mapToAggregateMetric[dataType]?.let { metricClassType -> - val request = AggregateGroupByDurationRequest( - metrics = setOf(metricClassType), - timeRangeFilter = TimeRangeFilter.between(startTime, endTime), - timeRangeSlicer = Duration.ofSeconds(interval) - ) - val response = healthConnectClient.aggregateGroupByDuration(request) - - for (durationResult in response) { - var totalValue = durationResult.result[metricClassType] - if (totalValue is Length) { - totalValue = totalValue.inMeters - } else if (totalValue is Energy) { - totalValue = totalValue.inKilocalories - } - - val packageNames = durationResult.result.dataOrigins - .joinToString { origin -> origin.packageName } - - val data = mapOf( - "value" to (totalValue ?: 0), - "date_from" to durationResult.startTime.toEpochMilli(), - "date_to" to durationResult.endTime.toEpochMilli(), - "source_name" to packageNames, - "source_id" to "", - "is_manual_entry" to packageNames.contains("user_input") - ) - healthConnectData.add(data) - } - } - Handler(context.mainLooper).run { result.success(healthConnectData) } - } catch (e: Exception) { - Log.i( - "FLUTTER_HEALTH::ERROR", - "Unable to return $dataType due to the following exception:" - ) - Log.e("FLUTTER_HEALTH::ERROR", Log.getStackTraceString(e)) - result.success(null) - } - } - } - - /** - * Retrieves interval-based health data. Currently delegates to getAggregateData. - * Maintained for API compatibility and potential future differentiation. - * - * @param call Method call with interval data parameters - * @param result Flutter result callback returning interval data - */ - fun getIntervalData(call: MethodCall, result: Result) { - getAggregateData(call, result) - } - - /** - * Gets total step count within a specified time interval with optional filtering. - * Optimizes between aggregated queries and filtered individual record queries - * based on whether recording method filtering is required. - * - * @param call Method call containing 'startTime', 'endTime', 'recordingMethodsToFilter' - * @param result Flutter result callback returning total step count as integer - */ - fun getTotalStepsInInterval(call: MethodCall, result: Result) { - val start = call.argument("startTime")!! - val end = call.argument("endTime")!! - val recordingMethodsToFilter = call.argument>("recordingMethodsToFilter")!! - - if (recordingMethodsToFilter.isEmpty()) { - getAggregatedStepCount(start, end, result) - } else { - getStepCountFiltered(start, end, recordingMethodsToFilter, result) - } - } - - // --------- Private Methods --------- - - /** - * Retrieves aggregated step count using Health Connect's built-in aggregation. - * Provides optimized step counting when no filtering is required. - * - * @param start Start time in milliseconds - * @param end End time in milliseconds - * @param result Flutter result callback returning step count - */ - private fun getAggregatedStepCount(start: Long, end: Long, result: Result) { - val startInstant = Instant.ofEpochMilli(start) - val endInstant = Instant.ofEpochMilli(end) - - scope.launch { - try { - val response = healthConnectClient.aggregate( - AggregateRequest( - metrics = setOf(StepsRecord.COUNT_TOTAL), - timeRangeFilter = TimeRangeFilter.between(startInstant, endInstant), - ), - ) - val stepsInInterval = response[StepsRecord.COUNT_TOTAL] ?: 0L - - Log.i("FLUTTER_HEALTH::SUCCESS", "returning $stepsInInterval steps") - result.success(stepsInInterval) - } catch (e: Exception) { - Log.e( - "FLUTTER_HEALTH::ERROR", - "Unable to return steps due to the following exception:" - ) - Log.e("FLUTTER_HEALTH::ERROR", Log.getStackTraceString(e)) - result.success(null) - } - } - } - - /** - * Retrieves step count with recording method filtering applied. - * Manually sums individual step records after applying specified filters. - * - * @param start Start time in milliseconds - * @param end End time in milliseconds - * @param recordingMethodsToFilter List of recording methods to exclude - * @param result Flutter result callback returning filtered step count - */ - private fun getStepCountFiltered( - start: Long, - end: Long, - recordingMethodsToFilter: List, - result: Result - ) { - scope.launch { - try { - val request = ReadRecordsRequest( - recordType = StepsRecord::class, - timeRangeFilter = TimeRangeFilter.between( - Instant.ofEpochMilli(start), - Instant.ofEpochMilli(end) - ), - ) - val response = healthConnectClient.readRecords(request) - val filteredRecords = recordingFilter.filterRecordsByRecordingMethods( - recordingMethodsToFilter, - response.records - ) - val totalSteps = filteredRecords.sumOf { (it as StepsRecord).count.toInt() } - - Log.i( - "FLUTTER_HEALTH::SUCCESS", - "returning $totalSteps steps (excluding manual entries)" - ) - result.success(totalSteps) - } catch (e: Exception) { - Log.e( - "FLUTTER_HEALTH::ERROR", - "Unable to return steps due to the following exception:" - ) - Log.e("FLUTTER_HEALTH::ERROR", Log.getStackTraceString(e)) - result.success(null) - } - } - } - - /** - * Handles special processing for workout/exercise session data. - * Enriches workout records with associated distance, energy, and step data - * by querying related records within the workout time period. - * - * @param records List of ExerciseSessionRecord objects - * @param recordingMethodsToFilter Recording methods to exclude (empty list means no filtering) - * @param healthConnectData Mutable list to append processed workout data - */ - private suspend fun handleWorkoutData( - records: List, - recordingMethodsToFilter: List = emptyList(), - healthConnectData: MutableList> - ) { - val filteredRecords = if (recordingMethodsToFilter.isEmpty()) { - records - } else { - recordingFilter.filterRecordsByRecordingMethods( - recordingMethodsToFilter, - records - ) - } - - for (rec in filteredRecords) { - val record = rec as ExerciseSessionRecord - - // Get distance data - val distanceRequest = healthConnectClient.readRecords( - ReadRecordsRequest( - recordType = DistanceRecord::class, - timeRangeFilter = TimeRangeFilter.between( - record.startTime, - record.endTime, - ), - ), - ) - var totalDistance = 0.0 - for (distanceRec in distanceRequest.records) { - totalDistance += distanceRec.distance.inMeters - } - - // Get energy burned data - val energyBurnedRequest = healthConnectClient.readRecords( - ReadRecordsRequest( - recordType = TotalCaloriesBurnedRecord::class, - timeRangeFilter = TimeRangeFilter.between( - record.startTime, - record.endTime, - ), - ), - ) - var totalEnergyBurned = 0.0 - for (energyBurnedRec in energyBurnedRequest.records) { - totalEnergyBurned += energyBurnedRec.energy.inKilocalories - } - - // Get steps data - val stepRequest = healthConnectClient.readRecords( - ReadRecordsRequest( - recordType = StepsRecord::class, - timeRangeFilter = TimeRangeFilter.between( - record.startTime, - record.endTime - ), - ), - ) - var totalSteps = 0.0 - for (stepRec in stepRequest.records) { - totalSteps += stepRec.count - } - - // Add final datapoint - healthConnectData.add( - mapOf( - "uuid" to record.metadata.id, - "workoutActivityType" to - (HealthConstants.workoutTypeMap - .filterValues { it == record.exerciseType } - .keys - .firstOrNull() ?: "OTHER"), - "totalDistance" to if (totalDistance == 0.0) null else totalDistance, - "totalDistanceUnit" to "METER", - "totalEnergyBurned" to if (totalEnergyBurned == 0.0) null else totalEnergyBurned, - "totalEnergyBurnedUnit" to "KILOCALORIE", - "totalSteps" to if (totalSteps == 0.0) null else totalSteps, - "totalStepsUnit" to "COUNT", - "unit" to "MINUTES", - "date_from" to record.startTime.toEpochMilli(), - "date_to" to record.endTime.toEpochMilli(), - "source_id" to "", - "source_name" to record.metadata.dataOrigin.packageName, - ), - ) - } - } - - /** - * Handles special processing for sleep session and stage data. - * Processes sleep sessions and individual sleep stages based on requested data type. - * Converts sleep stage enumerations to meaningful duration and type information. - * - * @param records List of SleepSessionRecord objects - * @param recordingMethodsToFilter Recording methods to exclude (empty list means no filtering) - * @param dataType Specific sleep data type being requested - * @param healthConnectData Mutable list to append processed sleep data - */ - private fun handleSleepData( - records: List, - recordingMethodsToFilter: List = emptyList(), - dataType: String, - healthConnectData: MutableList> - ) { - val filteredRecords = if (recordingMethodsToFilter.isEmpty()) { - records - } else { - recordingFilter.filterRecordsByRecordingMethods( - recordingMethodsToFilter, - records - ) - } - - for (rec in filteredRecords) { - if (rec is SleepSessionRecord) { - if (dataType == SLEEP_SESSION) { - healthConnectData.addAll( - dataConverter.convertRecord(rec, dataType) - ) - } else { - for (recStage in rec.stages) { - if (dataType == HealthConstants.mapSleepStageToType[recStage.stage]) { - healthConnectData.addAll( - dataConverter.convertRecordStage( - recStage, - dataType, - rec.metadata - ) - ) - } - } - } - } - } - } - - companion object { - // Sleep-related constants - private const val SLEEP_SESSION = "SLEEP_SESSION" - private const val SLEEP_ASLEEP = "SLEEP_ASLEEP" - private const val SLEEP_AWAKE = "SLEEP_AWAKE" - private const val SLEEP_AWAKE_IN_BED = "SLEEP_AWAKE_IN_BED" - private const val SLEEP_LIGHT = "SLEEP_LIGHT" - private const val SLEEP_DEEP = "SLEEP_DEEP" - private const val SLEEP_REM = "SLEEP_REM" - private const val SLEEP_OUT_OF_BED = "SLEEP_OUT_OF_BED" - private const val SLEEP_UNKNOWN = "SLEEP_UNKNOWN" - private const val WORKOUT = "WORKOUT" - } -} diff --git a/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthDataWriter.kt b/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthDataWriter.kt deleted file mode 100644 index 976a9bf15..000000000 --- a/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthDataWriter.kt +++ /dev/null @@ -1,867 +0,0 @@ -package cachet.plugins.health - -import android.util.Log -import androidx.health.connect.client.HealthConnectClient -import androidx.health.connect.client.records.* -import androidx.health.connect.client.records.metadata.Device -import androidx.health.connect.client.records.metadata.Metadata -import androidx.health.connect.client.units.* -import io.flutter.plugin.common.MethodCall -import io.flutter.plugin.common.MethodChannel.Result -import java.time.Instant -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch - -/** - * Handles writing health data to Health Connect. Manages data insertion for various health metrics, - * specialized records like workouts and nutrition, and proper data type conversion from Flutter to - * Health Connect format. - */ -class HealthDataWriter( - private val healthConnectClient: HealthConnectClient, - private val scope: CoroutineScope -) { - - // Maps incoming recordingMethod int -> Metadata factory method. - // 0: unknown, 1: manual, 2: auto, 3: active (default unknown for others) - private fun buildMetadata( - recordingMethod: Int, - clientRecordId: String? = null, - clientRecordVersion: Long? = null, - deviceType: Int? = null, - ): Metadata { - // Device is required for auto/active; optional for manual/unknown - val deviceForAutoOrActive = - when (recordingMethod) { - RECORDING_METHOD_AUTOMATICALLY_RECORDED, - RECORDING_METHOD_ACTIVELY_RECORDED -> - Device(type = deviceType ?: Device.TYPE_UNKNOWN) - else -> null - } - - return when (recordingMethod) { - RECORDING_METHOD_MANUAL_ENTRY -> { - if (clientRecordId != null && clientRecordVersion != null) { - Metadata.manualEntry( - device = null, - clientRecordId = clientRecordId, - clientRecordVersion = clientRecordVersion, - ) - } else { - Metadata.manualEntry() - } - } - RECORDING_METHOD_AUTOMATICALLY_RECORDED -> { - val dev = deviceForAutoOrActive!! - if (clientRecordId != null && clientRecordVersion != null) { - Metadata.autoRecorded( - device = dev, - clientRecordId = clientRecordId, - clientRecordVersion = clientRecordVersion, - ) - } else { - Metadata.autoRecorded(dev) - } - } - RECORDING_METHOD_ACTIVELY_RECORDED -> { - val dev = deviceForAutoOrActive!! - if (clientRecordId != null && clientRecordVersion != null) { - Metadata.activelyRecorded( - device = dev, - clientRecordId = clientRecordId, - clientRecordVersion = clientRecordVersion, - ) - } else { - Metadata.activelyRecorded(dev) - } - } - else -> { // unknown - if (clientRecordId != null && clientRecordVersion != null) { - Metadata.unknownRecordingMethod( - device = null, - clientRecordId = clientRecordId, - clientRecordVersion = clientRecordVersion, - ) - } else { - Metadata.unknownRecordingMethod() - } - } - } - } - - - /** - * Writes a single health data record to Health Connect. Supports most basic health metrics with - * automatic type conversion and validation. - * - * @param call Method call containing 'dataTypeKey', 'startTime', 'endTime', 'value', - * 'recordingMethod' - * @param result Flutter result callback returning boolean success status - */ - fun writeData(call: MethodCall, result: Result) { - val type = call.argument("dataTypeKey")!! - val startTime = call.argument("startTime")!! - val endTime = call.argument("endTime")!! - val value = call.argument("value")!! - val clientRecordId: String? = call.argument("clientRecordId") - val clientRecordVersion: Double? = call.argument("clientRecordVersion") - val recordingMethod = call.argument("recordingMethod")!! - val deviceType: Int? = call.argument("deviceType") - - Log.i( - "FLUTTER_HEALTH", - "Writing data for $type between $startTime and $endTime, value: $value, recording method: $recordingMethod" - ) - - val metadata: Metadata = buildMetadata( - recordingMethod = recordingMethod, - clientRecordId = clientRecordId, - clientRecordVersion = clientRecordVersion?.toLong(), - deviceType = deviceType, - ) - - val record = createRecord(type, startTime, endTime, value, metadata) - - if (record == null) { - result.success(false) - return - } - - scope.launch { - try { - healthConnectClient.insertRecords(listOf(record)) - result.success(true) - } catch (e: Exception) { - Log.e("FLUTTER_HEALTH::ERROR", "Error writing $type: ${e.message}") - result.success(false) - } - } - } - - /** - * Writes a comprehensive workout session with optional distance and calorie data. Creates an - * ExerciseSessionRecord with associated DistanceRecord and TotalCaloriesBurnedRecord if - * supplementary data is provided. - * - * @param call Method call containing workout details: 'activityType', 'startTime', 'endTime', - * ``` - * 'totalEnergyBurned', 'totalDistance', 'recordingMethod', 'title' - * @param result - * ``` - * Flutter result callback returning boolean success status - */ - fun writeWorkoutData(call: MethodCall, result: Result) { - val type = call.argument("activityType")!! - val startTime = Instant.ofEpochMilli(call.argument("startTime")!!) - val endTime = Instant.ofEpochMilli(call.argument("endTime")!!) - val totalEnergyBurned = call.argument("totalEnergyBurned") - val totalDistance = call.argument("totalDistance") - val recordingMethod = call.argument("recordingMethod")!! - val deviceType: Int? = call.argument("deviceType") - val workoutMetadata = buildMetadata(recordingMethod = recordingMethod, deviceType = deviceType) - - if (!HealthConstants.workoutTypeMap.containsKey(type)) { - result.success(false) - Log.w("FLUTTER_HEALTH::ERROR", "[Health Connect] Workout type not supported") - return - } - - val workoutType = HealthConstants.workoutTypeMap[type]!! - val title = call.argument("title") ?: type - - scope.launch { - try { - val list = mutableListOf() - - // Add exercise session record - list.add( - ExerciseSessionRecord( - startTime = startTime, - startZoneOffset = null, - endTime = endTime, - endZoneOffset = null, - exerciseType = workoutType, - title = title, - metadata = workoutMetadata, - ), - ) - - // Add distance record if provided - if (totalDistance != null) { - list.add( - DistanceRecord( - startTime = startTime, - startZoneOffset = null, - endTime = endTime, - endZoneOffset = null, - distance = Length.meters(totalDistance.toDouble()), - metadata = workoutMetadata, - ), - ) - } - - // Add energy burned record if provided - if (totalEnergyBurned != null) { - list.add( - TotalCaloriesBurnedRecord( - startTime = startTime, - startZoneOffset = null, - endTime = endTime, - endZoneOffset = null, - energy = Energy.kilocalories(totalEnergyBurned.toDouble()), - metadata = workoutMetadata, - ), - ) - } - - healthConnectClient.insertRecords(list) - result.success(true) - Log.i("FLUTTER_HEALTH::SUCCESS", "[Health Connect] Workout was successfully added!") - } catch (e: Exception) { - Log.w( - "FLUTTER_HEALTH::ERROR", - "[Health Connect] There was an error adding the workout", - ) - Log.w("FLUTTER_HEALTH::ERROR", e.message ?: "unknown error") - Log.w("FLUTTER_HEALTH::ERROR", e.stackTrace.toString()) - result.success(false) - } - } - } - - /** - * Writes blood pressure measurement with both systolic and diastolic values. Creates a single - * BloodPressureRecord containing both pressure readings taken at the same time point. - * - * @param call Method call containing 'systolic', 'diastolic', 'startTime', 'recordingMethod' - * @param result Flutter result callback returning boolean success status - */ - fun writeBloodPressure(call: MethodCall, result: Result) { - val systolic = call.argument("systolic")!! - val diastolic = call.argument("diastolic")!! - val startTime = Instant.ofEpochMilli(call.argument("startTime")!!) - val recordingMethod = call.argument("recordingMethod")!! - val clientRecordId: String? = call.argument("clientRecordId") - val clientRecordVersion: Double? = call.argument("clientRecordVersion") - val deviceType: Int? = call.argument("deviceType") - - scope.launch { - try { - val metadata: Metadata = buildMetadata( - recordingMethod = recordingMethod, - clientRecordId = clientRecordId, - clientRecordVersion = clientRecordVersion?.toLong(), - deviceType = deviceType, - ) - healthConnectClient.insertRecords( - listOf( - BloodPressureRecord( - time = startTime, - systolic = Pressure.millimetersOfMercury(systolic), - diastolic = Pressure.millimetersOfMercury(diastolic), - zoneOffset = null, - metadata = metadata, - ), - ), - ) - result.success(true) - Log.i( - "FLUTTER_HEALTH::SUCCESS", - "[Health Connect] Blood pressure was successfully added!", - ) - } catch (e: Exception) { - Log.w( - "FLUTTER_HEALTH::ERROR", - "[Health Connect] There was an error adding the blood pressure", - ) - Log.w("FLUTTER_HEALTH::ERROR", e.message ?: "unknown error") - Log.w("FLUTTER_HEALTH::ERROR", e.stackTrace.toString()) - result.success(false) - } - } - } - - /** - * Writes blood oxygen saturation measurement. Delegates to standard writeData method for - * OxygenSaturationRecord handling. - * - * @param call Method call with blood oxygen data - * @param result Flutter result callback returning success status - */ - fun writeBloodOxygen(call: MethodCall, result: Result) { - writeData(call, result) - } - - /** - * Writes menstrual flow data. Delegates to standard writeData method for MenstruationFlowRecord - * handling. - * - * @param call Method call with menstruation flow data - * @param result Flutter result callback returning success status - */ - fun writeMenstruationFlow(call: MethodCall, result: Result) { - writeData(call, result) - } - - /** - * Writes comprehensive nutrition/meal data with detailed nutrient breakdown. Creates - * NutritionRecord with extensive nutrient information including vitamins, minerals, - * macronutrients, and meal classification. - * - * @param call Method call containing nutrition data: calories, macronutrients, vitamins, - * ``` - * minerals, meal details, timing information - * @param result - * ``` - * Flutter result callback returning boolean success status - */ - fun writeMeal(call: MethodCall, result: Result) { - val startTime = Instant.ofEpochMilli(call.argument("start_time")!!) - val endTime = Instant.ofEpochMilli(call.argument("end_time")!!) - val calories = call.argument("calories") - val protein = call.argument("protein") - val carbs = call.argument("carbs") - val fat = call.argument("fat") - val caffeine = call.argument("caffeine") - val vitaminA = call.argument("vitamin_a") - val b1Thiamine = call.argument("b1_thiamine") - val b2Riboflavin = call.argument("b2_riboflavin") - val b3Niacin = call.argument("b3_niacin") - val b5PantothenicAcid = call.argument("b5_pantothenic_acid") - val b6Pyridoxine = call.argument("b6_pyridoxine") - val b7Biotin = call.argument("b7_biotin") - val b9Folate = call.argument("b9_folate") - val b12Cobalamin = call.argument("b12_cobalamin") - val vitaminC = call.argument("vitamin_c") - val vitaminD = call.argument("vitamin_d") - val vitaminE = call.argument("vitamin_e") - val vitaminK = call.argument("vitamin_k") - val calcium = call.argument("calcium") - val chloride = call.argument("chloride") - val cholesterol = call.argument("cholesterol") - val chromium = call.argument("chromium") - val copper = call.argument("copper") - val fatUnsaturated = call.argument("fat_unsaturated") - val fatMonounsaturated = call.argument("fat_monounsaturated") - val fatPolyunsaturated = call.argument("fat_polyunsaturated") - val fatSaturated = call.argument("fat_saturated") - val fatTransMonoenoic = call.argument("fat_trans_monoenoic") - val fiber = call.argument("fiber") - val iodine = call.argument("iodine") - val iron = call.argument("iron") - val magnesium = call.argument("magnesium") - val manganese = call.argument("manganese") - val molybdenum = call.argument("molybdenum") - val phosphorus = call.argument("phosphorus") - val potassium = call.argument("potassium") - val selenium = call.argument("selenium") - val sodium = call.argument("sodium") - val sugar = call.argument("sugar") - val zinc = call.argument("zinc") - - val name = call.argument("name") - val mealType = call.argument("meal_type")!! - val recordingMethod = call.argument("recordingMethod") ?: RECORDING_METHOD_MANUAL_ENTRY - val clientRecordId: String? = call.argument("clientRecordId") - val clientRecordVersion: Double? = call.argument("clientRecordVersion") - val deviceType: Int? = call.argument("deviceType") - - scope.launch { - try { - val metadata: Metadata = buildMetadata( - recordingMethod = recordingMethod, - clientRecordId = clientRecordId, - clientRecordVersion = clientRecordVersion?.toLong(), - deviceType = deviceType, - ) - val list = mutableListOf() - - list.add( - NutritionRecord( - name = name, - metadata = metadata, - energy = calories?.kilocalories, - totalCarbohydrate = carbs?.grams, - protein = protein?.grams, - totalFat = fat?.grams, - caffeine = caffeine?.grams, - vitaminA = vitaminA?.grams, - thiamin = b1Thiamine?.grams, - riboflavin = b2Riboflavin?.grams, - niacin = b3Niacin?.grams, - pantothenicAcid = b5PantothenicAcid?.grams, - vitaminB6 = b6Pyridoxine?.grams, - biotin = b7Biotin?.grams, - folate = b9Folate?.grams, - vitaminB12 = b12Cobalamin?.grams, - vitaminC = vitaminC?.grams, - vitaminD = vitaminD?.grams, - vitaminE = vitaminE?.grams, - vitaminK = vitaminK?.grams, - calcium = calcium?.grams, - chloride = chloride?.grams, - cholesterol = cholesterol?.grams, - chromium = chromium?.grams, - copper = copper?.grams, - unsaturatedFat = fatUnsaturated?.grams, - monounsaturatedFat = fatMonounsaturated?.grams, - polyunsaturatedFat = fatPolyunsaturated?.grams, - saturatedFat = fatSaturated?.grams, - transFat = fatTransMonoenoic?.grams, - dietaryFiber = fiber?.grams, - iodine = iodine?.grams, - iron = iron?.grams, - magnesium = magnesium?.grams, - manganese = manganese?.grams, - molybdenum = molybdenum?.grams, - phosphorus = phosphorus?.grams, - potassium = potassium?.grams, - selenium = selenium?.grams, - sodium = sodium?.grams, - sugar = sugar?.grams, - zinc = zinc?.grams, - startTime = startTime, - startZoneOffset = null, - endTime = endTime, - endZoneOffset = null, - mealType = HealthConstants.mapMealTypeToType[mealType] - ?: MealType.MEAL_TYPE_UNKNOWN - ), - ) - healthConnectClient.insertRecords(list) - result.success(true) - Log.i("FLUTTER_HEALTH::SUCCESS", "[Health Connect] Meal was successfully added!") - } catch (e: Exception) { - Log.w( - "FLUTTER_HEALTH::ERROR", - "[Health Connect] There was an error adding the meal", - ) - Log.w("FLUTTER_HEALTH::ERROR", e.message ?: "unknown error") - Log.w("FLUTTER_HEALTH::ERROR", e.stackTrace.toString()) - result.success(false) - } - } - } - - /** - * Writes speed/velocity data with multiple samples to Health Connect. Creates a SpeedRecord - * containing time-series speed measurements captured during activities like running, cycling, - * or walking. Each sample represents the user's instantaneous speed at a specific moment within - * the recording period. - * - * @param call Method call containing startTime, endTime, recordingMethod, - * ``` - * samples: List> List of speed measurements, each - * containing: time, speed (m/s) - * - * @param result - * ``` - * Flutter result callback returning boolean success status - */ - fun writeMultipleSpeedData(call: MethodCall, result: Result) { - val startTime = call.argument("startTime")!! - val endTime = call.argument("endTime")!! - val samples = call.argument>>("samples")!! - val recordingMethod = call.argument("recordingMethod")!! - val deviceType: Int? = call.argument("deviceType") - - scope.launch { - try { - val speedSamples = - samples.map { sample -> - SpeedRecord.Sample( - time = Instant.ofEpochMilli(sample["time"] as Long), - speed = Velocity.metersPerSecond(sample["speed"] as Double) - ) - } - - val metadata = buildMetadata(recordingMethod, deviceType = deviceType) - - val speedRecord = - SpeedRecord( - startTime = Instant.ofEpochMilli(startTime), - endTime = Instant.ofEpochMilli(endTime), - samples = speedSamples, - startZoneOffset = null, - endZoneOffset = null, - metadata = metadata, - ) - - healthConnectClient.insertRecords(listOf(speedRecord)) - result.success(true) - Log.i( - "FLUTTER_HEALTH::SUCCESS", - "Successfully wrote ${speedSamples.size} speed samples" - ) - } catch (e: Exception) { - Log.e("FLUTTER_HEALTH::ERROR", "Error writing speed data: ${e.message}") - result.success(false) - } - } - } - - // ---------- Private Methods ---------- - - /** - * Creates appropriate Health Connect record objects based on data type. Factory method that - * instantiates the correct record type with proper unit conversion and metadata assignment. - * - * @param type Health data type string identifier - * @param startTime Record start time in milliseconds - * @param endTime Record end time in milliseconds - * @param value Measured value to record - * @param recordingMethod How the data was recorded (manual, automatic, etc.) - * @return Record? Properly configured Health Connect record, or null if type unsupported - */ - private fun createRecord( - type: String, - startTime: Long, - endTime: Long, - value: Double, - metadata: Metadata - ): Record? { - return when (type) { - BODY_FAT_PERCENTAGE -> - BodyFatRecord( - time = Instant.ofEpochMilli(startTime), - percentage = Percentage(value), - zoneOffset = null, - metadata = metadata, - ) - LEAN_BODY_MASS -> - LeanBodyMassRecord( - time = Instant.ofEpochMilli(startTime), - mass = Mass.kilograms(value), - zoneOffset = null, - metadata = metadata, - ) - HEIGHT -> - HeightRecord( - time = Instant.ofEpochMilli(startTime), - height = Length.meters(value), - zoneOffset = null, - metadata = metadata, - ) - WEIGHT -> - WeightRecord( - time = Instant.ofEpochMilli(startTime), - weight = Mass.kilograms(value), - zoneOffset = null, - metadata = metadata, - ) - STEPS -> - StepsRecord( - startTime = Instant.ofEpochMilli(startTime), - endTime = Instant.ofEpochMilli(endTime), - count = value.toLong(), - startZoneOffset = null, - endZoneOffset = null, - metadata = metadata, - ) - ACTIVE_ENERGY_BURNED -> - ActiveCaloriesBurnedRecord( - startTime = Instant.ofEpochMilli(startTime), - endTime = Instant.ofEpochMilli(endTime), - energy = Energy.kilocalories(value), - startZoneOffset = null, - endZoneOffset = null, - metadata = metadata, - ) - HEART_RATE -> - HeartRateRecord( - startTime = Instant.ofEpochMilli(startTime), - endTime = Instant.ofEpochMilli(endTime), - samples = - listOf( - HeartRateRecord.Sample( - time = Instant.ofEpochMilli(startTime), - beatsPerMinute = value.toLong(), - ), - ), - startZoneOffset = null, - endZoneOffset = null, - metadata = metadata, - ) - BODY_TEMPERATURE -> - BodyTemperatureRecord( - time = Instant.ofEpochMilli(startTime), - temperature = Temperature.celsius(value), - zoneOffset = null, - metadata = metadata, - ) - BODY_WATER_MASS -> - BodyWaterMassRecord( - time = Instant.ofEpochMilli(startTime), - mass = Mass.kilograms(value), - zoneOffset = null, - metadata = metadata, - ) - BLOOD_OXYGEN -> - OxygenSaturationRecord( - time = Instant.ofEpochMilli(startTime), - percentage = Percentage(value), - zoneOffset = null, - metadata = metadata, - ) - BLOOD_GLUCOSE -> - BloodGlucoseRecord( - time = Instant.ofEpochMilli(startTime), - level = BloodGlucose.milligramsPerDeciliter(value), - zoneOffset = null, - metadata = metadata, - ) - HEART_RATE_VARIABILITY_RMSSD -> - HeartRateVariabilityRmssdRecord( - time = Instant.ofEpochMilli(startTime), - heartRateVariabilityMillis = value, - zoneOffset = null, - metadata = metadata, - ) - DISTANCE_DELTA -> - DistanceRecord( - startTime = Instant.ofEpochMilli(startTime), - endTime = Instant.ofEpochMilli(endTime), - distance = Length.meters(value), - startZoneOffset = null, - endZoneOffset = null, - metadata = metadata, - ) - WATER -> - HydrationRecord( - startTime = Instant.ofEpochMilli(startTime), - endTime = Instant.ofEpochMilli(endTime), - volume = Volume.liters(value), - startZoneOffset = null, - endZoneOffset = null, - metadata = metadata, - ) - SLEEP_ASLEEP -> - createSleepRecord( - startTime, - endTime, - SleepSessionRecord.STAGE_TYPE_SLEEPING, - metadata - ) - SLEEP_LIGHT -> - createSleepRecord( - startTime, - endTime, - SleepSessionRecord.STAGE_TYPE_LIGHT, - metadata - ) - SLEEP_DEEP -> - createSleepRecord( - startTime, - endTime, - SleepSessionRecord.STAGE_TYPE_DEEP, - metadata - ) - SLEEP_REM -> - createSleepRecord( - startTime, - endTime, - SleepSessionRecord.STAGE_TYPE_REM, - metadata - ) - SLEEP_OUT_OF_BED -> - createSleepRecord( - startTime, - endTime, - SleepSessionRecord.STAGE_TYPE_OUT_OF_BED, - metadata - ) - SLEEP_AWAKE -> - createSleepRecord( - startTime, - endTime, - SleepSessionRecord.STAGE_TYPE_AWAKE, - metadata - ) - SLEEP_AWAKE_IN_BED -> - createSleepRecord( - startTime, - endTime, - SleepSessionRecord.STAGE_TYPE_AWAKE_IN_BED, - metadata - ) - SLEEP_UNKNOWN -> - createSleepRecord( - startTime, - endTime, - SleepSessionRecord.STAGE_TYPE_UNKNOWN, - metadata - ) - SLEEP_SESSION -> - SleepSessionRecord( - startTime = Instant.ofEpochMilli(startTime), - endTime = Instant.ofEpochMilli(endTime), - startZoneOffset = null, - endZoneOffset = null, - metadata = metadata, - ) - RESTING_HEART_RATE -> - RestingHeartRateRecord( - time = Instant.ofEpochMilli(startTime), - beatsPerMinute = value.toLong(), - zoneOffset = null, - metadata = metadata, - ) - BASAL_ENERGY_BURNED -> - BasalMetabolicRateRecord( - time = Instant.ofEpochMilli(startTime), - basalMetabolicRate = Power.kilocaloriesPerDay(value), - zoneOffset = null, - metadata = metadata, - ) - FLIGHTS_CLIMBED -> - FloorsClimbedRecord( - startTime = Instant.ofEpochMilli(startTime), - endTime = Instant.ofEpochMilli(endTime), - floors = value, - startZoneOffset = null, - endZoneOffset = null, - metadata = metadata, - ) - RESPIRATORY_RATE -> - RespiratoryRateRecord( - time = Instant.ofEpochMilli(startTime), - rate = value, - zoneOffset = null, - metadata = metadata, - ) - TOTAL_CALORIES_BURNED -> - TotalCaloriesBurnedRecord( - startTime = Instant.ofEpochMilli(startTime), - endTime = Instant.ofEpochMilli(endTime), - energy = Energy.kilocalories(value), - startZoneOffset = null, - endZoneOffset = null, - metadata = metadata, - ) - MENSTRUATION_FLOW -> - MenstruationFlowRecord( - time = Instant.ofEpochMilli(startTime), - flow = value.toInt(), - zoneOffset = null, - metadata = metadata, - ) - SPEED -> - SpeedRecord( - startTime = Instant.ofEpochMilli(startTime), - endTime = Instant.ofEpochMilli(endTime), - samples = - listOf( - SpeedRecord.Sample( - time = Instant.ofEpochMilli(startTime), - speed = Velocity.metersPerSecond(value), - ) - ), - startZoneOffset = null, - endZoneOffset = null, - metadata = metadata, - ) - BLOOD_PRESSURE_SYSTOLIC -> { - Log.e("FLUTTER_HEALTH::ERROR", "You must use the [writeBloodPressure] API") - null - } - BLOOD_PRESSURE_DIASTOLIC -> { - Log.e("FLUTTER_HEALTH::ERROR", "You must use the [writeBloodPressure] API") - null - } - WORKOUT -> { - Log.e("FLUTTER_HEALTH::ERROR", "You must use the [writeWorkoutData] API") - null - } - NUTRITION -> { - Log.e("FLUTTER_HEALTH::ERROR", "You must use the [writeMeal] API") - null - } - else -> { - Log.e( - "FLUTTER_HEALTH::ERROR", - "The type $type was not supported by the Health plugin or you must use another API" - ) - null - } - } - } - - /** - * Creates sleep session records with stage information. Builds SleepSessionRecord with - * appropriate sleep stage data and timing. - * - * @param startTime Sleep period start time in milliseconds - * @param endTime Sleep period end time in milliseconds - * @param stageType Sleep stage type constant - * @param recordingMethod How sleep data was recorded - * @return SleepSessionRecord Configured sleep session record - */ - private fun createSleepRecord( - startTime: Long, - endTime: Long, - stageType: Int, - metadata: Metadata - ): SleepSessionRecord { - return SleepSessionRecord( - startTime = Instant.ofEpochMilli(startTime), - endTime = Instant.ofEpochMilli(endTime), - startZoneOffset = null, - endZoneOffset = null, - stages = - listOf( - SleepSessionRecord.Stage( - Instant.ofEpochMilli(startTime), - Instant.ofEpochMilli(endTime), - stageType - ) - ), - metadata = metadata, - ) - } - - companion object { - // Health data type constants - private const val BODY_FAT_PERCENTAGE = "BODY_FAT_PERCENTAGE" - private const val LEAN_BODY_MASS = "LEAN_BODY_MASS" - private const val HEIGHT = "HEIGHT" - private const val WEIGHT = "WEIGHT" - private const val STEPS = "STEPS" - private const val ACTIVE_ENERGY_BURNED = "ACTIVE_ENERGY_BURNED" - private const val HEART_RATE = "HEART_RATE" - private const val BODY_TEMPERATURE = "BODY_TEMPERATURE" - private const val BODY_WATER_MASS = "BODY_WATER_MASS" - private const val BLOOD_OXYGEN = "BLOOD_OXYGEN" - private const val BLOOD_GLUCOSE = "BLOOD_GLUCOSE" - private const val HEART_RATE_VARIABILITY_RMSSD = "HEART_RATE_VARIABILITY_RMSSD" - private const val DISTANCE_DELTA = "DISTANCE_DELTA" - private const val WATER = "WATER" - private const val RESTING_HEART_RATE = "RESTING_HEART_RATE" - private const val BASAL_ENERGY_BURNED = "BASAL_ENERGY_BURNED" - private const val FLIGHTS_CLIMBED = "FLIGHTS_CLIMBED" - private const val RESPIRATORY_RATE = "RESPIRATORY_RATE" - private const val TOTAL_CALORIES_BURNED = "TOTAL_CALORIES_BURNED" - private const val MENSTRUATION_FLOW = "MENSTRUATION_FLOW" - private const val BLOOD_PRESSURE_SYSTOLIC = "BLOOD_PRESSURE_SYSTOLIC" - private const val BLOOD_PRESSURE_DIASTOLIC = "BLOOD_PRESSURE_DIASTOLIC" - private const val WORKOUT = "WORKOUT" - private const val NUTRITION = "NUTRITION" - private const val SPEED = "SPEED" - - // Recording method mapping expected from Flutter side - private const val RECORDING_METHOD_UNKNOWN = 0 - private const val RECORDING_METHOD_MANUAL_ENTRY = 1 - private const val RECORDING_METHOD_AUTOMATICALLY_RECORDED = 2 - private const val RECORDING_METHOD_ACTIVELY_RECORDED = 3 - - // Sleep types - private const val SLEEP_ASLEEP = "SLEEP_ASLEEP" - private const val SLEEP_LIGHT = "SLEEP_LIGHT" - private const val SLEEP_DEEP = "SLEEP_DEEP" - private const val SLEEP_REM = "SLEEP_REM" - private const val SLEEP_OUT_OF_BED = "SLEEP_OUT_OF_BED" - private const val SLEEP_AWAKE = "SLEEP_AWAKE" - private const val SLEEP_AWAKE_IN_BED = "SLEEP_AWAKE_IN_BED" - private const val SLEEP_UNKNOWN = "SLEEP_UNKNOWN" - private const val SLEEP_SESSION = "SLEEP_SESSION" - } -} diff --git a/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt b/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt deleted file mode 100644 index 888240eee..000000000 --- a/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt +++ /dev/null @@ -1,366 +0,0 @@ -package cachet.plugins.health - -import android.app.Activity -import android.content.Context -import android.content.Intent -import android.os.Handler -import android.util.Log -import androidx.activity.ComponentActivity -import androidx.activity.result.ActivityResultLauncher -import androidx.annotation.NonNull -import androidx.health.connect.client.HealthConnectClient -import androidx.health.connect.client.PermissionController -import androidx.health.connect.client.permission.HealthPermission -import io.flutter.embedding.engine.plugins.FlutterPlugin -import io.flutter.embedding.engine.plugins.activity.ActivityAware -import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding -import io.flutter.plugin.common.MethodCall -import io.flutter.plugin.common.MethodChannel -import io.flutter.plugin.common.MethodChannel.MethodCallHandler -import io.flutter.plugin.common.MethodChannel.Result -import io.flutter.plugin.common.PluginRegistry.ActivityResultListener -import kotlinx.coroutines.* - -/** - * Main Flutter plugin class for Health Connect integration. Manages plugin lifecycle, method - * channel communication, permission handling, and coordinates between Flutter and Android Health - * Connect APIs. - */ -class HealthPlugin(private var channel: MethodChannel? = null) : - MethodCallHandler, ActivityResultListener, Result, ActivityAware, FlutterPlugin { - - private var mResult: Result? = null - private var handler: Handler? = null - private var activity: Activity? = null - private var context: Context? = null - private var healthConnectRequestPermissionsLauncher: ActivityResultLauncher>? = null - private lateinit var healthConnectClient: HealthConnectClient - private lateinit var scope: CoroutineScope - private var isReplySubmitted = false - - // Helper classes - private lateinit var dataReader: HealthDataReader - private lateinit var dataWriter: HealthDataWriter - private lateinit var dataOperations: HealthDataOperations - private lateinit var dataConverter: HealthDataConverter - - // Health Connect availability - private var healthConnectAvailable = false - private var healthConnectStatus = HealthConnectClient.SDK_UNAVAILABLE - - companion object { - const val CHANNEL_NAME = "flutter_health" - } - - /** - * Initializes the plugin when attached to the Flutter engine. Sets up method channel, checks - * Health Connect availability, and initializes helper classes. - * - * @param flutterPluginBinding Plugin binding providing access to Flutter engine resources - */ - override fun onAttachedToEngine( - @NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding - ) { - scope = CoroutineScope(SupervisorJob() + Dispatchers.Main) - channel = MethodChannel(flutterPluginBinding.binaryMessenger, CHANNEL_NAME) - channel?.setMethodCallHandler(this) - context = flutterPluginBinding.applicationContext - handler = Handler(context!!.mainLooper) - - checkAvailability() - if (healthConnectAvailable) { - healthConnectClient = - HealthConnectClient.getOrCreate(flutterPluginBinding.applicationContext) - initializeHelpers() - } - } - - /** - * Cleans up resources when plugin is detached from Flutter engine. Cancels coroutines and - * nullifies references to prevent memory leaks. - * - * @param binding Plugin binding (unused in cleanup) - */ - override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { - channel = null - activity = null - scope.cancel() - } - - override fun success(p0: Any?) { - handler?.post { mResult?.success(p0) } - } - - override fun notImplemented() { - handler?.post { mResult?.notImplemented() } - } - - override fun error( - errorCode: String, - errorMessage: String?, - errorDetails: Any?, - ) { - handler?.post { mResult?.error(errorCode, errorMessage, errorDetails) } - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean { - return false - } - - /** - * Handles method calls from Flutter and routes them to appropriate handler classes. Central - * dispatcher for all Health Connect operations including permissions, data reading, writing, - * and deletion. - * - * @param call Method call from Flutter containing method name and arguments - * @param result Result callback to return data or status to Flutter - */ - override fun onMethodCall(call: MethodCall, result: Result) { - when (call.method) { - // SDK and Installation - "installHealthConnect" -> installHealthConnect(call, result) - "getHealthConnectSdkStatus" -> { - checkAvailability() - if (healthConnectAvailable && !(this::dataOperations.isInitialized)) { - healthConnectClient = HealthConnectClient.getOrCreate(context!!) - initializeHelpers() - } - result.success(healthConnectStatus) - } - - // Permissions - "hasPermissions" -> dataOperations.hasPermissions(call, result) - "requestAuthorization" -> requestAuthorization(call, result) - "revokePermissions" -> dataOperations.revokePermissions(call, result) - - // History permissions - "isHealthDataHistoryAvailable" -> - dataOperations.isHealthDataHistoryAvailable(call, result) - "isHealthDataHistoryAuthorized" -> - dataOperations.isHealthDataHistoryAuthorized(call, result) - "requestHealthDataHistoryAuthorization" -> - requestHealthDataHistoryAuthorization(call, result) - - // Background permissions - "isHealthDataInBackgroundAvailable" -> - dataOperations.isHealthDataInBackgroundAvailable(call, result) - "isHealthDataInBackgroundAuthorized" -> - dataOperations.isHealthDataInBackgroundAuthorized(call, result) - "requestHealthDataInBackgroundAuthorization" -> - requestHealthDataInBackgroundAuthorization(call, result) - - // Reading data - "getData" -> dataReader.getData(call, result) - "getDataByUUID" -> dataReader.getDataByUUID(call, result) - "getIntervalData" -> dataReader.getIntervalData(call, result) - "getAggregateData" -> dataReader.getAggregateData(call, result) - "getTotalStepsInInterval" -> dataReader.getTotalStepsInInterval(call, result) - - // Writing data - "writeData" -> dataWriter.writeData(call, result) - "writeWorkoutData" -> dataWriter.writeWorkoutData(call, result) - "writeBloodPressure" -> dataWriter.writeBloodPressure(call, result) - "writeBloodOxygen" -> dataWriter.writeBloodOxygen(call, result) - "writeMenstruationFlow" -> dataWriter.writeMenstruationFlow(call, result) - "writeMeal" -> dataWriter.writeMeal(call, result) - // TODO: Add support for multiple speed for iOS as well - // "writeMultipleSpeed" -> dataWriter.writeMultipleSpeedData(call, result) - - // Deleting data - "delete" -> dataOperations.deleteData(call, result) - "deleteByUUID" -> dataOperations.deleteByUUID(call, result) - "deleteByClientRecordId" -> dataOperations.deleteByClientRecordId(call, result) - else -> result.notImplemented() - } - } - - /** - * Called when activity is attached to the plugin. Sets up permission request launcher and - * activity result handling. - * - * @param binding Activity plugin binding providing activity context - */ - override fun onAttachedToActivity(binding: ActivityPluginBinding) { - if (channel == null) { - return - } - binding.addActivityResultListener(this) - activity = binding.activity - - val requestPermissionActivityContract = - PermissionController.createRequestPermissionResultContract() - - healthConnectRequestPermissionsLauncher = - (activity as ComponentActivity).registerForActivityResult( - requestPermissionActivityContract - ) { granted -> onHealthConnectPermissionCallback(granted) } - } - - override fun onDetachedFromActivityForConfigChanges() { - onDetachedFromActivity() - } - - override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { - onAttachedToActivity(binding) - } - - /** - * Called when activity is detached from plugin. Cleans up activity-specific resources and - * permission launchers. - */ - override fun onDetachedFromActivity() { - if (channel == null) { - return - } - activity = null - healthConnectRequestPermissionsLauncher = null - } - - /** - * Checks Health Connect availability and SDK status on the current device. Determines if Health - * Connect is installed and accessible. - */ - private fun checkAvailability() { - healthConnectStatus = HealthConnectClient.getSdkStatus(context!!) - healthConnectAvailable = healthConnectStatus == HealthConnectClient.SDK_AVAILABLE - } - - /** - * Initializes helper classes for data operations after Health Connect client is ready. Creates - * instances of reader, writer, operations, and converter classes. - */ - private fun initializeHelpers() { - dataConverter = HealthDataConverter() - dataReader = HealthDataReader(healthConnectClient, scope, context!!, dataConverter) - dataWriter = HealthDataWriter(healthConnectClient, scope) - dataOperations = - HealthDataOperations( - healthConnectClient, - scope, - healthConnectStatus, - healthConnectAvailable - ) - } - - /** - * Launches Health Connect installation flow via Google Play Store. Directs users to install - * Health Connect when it's not available. - * - * @param call Method call from Flutter (unused) - * @param result Flutter result callback - */ - private fun installHealthConnect(call: MethodCall, result: Result) { - val uriString = - "market://details?id=com.google.android.apps.healthdata&url=healthconnect%3A%2F%2Fonboarding" - context!!.startActivity( - Intent(Intent.ACTION_VIEW).apply { - setPackage("com.android.vending") - data = android.net.Uri.parse(uriString) - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - putExtra("overlay", true) - putExtra("callerId", context!!.packageName) - } - ) - result.success(null) - } - - /** - * Handles permission request results from Health Connect permission dialog. Called when user - * responds to permission request, updates Flutter with result. - * - * @param permissionGranted Set of permission strings that were granted - */ - private fun onHealthConnectPermissionCallback(permissionGranted: Set) { - if (!isReplySubmitted) { - if (permissionGranted.isEmpty()) { - mResult?.success(false) - Log.i( - "FLUTTER_HEALTH", - "Health Connect permissions were not granted! Make sure to declare the required permissions in the AndroidManifest.xml file." - ) - } else { - mResult?.success(true) - Log.i( - "FLUTTER_HEALTH", - "${permissionGranted.size} Health Connect permissions were granted!" - ) - Log.i("FLUTTER_HEALTH", "Permissions granted: $permissionGranted") - } - isReplySubmitted = true - } - } - - /** - * Initiates Health Connect permission request flow. Prepares permission list and launches - * system permission dialog. - * - * @param call Method call containing permission types and access levels - * @param result Flutter result callback for permission request outcome - */ - private fun requestAuthorization(call: MethodCall, result: Result) { - if (context == null) { - result.success(false) - return - } - - if (healthConnectRequestPermissionsLauncher == null) { - result.success(false) - Log.i("FLUTTER_HEALTH", "Permission launcher not found") - return - } - - // Store the result to be called in onHealthConnectPermissionCallback - mResult = result - isReplySubmitted = false - - val permList = dataOperations.preparePermissionsList(call) - if (permList == null) { - result.success(false) - return - } - - healthConnectRequestPermissionsLauncher!!.launch(permList.toSet()) - } - - /** - * Requests specific permission for accessing health data history. Launches permission dialog - * for historical data access capability. - * - * @param call Method call from Flutter (unused) - * @param result Flutter result callback for permission request outcome - */ - private fun requestHealthDataHistoryAuthorization(call: MethodCall, result: Result) { - if (context == null || healthConnectRequestPermissionsLauncher == null) { - result.success(false) - Log.i("FLUTTER_HEALTH", "Permission launcher not found") - return - } - - mResult = result - isReplySubmitted = false - healthConnectRequestPermissionsLauncher!!.launch( - setOf(HealthPermission.PERMISSION_READ_HEALTH_DATA_HISTORY) - ) - } - - /** - * Requests specific permission for background health data access. Launches permission dialog - * for background data reading capability. - * - * @param call Method call from Flutter (unused) - * @param result Flutter result callback for permission request outcome - */ - private fun requestHealthDataInBackgroundAuthorization(call: MethodCall, result: Result) { - if (context == null || healthConnectRequestPermissionsLauncher == null) { - result.success(false) - Log.i("FLUTTER_HEALTH", "Permission launcher not found") - return - } - - mResult = result - isReplySubmitted = false - healthConnectRequestPermissionsLauncher!!.launch( - setOf(HealthPermission.PERMISSION_READ_HEALTH_DATA_IN_BACKGROUND) - ) - } -} diff --git a/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthRecordingFilter.kt b/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthRecordingFilter.kt deleted file mode 100644 index c91176dfe..000000000 --- a/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthRecordingFilter.kt +++ /dev/null @@ -1,43 +0,0 @@ -package cachet.plugins.health - -import android.util.Log -import androidx.health.connect.client.records.Record - -/** - * Utility class for filtering health records based on recording methods. - * Provides functionality to exclude records based on how they were recorded - * (manual entry, automatic detection, etc.). - */ -class HealthRecordingFilter { - - /** - * Filters a list of health records by excluding specified recording methods. - * Allows applications to filter out manually entered data, automatic readings, - * or other recording method types based on user preferences. - * - * @param recordingMethodsToFilter List of recording method integers to exclude from results - * @param records List of Health Connect records to filter - * @return List Filtered list containing only records with allowed recording methods - */ - fun filterRecordsByRecordingMethods( - recordingMethodsToFilter: List, - records: List - ): List { - if (recordingMethodsToFilter.isEmpty()) { - return records - } - - return records.filter { record -> - val shouldInclude = !recordingMethodsToFilter.contains(record.metadata.recordingMethod) - - Log.i( - "FLUTTER_HEALTH", - "Filtering record with recording method ${record.metadata.recordingMethod}, " + - "filtering by $recordingMethodsToFilter. " + - "Result: $shouldInclude" - ) - - shouldInclude - } - } -} \ No newline at end of file diff --git a/packages/health/example/.flutter-plugins-dependencies~master b/packages/health/example/.flutter-plugins-dependencies~master deleted file mode 100644 index 921bb9042..000000000 --- a/packages/health/example/.flutter-plugins-dependencies~master +++ /dev/null @@ -1 +0,0 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"device_info","path":"/Users/tnni/.pub-cache/hosted/pub.dartlang.org/device_info-0.4.2+8/","dependencies":[]},{"name":"health","path":"/Users/tnni/GitHub/flutter-plugins/packages/health/","dependencies":["device_info"]}],"android":[{"name":"device_info","path":"/Users/tnni/.pub-cache/hosted/pub.dartlang.org/device_info-0.4.2+8/","dependencies":[]},{"name":"health","path":"/Users/tnni/GitHub/flutter-plugins/packages/health/","dependencies":["device_info"]}],"macos":[],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"device_info","dependencies":[]},{"name":"health","dependencies":["device_info"]}],"date_created":"2020-10-10 23:22:29.930309","version":"1.17.5"} \ No newline at end of file diff --git a/packages/health/example/.gitignore b/packages/health/example/.gitignore deleted file mode 100644 index 79c113f9b..000000000 --- a/packages/health/example/.gitignore +++ /dev/null @@ -1,45 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.build/ -.buildlog/ -.history -.svn/ -.swiftpm/ -migrate_working_dir/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ - -# Flutter/Dart/Pub related -**/doc/api/ -**/ios/Flutter/.last_build_id -.dart_tool/ -.flutter-plugins -.flutter-plugins-dependencies -.pub-cache/ -.pub/ -/build/ - -# Symbolication related -app.*.symbols - -# Obfuscation related -app.*.map.json - -# Android Studio will place build artifacts here -/android/app/debug -/android/app/profile -/android/app/release diff --git a/packages/health/example/.metadata b/packages/health/example/.metadata deleted file mode 100644 index 9a674c613..000000000 --- a/packages/health/example/.metadata +++ /dev/null @@ -1,45 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled and should not be manually edited. - -version: - revision: "35c388afb57ef061d06a39b537336c87e0e3d1b1" - channel: "stable" - -project_type: app - -# Tracks metadata for the flutter migrate command -migration: - platforms: - - platform: root - create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 - base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 - - platform: android - create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 - base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 - - platform: ios - create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 - base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 - - platform: linux - create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 - base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 - - platform: macos - create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 - base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 - - platform: web - create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 - base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 - - platform: windows - create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 - base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 - - # User provided section - - # List of Local paths (relative to this file) that should be - # ignored by the migrate tool. - # - # Files that are not part of the templates will be ignored by default. - unmanaged_files: - - 'lib/main.dart' - - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/packages/health/example/README.md b/packages/health/example/README.md deleted file mode 100644 index 1c4dbcfc1..000000000 --- a/packages/health/example/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# health_example - -Demonstrates how to use the health plugin. - -## Getting Started - -This project is a starting point for a Flutter application. - -A few resources to get you started if this is your first Flutter project: - -- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) - -For help getting started with Flutter, view our -[online documentation](https://flutter.dev/docs), which offers tutorials, -samples, guidance on mobile development, and a full API reference. diff --git a/packages/health/example/analysis_options.yaml b/packages/health/example/analysis_options.yaml deleted file mode 100644 index 0d2902135..000000000 --- a/packages/health/example/analysis_options.yaml +++ /dev/null @@ -1,28 +0,0 @@ -# This file configures the analyzer, which statically analyzes Dart code to -# check for errors, warnings, and lints. -# -# The issues identified by the analyzer are surfaced in the UI of Dart-enabled -# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be -# invoked from the command line by running `flutter analyze`. - -# The following line activates a set of recommended lints for Flutter apps, -# packages, and plugins designed to encourage good coding practices. -include: package:flutter_lints/flutter.yaml - -linter: - # The lint rules applied to this project can be customized in the - # section below to disable rules from the `package:flutter_lints/flutter.yaml` - # included above or to enable additional rules. A list of all available lints - # and their documentation is published at https://dart.dev/lints. - # - # Instead of disabling a lint rule for the entire project in the - # section below, it can also be suppressed for a single line of code - # or a specific dart file by using the `// ignore: name_of_lint` and - # `// ignore_for_file: name_of_lint` syntax on the line or in the file - # producing the lint. - rules: - # avoid_print: false # Uncomment to disable the `avoid_print` rule - # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options diff --git a/packages/health/example/android/.gitignore b/packages/health/example/android/.gitignore deleted file mode 100644 index be3943c96..000000000 --- a/packages/health/example/android/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -gradle-wrapper.jar -/.gradle -/captures/ -/gradlew -/gradlew.bat -/local.properties -GeneratedPluginRegistrant.java -.cxx/ - -# Remember to never publicly share your keystore. -# See https://flutter.dev/to/reference-keystore -key.properties -**/*.keystore -**/*.jks diff --git a/packages/health/example/android/app/build.gradle.kts b/packages/health/example/android/app/build.gradle.kts deleted file mode 100644 index a9130cad1..000000000 --- a/packages/health/example/android/app/build.gradle.kts +++ /dev/null @@ -1,44 +0,0 @@ -plugins { - id("com.android.application") - id("kotlin-android") - // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. - id("dev.flutter.flutter-gradle-plugin") -} - -android { - namespace = "cachet.plugins.health.health_example" - compileSdk = flutter.compileSdkVersion - ndkVersion = flutter.ndkVersion - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } - - kotlinOptions { - jvmTarget = JavaVersion.VERSION_11.toString() - } - - defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId = "cachet.plugins.health.health_example" - // You can update the following values to match your application needs. - // For more information, see: https://flutter.dev/to/review-gradle-config. - minSdk = 26 - targetSdk = flutter.targetSdkVersion - versionCode = flutter.versionCode - versionName = flutter.versionName - } - - buildTypes { - release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig = signingConfigs.getByName("debug") - } - } -} - -flutter { - source = "../.." -} diff --git a/packages/health/example/android/app/src/debug/AndroidManifest.xml b/packages/health/example/android/app/src/debug/AndroidManifest.xml deleted file mode 100644 index 399f6981d..000000000 --- a/packages/health/example/android/app/src/debug/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/packages/health/example/android/app/src/main/AndroidManifest.xml b/packages/health/example/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index 70e81aa1e..000000000 --- a/packages/health/example/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,122 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/health/example/android/app/src/main/kotlin/cachet/plugins/health/health_example/MainActivity.kt b/packages/health/example/android/app/src/main/kotlin/cachet/plugins/health/health_example/MainActivity.kt deleted file mode 100644 index 4e511d0a4..000000000 --- a/packages/health/example/android/app/src/main/kotlin/cachet/plugins/health/health_example/MainActivity.kt +++ /dev/null @@ -1,8 +0,0 @@ -package cachet.plugins.health.health_example - -import android.os.Bundle - -import io.flutter.embedding.android.FlutterFragmentActivity - -class MainActivity: FlutterFragmentActivity() { -} diff --git a/packages/health/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/health/example/android/app/src/main/res/drawable-v21/launch_background.xml deleted file mode 100644 index f74085f3f..000000000 --- a/packages/health/example/android/app/src/main/res/drawable-v21/launch_background.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/packages/health/example/android/app/src/main/res/drawable/launch_background.xml b/packages/health/example/android/app/src/main/res/drawable/launch_background.xml deleted file mode 100644 index 304732f88..000000000 --- a/packages/health/example/android/app/src/main/res/drawable/launch_background.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/packages/health/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/health/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index db77bb4b7b0906d62b1847e87f15cdcacf6a4f29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ diff --git a/packages/health/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/health/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 17987b79bb8a35cc66c3c1fd44f5a5526c1b78be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ diff --git a/packages/health/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/health/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index d5f1c8d34e7a88e3f88bea192c3a370d44689c3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof diff --git a/packages/health/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/health/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 4d6372eebdb28e45604e46eeda8dd24651419bc0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` diff --git a/packages/health/example/android/app/src/main/res/values-night/styles.xml b/packages/health/example/android/app/src/main/res/values-night/styles.xml deleted file mode 100644 index 06952be74..000000000 --- a/packages/health/example/android/app/src/main/res/values-night/styles.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - diff --git a/packages/health/example/android/app/src/main/res/values/styles.xml b/packages/health/example/android/app/src/main/res/values/styles.xml deleted file mode 100644 index cb1ef8805..000000000 --- a/packages/health/example/android/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - diff --git a/packages/health/example/android/app/src/profile/AndroidManifest.xml b/packages/health/example/android/app/src/profile/AndroidManifest.xml deleted file mode 100644 index 399f6981d..000000000 --- a/packages/health/example/android/app/src/profile/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/packages/health/example/android/build.gradle.kts b/packages/health/example/android/build.gradle.kts deleted file mode 100644 index 89176ef44..000000000 --- a/packages/health/example/android/build.gradle.kts +++ /dev/null @@ -1,21 +0,0 @@ -allprojects { - repositories { - google() - mavenCentral() - } -} - -val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() -rootProject.layout.buildDirectory.value(newBuildDir) - -subprojects { - val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) - project.layout.buildDirectory.value(newSubprojectBuildDir) -} -subprojects { - project.evaluationDependsOn(":app") -} - -tasks.register("clean") { - delete(rootProject.layout.buildDirectory) -} diff --git a/packages/health/example/android/gradle.properties b/packages/health/example/android/gradle.properties deleted file mode 100644 index f018a6181..000000000 --- a/packages/health/example/android/gradle.properties +++ /dev/null @@ -1,3 +0,0 @@ -org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError -android.useAndroidX=true -android.enableJetifier=true diff --git a/packages/health/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/health/example/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 02767eb1c..000000000 --- a/packages/health/example/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,5 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip diff --git a/packages/health/example/android/settings.gradle.kts b/packages/health/example/android/settings.gradle.kts deleted file mode 100644 index 43394ed5e..000000000 --- a/packages/health/example/android/settings.gradle.kts +++ /dev/null @@ -1,25 +0,0 @@ -pluginManagement { - val flutterSdkPath = run { - val properties = java.util.Properties() - file("local.properties").inputStream().use { properties.load(it) } - val flutterSdkPath = properties.getProperty("flutter.sdk") - require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } - flutterSdkPath - } - - includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") - - repositories { - google() - mavenCentral() - gradlePluginPortal() - } -} - -plugins { - id("dev.flutter.flutter-plugin-loader") version "1.0.0" - id("com.android.application") version "8.9.1" apply false - id("org.jetbrains.kotlin.android") version "2.1.0" apply false -} - -include(":app") diff --git a/packages/health/example/ios/.gitignore b/packages/health/example/ios/.gitignore deleted file mode 100644 index 7a7f9873a..000000000 --- a/packages/health/example/ios/.gitignore +++ /dev/null @@ -1,34 +0,0 @@ -**/dgph -*.mode1v3 -*.mode2v3 -*.moved-aside -*.pbxuser -*.perspectivev3 -**/*sync/ -.sconsign.dblite -.tags* -**/.vagrant/ -**/DerivedData/ -Icon? -**/Pods/ -**/.symlinks/ -profile -xcuserdata -**/.generated/ -Flutter/App.framework -Flutter/Flutter.framework -Flutter/Flutter.podspec -Flutter/Generated.xcconfig -Flutter/ephemeral/ -Flutter/app.flx -Flutter/app.zip -Flutter/flutter_assets/ -Flutter/flutter_export_environment.sh -ServiceDefinitions.json -Runner/GeneratedPluginRegistrant.* - -# Exceptions to above rules. -!default.mode1v3 -!default.mode2v3 -!default.pbxuser -!default.perspectivev3 diff --git a/packages/health/example/ios/Flutter/AppFrameworkInfo.plist b/packages/health/example/ios/Flutter/AppFrameworkInfo.plist deleted file mode 100644 index 1dc6cf765..000000000 --- a/packages/health/example/ios/Flutter/AppFrameworkInfo.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - App - CFBundleIdentifier - io.flutter.flutter.app - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - App - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1.0 - MinimumOSVersion - 13.0 - - diff --git a/packages/health/example/ios/Flutter/Debug.xcconfig b/packages/health/example/ios/Flutter/Debug.xcconfig deleted file mode 100644 index ec97fc6f3..000000000 --- a/packages/health/example/ios/Flutter/Debug.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" -#include "Generated.xcconfig" diff --git a/packages/health/example/ios/Flutter/Release.xcconfig b/packages/health/example/ios/Flutter/Release.xcconfig deleted file mode 100644 index c4855bfe2..000000000 --- a/packages/health/example/ios/Flutter/Release.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" -#include "Generated.xcconfig" diff --git a/packages/health/example/ios/Podfile b/packages/health/example/ios/Podfile deleted file mode 100644 index c2c1dc543..000000000 --- a/packages/health/example/ios/Podfile +++ /dev/null @@ -1,43 +0,0 @@ -# Uncomment this line to define a global platform for your project -platform :ios, '14.0' - -# CocoaPods analytics sends network stats synchronously affecting flutter build latency. -ENV['COCOAPODS_DISABLE_STATS'] = 'true' - -project 'Runner', { - 'Debug' => :debug, - 'Profile' => :release, - 'Release' => :release, -} - -def flutter_root - generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) - unless File.exist?(generated_xcode_build_settings_path) - raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" - end - - File.foreach(generated_xcode_build_settings_path) do |line| - matches = line.match(/FLUTTER_ROOT\=(.*)/) - return matches[1].strip if matches - end - raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" -end - -require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) - -flutter_ios_podfile_setup - -target 'Runner' do - use_frameworks! - - flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) - target 'RunnerTests' do - inherit! :search_paths - end -end - -post_install do |installer| - installer.pods_project.targets.each do |target| - flutter_additional_ios_build_settings(target) - end -end diff --git a/packages/health/example/ios/Runner.xcodeproj/project.pbxproj b/packages/health/example/ios/Runner.xcodeproj/project.pbxproj deleted file mode 100644 index 0b4f320d0..000000000 --- a/packages/health/example/ios/Runner.xcodeproj/project.pbxproj +++ /dev/null @@ -1,794 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 54; - objects = { - -/* Begin PBXBuildFile section */ - 0E73C03C9CE12BC96B79C15D /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 331C3466792E0AE145779DB5 /* Pods_Runner.framework */; }; - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; - 1538537FCF3E6B2D5EE731AB /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7CF05C63DD5841073CB4E39B /* Pods_RunnerTests.framework */; }; - 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; - 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 */; }; - ABB05D872D6BB16700FA4740 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = ABB05D862D6BB16700FA4740 /* FlutterGeneratedPluginSwiftPackage */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 97C146E61CF9000F007C117D /* Project object */; - proxyType = 1; - remoteGlobalIDString = 97C146ED1CF9000F007C117D; - remoteInfo = Runner; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 9705A1C41CF9048500538489 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 080FC4BE7B2926E42EFB9EF5 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; - 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; - 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 331C3466792E0AE145779DB5 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; - 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 3A090B140475A389FCD6B6D8 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 53E0CC498F9E1686FE7B71EC /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; - 66EFA595EFC367CCF62B5486 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; 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 = ""; }; - 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 7CF05C63DD5841073CB4E39B /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; - 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - ABEE568C2D6B9E4300551983 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; - F97011D03CBC7A35D177D127 /* 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 = ""; }; - FCEA0A19EEB97E6889BA5240 /* 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 = ""; }; - 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 5F1F7F882329D7A1B0D9F8A5 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 1538537FCF3E6B2D5EE731AB /* Pods_RunnerTests.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 97C146EB1CF9000F007C117D /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, - ABB05D872D6BB16700FA4740 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, - 0E73C03C9CE12BC96B79C15D /* Pods_Runner.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 0FF3A715528E9DF113CAF119 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 331C3466792E0AE145779DB5 /* Pods_Runner.framework */, - 7CF05C63DD5841073CB4E39B /* Pods_RunnerTests.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - 331C8082294A63A400263BE5 /* RunnerTests */ = { - isa = PBXGroup; - children = ( - 331C807B294A618700263BE5 /* RunnerTests.swift */, - ); - path = RunnerTests; - sourceTree = ""; - }; - 9740EEB11CF90186004384FC /* Flutter */ = { - isa = PBXGroup; - children = ( - 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 9740EEB21CF90195004384FC /* Debug.xcconfig */, - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, - 9740EEB31CF90195004384FC /* Generated.xcconfig */, - ); - name = Flutter; - sourceTree = ""; - }; - 97C146E51CF9000F007C117D = { - isa = PBXGroup; - children = ( - 9740EEB11CF90186004384FC /* Flutter */, - 97C146F01CF9000F007C117D /* Runner */, - 97C146EF1CF9000F007C117D /* Products */, - 331C8082294A63A400263BE5 /* RunnerTests */, - F5F8E5994A43423CF6FC3663 /* Pods */, - 0FF3A715528E9DF113CAF119 /* Frameworks */, - ); - sourceTree = ""; - }; - 97C146EF1CF9000F007C117D /* Products */ = { - isa = PBXGroup; - children = ( - 97C146EE1CF9000F007C117D /* Runner.app */, - 331C8081294A63A400263BE5 /* RunnerTests.xctest */, - ); - name = Products; - sourceTree = ""; - }; - 97C146F01CF9000F007C117D /* Runner */ = { - isa = PBXGroup; - children = ( - ABEE568C2D6B9E4300551983 /* Runner.entitlements */, - 97C146FA1CF9000F007C117D /* Main.storyboard */, - 97C146FD1CF9000F007C117D /* Assets.xcassets */, - 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, - 97C147021CF9000F007C117D /* Info.plist */, - 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, - 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, - ); - path = Runner; - sourceTree = ""; - }; - F5F8E5994A43423CF6FC3663 /* Pods */ = { - isa = PBXGroup; - children = ( - F97011D03CBC7A35D177D127 /* Pods-Runner.debug.xcconfig */, - FCEA0A19EEB97E6889BA5240 /* Pods-Runner.release.xcconfig */, - 66EFA595EFC367CCF62B5486 /* Pods-Runner.profile.xcconfig */, - 53E0CC498F9E1686FE7B71EC /* Pods-RunnerTests.debug.xcconfig */, - 080FC4BE7B2926E42EFB9EF5 /* Pods-RunnerTests.release.xcconfig */, - 3A090B140475A389FCD6B6D8 /* Pods-RunnerTests.profile.xcconfig */, - ); - path = Pods; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 331C8080294A63A400263BE5 /* RunnerTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; - buildPhases = ( - F39DD7443B254A13543A9E9D /* [CP] Check Pods Manifest.lock */, - 331C807D294A63A400263BE5 /* Sources */, - 331C807F294A63A400263BE5 /* Resources */, - 5F1F7F882329D7A1B0D9F8A5 /* Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - 331C8086294A63A400263BE5 /* PBXTargetDependency */, - ); - name = RunnerTests; - productName = RunnerTests; - productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - 97C146ED1CF9000F007C117D /* Runner */ = { - isa = PBXNativeTarget; - buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; - buildPhases = ( - 53B344462328A747F09FF208 /* [CP] Check Pods Manifest.lock */, - 9740EEB61CF901F6004384FC /* Run Script */, - 97C146EA1CF9000F007C117D /* Sources */, - 97C146EB1CF9000F007C117D /* Frameworks */, - 97C146EC1CF9000F007C117D /* Resources */, - 9705A1C41CF9048500538489 /* Embed Frameworks */, - 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 6FEBDBC7D4FF675303904EA3 /* [CP] Embed Pods Frameworks */, - BC3ECE390F66D6EA0F9FC9C4 /* [CP] Copy Pods Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = Runner; - packageProductDependencies = ( - 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, - ABB05D862D6BB16700FA4740 /* FlutterGeneratedPluginSwiftPackage */, - ); - productName = Runner; - productReference = 97C146EE1CF9000F007C117D /* Runner.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 97C146E61CF9000F007C117D /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 1510; - ORGANIZATIONNAME = ""; - TargetAttributes = { - 331C8080294A63A400263BE5 = { - CreatedOnToolsVersion = 14.0; - TestTargetID = 97C146ED1CF9000F007C117D; - }; - 97C146ED1CF9000F007C117D = { - CreatedOnToolsVersion = 7.3.1; - LastSwiftMigration = 1100; - }; - }; - }; - buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 97C146E51CF9000F007C117D; - packageReferences = ( - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, - ABB05D852D6BB16700FA4740 /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, - ); - productRefGroup = 97C146EF1CF9000F007C117D /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 97C146ED1CF9000F007C117D /* Runner */, - 331C8080294A63A400263BE5 /* RunnerTests */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 331C807F294A63A400263BE5 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 97C146EC1CF9000F007C117D /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", - ); - name = "Thin Binary"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; - }; - 53B344462328A747F09FF208 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 6FEBDBC7D4FF675303904EA3 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - 9740EEB61CF901F6004384FC /* Run Script */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Run Script"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; - }; - BC3ECE390F66D6EA0F9FC9C4 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Copy Pods Resources"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; - }; - F39DD7443B254A13543A9E9D /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 331C807D294A63A400263BE5 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 97C146EA1CF9000F007C117D /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 97C146ED1CF9000F007C117D /* Runner */; - targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin PBXVariantGroup section */ - 97C146FA1CF9000F007C117D /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 97C146FB1CF9000F007C117D /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 97C147001CF9000F007C117D /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 249021D3217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Profile; - }; - 249021D4217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 4A2HNSB52U; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = cachet.plugins.health.example; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Profile; - }; - 331C8088294A63A400263BE5 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 53E0CC498F9E1686FE7B71EC /* Pods-RunnerTests.debug.xcconfig */; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = cachet.plugins.health.healthExample.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = Debug; - }; - 331C8089294A63A400263BE5 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 080FC4BE7B2926E42EFB9EF5 /* Pods-RunnerTests.release.xcconfig */; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = cachet.plugins.health.healthExample.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = Release; - }; - 331C808A294A63A400263BE5 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 3A090B140475A389FCD6B6D8 /* Pods-RunnerTests.profile.xcconfig */; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = cachet.plugins.health.healthExample.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = Profile; - }; - 97C147031CF9000F007C117D /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 97C147041CF9000F007C117D /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 97C147061CF9000F007C117D /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 4A2HNSB52U; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = cachet.plugins.health.example; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Debug; - }; - 97C147071CF9000F007C117D /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 4A2HNSB52U; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = cachet.plugins.health.example; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 331C8088294A63A400263BE5 /* Debug */, - 331C8089294A63A400263BE5 /* Release */, - 331C808A294A63A400263BE5 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 97C147031CF9000F007C117D /* Debug */, - 97C147041CF9000F007C117D /* Release */, - 249021D3217E4FDB00AE95B9 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 97C147061CF9000F007C117D /* Debug */, - 97C147071CF9000F007C117D /* Release */, - 249021D4217E4FDB00AE95B9 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - -/* Begin XCLocalSwiftPackageReference section */ - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { - isa = XCLocalSwiftPackageReference; - relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; - }; - ABB05D852D6BB16700FA4740 /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { - isa = XCLocalSwiftPackageReference; - relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; - }; - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { - isa = XCLocalSwiftPackageReference; - relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; - }; -/* End XCLocalSwiftPackageReference section */ - -/* Begin XCSwiftPackageProductDependency section */ - 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { - isa = XCSwiftPackageProductDependency; - productName = FlutterGeneratedPluginSwiftPackage; - }; - ABB05D862D6BB16700FA4740 /* FlutterGeneratedPluginSwiftPackage */ = { - isa = XCSwiftPackageProductDependency; - productName = FlutterGeneratedPluginSwiftPackage; - }; -/* End XCSwiftPackageProductDependency section */ - }; - rootObject = 97C146E61CF9000F007C117D /* Project object */; -} diff --git a/packages/health/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/health/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a62..000000000 --- a/packages/health/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/packages/health/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/health/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d981003..000000000 --- a/packages/health/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/packages/health/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/health/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index f9b0d7c5e..000000000 --- a/packages/health/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PreviewsEnabled - - - diff --git a/packages/health/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/health/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme deleted file mode 100644 index 23618233e..000000000 --- a/packages/health/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ /dev/null @@ -1,119 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/health/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/health/example/ios/Runner.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 21a3cc14c..000000000 --- a/packages/health/example/ios/Runner.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/packages/health/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/health/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d981003..000000000 --- a/packages/health/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/packages/health/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/health/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index f9b0d7c5e..000000000 --- a/packages/health/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PreviewsEnabled - - - diff --git a/packages/health/example/ios/Runner/AppDelegate.swift b/packages/health/example/ios/Runner/AppDelegate.swift deleted file mode 100644 index 626664468..000000000 --- a/packages/health/example/ios/Runner/AppDelegate.swift +++ /dev/null @@ -1,13 +0,0 @@ -import Flutter -import UIKit - -@main -@objc class AppDelegate: FlutterAppDelegate { - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { - GeneratedPluginRegistrant.register(with: self) - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } -} diff --git a/packages/health/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/health/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index d36b1fab2..000000000 --- a/packages/health/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "images" : [ - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "Icon-App-1024x1024@1x.png", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/packages/health/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/health/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png deleted file mode 100644 index dc9ada4725e9b0ddb1deab583e5b5102493aa332..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10932 zcmeHN2~<R zh`|8`A_PQ1nSu(UMFx?8j8PC!!VDphaL#`F42fd#7Vlc`zIE4n%Y~eiz4y1j|NDpi z?<@|pSJ-HM`qifhf@m%MamgwK83`XpBA<+azdF#2QsT{X@z0A9Bq>~TVErigKH1~P zRX-!h-f0NJ4Mh++{D}J+K>~~rq}d%o%+4dogzXp7RxX4C>Km5XEI|PAFDmo;DFm6G zzjVoB`@qW98Yl0Kvc-9w09^PrsobmG*Eju^=3f?0o-t$U)TL1B3;sZ^!++3&bGZ!o-*6w?;oOhf z=A+Qb$scV5!RbG+&2S}BQ6YH!FKb0``VVX~T$dzzeSZ$&9=X$3)_7Z{SspSYJ!lGE z7yig_41zpQ)%5dr4ff0rh$@ky3-JLRk&DK)NEIHecf9c*?Z1bUB4%pZjQ7hD!A0r-@NF(^WKdr(LXj|=UE7?gBYGgGQV zidf2`ZT@pzXf7}!NH4q(0IMcxsUGDih(0{kRSez&z?CFA0RVXsVFw3^u=^KMtt95q z43q$b*6#uQDLoiCAF_{RFc{!H^moH_cmll#Fc^KXi{9GDl{>%+3qyfOE5;Zq|6#Hb zp^#1G+z^AXfRKaa9HK;%b3Ux~U@q?xg<2DXP%6k!3E)PA<#4$ui8eDy5|9hA5&{?v z(-;*1%(1~-NTQ`Is1_MGdQ{+i*ccd96ab$R$T3=% zw_KuNF@vI!A>>Y_2pl9L{9h1-C6H8<)J4gKI6{WzGBi<@u3P6hNsXG=bRq5c+z;Gc3VUCe;LIIFDmQAGy+=mRyF++u=drBWV8-^>0yE9N&*05XHZpPlE zxu@?8(ZNy7rm?|<+UNe0Vs6&o?l`Pt>P&WaL~M&#Eh%`rg@Mbb)J&@DA-wheQ>hRV z<(XhigZAT z>=M;URcdCaiO3d^?H<^EiEMDV+7HsTiOhoaMX%P65E<(5xMPJKxf!0u>U~uVqnPN7T!X!o@_gs3Ct1 zlZ_$5QXP4{Aj645wG_SNT&6m|O6~Tsl$q?nK*)(`{J4b=(yb^nOATtF1_aS978$x3 zx>Q@s4i3~IT*+l{@dx~Hst21fR*+5}S1@cf>&8*uLw-0^zK(+OpW?cS-YG1QBZ5q! zgTAgivzoF#`cSz&HL>Ti!!v#?36I1*l^mkrx7Y|K6L#n!-~5=d3;K<;Zqi|gpNUn_ z_^GaQDEQ*jfzh;`j&KXb66fWEk1K7vxQIMQ_#Wu_%3 z4Oeb7FJ`8I>Px;^S?)}2+4D_83gHEq>8qSQY0PVP?o)zAv3K~;R$fnwTmI-=ZLK`= zTm+0h*e+Yfr(IlH3i7gUclNH^!MU>id$Jw>O?2i0Cila#v|twub21@e{S2v}8Z13( zNDrTXZVgris|qYm<0NU(tAPouG!QF4ZNpZPkX~{tVf8xY690JqY1NVdiTtW+NqyRP zZ&;T0ikb8V{wxmFhlLTQ&?OP7 z;(z*<+?J2~z*6asSe7h`$8~Se(@t(#%?BGLVs$p``;CyvcT?7Y!{tIPva$LxCQ&4W z6v#F*);|RXvI%qnoOY&i4S*EL&h%hP3O zLsrFZhv&Hu5tF$Lx!8(hs&?!Kx5&L(fdu}UI5d*wn~A`nPUhG&Rv z2#ixiJdhSF-K2tpVL=)5UkXRuPAFrEW}7mW=uAmtVQ&pGE-&az6@#-(Te^n*lrH^m@X-ftVcwO_#7{WI)5v(?>uC9GG{lcGXYJ~Q8q zbMFl7;t+kV;|;KkBW2!P_o%Czhw&Q(nXlxK9ak&6r5t_KH8#1Mr-*0}2h8R9XNkr zto5-b7P_auqTJb(TJlmJ9xreA=6d=d)CVbYP-r4$hDn5|TIhB>SReMfh&OVLkMk-T zYf%$taLF0OqYF?V{+6Xkn>iX@TuqQ?&cN6UjC9YF&%q{Ut3zv{U2)~$>-3;Dp)*(? zg*$mu8^i=-e#acaj*T$pNowo{xiGEk$%DusaQiS!KjJH96XZ-hXv+jk%ard#fu=@Q z$AM)YWvE^{%tDfK%nD49=PI|wYu}lYVbB#a7wtN^Nml@CE@{Gv7+jo{_V?I*jkdLD zJE|jfdrmVbkfS>rN*+`#l%ZUi5_bMS<>=MBDNlpiSb_tAF|Zy`K7kcp@|d?yaTmB^ zo?(vg;B$vxS|SszusORgDg-*Uitzdi{dUV+glA~R8V(?`3GZIl^egW{a919!j#>f` znL1o_^-b`}xnU0+~KIFLQ)$Q6#ym%)(GYC`^XM*{g zv3AM5$+TtDRs%`2TyR^$(hqE7Y1b&`Jd6dS6B#hDVbJlUXcG3y*439D8MrK!2D~6gn>UD4Imctb z+IvAt0iaW73Iq$K?4}H`7wq6YkTMm`tcktXgK0lKPmh=>h+l}Y+pDtvHnG>uqBA)l zAH6BV4F}v$(o$8Gfo*PB>IuaY1*^*`OTx4|hM8jZ?B6HY;F6p4{`OcZZ(us-RVwDx zUzJrCQlp@mz1ZFiSZ*$yX3c_#h9J;yBE$2g%xjmGF4ca z&yL`nGVs!Zxsh^j6i%$a*I3ZD2SoNT`{D%mU=LKaEwbN(_J5%i-6Va?@*>=3(dQy` zOv%$_9lcy9+(t>qohkuU4r_P=R^6ME+wFu&LA9tw9RA?azGhjrVJKy&8=*qZT5Dr8g--d+S8zAyJ$1HlW3Olryt`yE zFIph~Z6oF&o64rw{>lgZISC6p^CBer9C5G6yq%?8tC+)7*d+ib^?fU!JRFxynRLEZ zj;?PwtS}Ao#9whV@KEmwQgM0TVP{hs>dg(1*DiMUOKHdQGIqa0`yZnHk9mtbPfoLx zo;^V6pKUJ!5#n`w2D&381#5#_t}AlTGEgDz$^;u;-vxDN?^#5!zN9ngytY@oTv!nc zp1Xn8uR$1Z;7vY`-<*?DfPHB;x|GUi_fI9@I9SVRv1)qETbNU_8{5U|(>Du84qP#7 z*l9Y$SgA&wGbj>R1YeT9vYjZuC@|{rajTL0f%N@>3$DFU=`lSPl=Iv;EjuGjBa$Gw zHD-;%YOE@<-!7-Mn`0WuO3oWuL6tB2cpPw~Nvuj|KM@))ixuDK`9;jGMe2d)7gHin zS<>k@!x;!TJEc#HdL#RF(`|4W+H88d4V%zlh(7#{q2d0OQX9*FW^`^_<3r$kabWAB z$9BONo5}*(%kx zOXi-yM_cmB3>inPpI~)duvZykJ@^^aWzQ=eQ&STUa}2uT@lV&WoRzkUoE`rR0)`=l zFT%f|LA9fCw>`enm$p7W^E@U7RNBtsh{_-7vVz3DtB*y#*~(L9+x9*wn8VjWw|Q~q zKFsj1Yl>;}%MG3=PY`$g$_mnyhuV&~O~u~)968$0b2!Jkd;2MtAP#ZDYw9hmK_+M$ zb3pxyYC&|CuAbtiG8HZjj?MZJBFbt`ryf+c1dXFuC z0*ZQhBzNBd*}s6K_G}(|Z_9NDV162#y%WSNe|FTDDhx)K!c(mMJh@h87@8(^YdK$&d*^WQe8Z53 z(|@MRJ$Lk-&ii74MPIs80WsOFZ(NX23oR-?As+*aq6b?~62@fSVmM-_*cb1RzZ)`5$agEiL`-E9s7{GM2?(KNPgK1(+c*|-FKoy}X(D_b#etO|YR z(BGZ)0Ntfv-7R4GHoXp?l5g#*={S1{u-QzxCGng*oWr~@X-5f~RA14b8~B+pLKvr4 zfgL|7I>jlak9>D4=(i(cqYf7#318!OSR=^`xxvI!bBlS??`xxWeg?+|>MxaIdH1U~#1tHu zB{QMR?EGRmQ_l4p6YXJ{o(hh-7Tdm>TAX380TZZZyVkqHNzjUn*_|cb?T? zt;d2s-?B#Mc>T-gvBmQZx(y_cfkXZO~{N zT6rP7SD6g~n9QJ)8F*8uHxTLCAZ{l1Y&?6v)BOJZ)=R-pY=Y=&1}jE7fQ>USS}xP#exo57uND0i*rEk@$;nLvRB@u~s^dwRf?G?_enN@$t* zbL%JO=rV(3Ju8#GqUpeE3l_Wu1lN9Y{D4uaUe`g>zlj$1ER$6S6@{m1!~V|bYkhZA z%CvrDRTkHuajMU8;&RZ&itnC~iYLW4DVkP<$}>#&(`UO>!n)Po;Mt(SY8Yb`AS9lt znbX^i?Oe9r_o=?})IHKHoQGKXsps_SE{hwrg?6dMI|^+$CeC&z@*LuF+P`7LfZ*yr+KN8B4{Nzv<`A(wyR@!|gw{zB6Ha ziwPAYh)oJ(nlqSknu(8g9N&1hu0$vFK$W#mp%>X~AU1ay+EKWcFdif{% z#4!4aoVVJ;ULmkQf!ke2}3hqxLK>eq|-d7Ly7-J9zMpT`?dxo6HdfJA|t)?qPEVBDv z{y_b?4^|YA4%WW0VZd8C(ZgQzRI5(I^)=Ub`Y#MHc@nv0w-DaJAqsbEHDWG8Ia6ju zo-iyr*sq((gEwCC&^TYBWt4_@|81?=B-?#P6NMff(*^re zYqvDuO`K@`mjm_Jd;mW_tP`3$cS?R$jR1ZN09$YO%_iBqh5ftzSpMQQtxKFU=FYmP zeY^jph+g<4>YO;U^O>-NFLn~-RqlHvnZl2yd2A{Yc1G@Ga$d+Q&(f^tnPf+Z7serIU};17+2DU_f4Z z@GaPFut27d?!YiD+QP@)T=77cR9~MK@bd~pY%X(h%L={{OIb8IQmf-!xmZkm8A0Ga zQSWONI17_ru5wpHg3jI@i9D+_Y|pCqVuHJNdHUauTD=R$JcD2K_liQisqG$(sm=k9;L* z!L?*4B~ql7uioSX$zWJ?;q-SWXRFhz2Jt4%fOHA=Bwf|RzhwqdXGr78y$J)LR7&3T zE1WWz*>GPWKZ0%|@%6=fyx)5rzUpI;bCj>3RKzNG_1w$fIFCZ&UR0(7S?g}`&Pg$M zf`SLsz8wK82Vyj7;RyKmY{a8G{2BHG%w!^T|Njr!h9TO2LaP^_f22Q1=l$QiU84ao zHe_#{S6;qrC6w~7{y(hs-?-j?lbOfgH^E=XcSgnwW*eEz{_Z<_xN#0001NP)t-s|Ns9~ z#rXRE|M&d=0au&!`~QyF`q}dRnBDt}*!qXo`c{v z{Djr|@Adh0(D_%#_&mM$D6{kE_x{oE{l@J5@%H*?%=t~i_`ufYOPkAEn!pfkr2$fs z652Tz0001XNklqeeKN4RM4i{jKqmiC$?+xN>3Apn^ z0QfuZLym_5b<*QdmkHjHlj811{If)dl(Z2K0A+ekGtrFJb?g|wt#k#pV-#A~bK=OT ts8>{%cPtyC${m|1#B1A6#u!Q;umknL1chzTM$P~L002ovPDHLkV1lTfnu!1a diff --git a/packages/health/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/health/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png deleted file mode 100644 index 797d452e458972bab9d994556c8305db4c827017..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 406 zcmV;H0crk;P))>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c diff --git a/packages/health/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/health/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index 6ed2d933e1120817fe9182483a228007b18ab6ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 450 zcmV;z0X_bSP)iGWQ_5NJQ_~rNh*z)}eT%KUb z`7gNk0#AwF^#0T0?hIa^`~Ck;!}#m+_uT050aTR(J!bU#|IzRL%^UsMS#KsYnTF*!YeDOytlP4VhV?b} z%rz_<=#CPc)tU1MZTq~*2=8~iZ!lSa<{9b@2Jl;?IEV8)=fG217*|@)CCYgFze-x? zIFODUIA>nWKpE+bn~n7;-89sa>#DR>TSlqWk*!2hSN6D~Qb#VqbP~4Fk&m`@1$JGr zXPIdeRE&b2Thd#{MtDK$px*d3-Wx``>!oimf%|A-&-q*6KAH)e$3|6JV%HX{Hig)k suLT-RhftRq8b9;(V=235Wa|I=027H2wCDra;{X5v07*qoM6N<$f;9x^2LJ#7 diff --git a/packages/health/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/health/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png deleted file mode 100644 index 4cd7b0099ca80c806f8fe495613e8d6c69460d76..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 282 zcmV+#0p(^bcu7P-R4C8Q z&e;xxFbF_Vrezo%_kH*OKhshZ6BFpG-Y1e10`QXJKbND7AMQ&cMj60B5TNObaZxYybcN07*qoM6N<$g3m;S%K!iX diff --git a/packages/health/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/health/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index fe730945a01f64a61e2235dbe3f45b08f7729182..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 462 zcmV;<0WtoGP)-}iV`2<;=$?g5M=KQbZ{F&YRNy7Nn@%_*5{gvDM0aKI4?ESmw z{NnZg)A0R`+4?NF_RZexyVB&^^ZvN!{I28tr{Vje;QNTz`dG&Jz0~Ek&f2;*Z7>B|cg}xYpxEFY+0YrKLF;^Q+-HreN0P{&i zK~zY`?b7ECf-n?@;d<&orQ*Q7KoR%4|C>{W^h6@&01>0SKS`dn{Q}GT%Qj_{PLZ_& zs`MFI#j-(>?bvdZ!8^xTwlY{qA)T4QLbY@j(!YJ7aXJervHy6HaG_2SB`6CC{He}f zHVw(fJWApwPq!6VY7r1w-Fs)@ox~N+q|w~e;JI~C4Vf^@d>Wvj=fl`^u9x9wd9 zR%3*Q+)t%S!MU_`id^@&Y{y7-r98lZX0?YrHlfmwb?#}^1b{8g&KzmkE(L>Z&)179 zp<)v6Y}pRl100G2FL_t(o!|l{-Q-VMg#&MKg7c{O0 z2wJImOS3Gy*Z2Qifdv~JYOp;v+U)a|nLoc7hNH;I$;lzDt$}rkaFw1mYK5_0Q(Sut zvbEloxON7$+HSOgC9Z8ltuC&0OSF!-mXv5caV>#bc3@hBPX@I$58-z}(ZZE!t-aOG zpjNkbau@>yEzH(5Yj4kZiMH32XI!4~gVXNnjAvRx;Sdg^`>2DpUEwoMhTs_st8pKG z(%SHyHdU&v%f36~uERh!bd`!T2dw;z6PrOTQ7Vt*#9F2uHlUVnb#ev_o^fh}Dzmq} zWtlk35}k=?xj28uO|5>>$yXadTUE@@IPpgH`gJ~Ro4>jd1IF|(+IX>8M4Ps{PNvmI zNj4D+XgN83gPt_Gm}`Ybv{;+&yu-C(Grdiahmo~BjG-l&mWM+{e5M1sm&=xduwgM9 z`8OEh`=F3r`^E{n_;%9weN{cf2%7=VzC@cYj+lg>+3|D|_1C@{hcU(DyQG_BvBWe? zvTv``=%b1zrol#=R`JB)>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c diff --git a/packages/health/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/health/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index 502f463a9bc882b461c96aadf492d1729e49e725..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 586 zcmV-Q0=4~#P)+}#`wDE{8-2Mebf5<{{PqV{TgVcv*r8?UZ3{-|G?_}T*&y;@cqf{ z{Q*~+qr%%p!1pS*_Uicl#q9lc(D`!D`LN62sNwq{oYw(Wmhk)k<@f$!$@ng~_5)Ru z0Z)trIA5^j{DIW^c+vT2%lW+2<(RtE2wR;4O@)Tm`Xr*?A(qYoM}7i5Yxw>D(&6ou zxz!_Xr~yNF+waPe00049Nkl*;a!v6h%{rlvIH#gW3s8p;bFr=l}mRqpW2h zw=OA%hdyL~z+UHOzl0eKhEr$YYOL-c-%Y<)=j?(bzDweB7{b+%_ypvm_cG{SvM=DK zhv{K@m>#Bw>2W$eUI#iU)Wdgs8Y3U+A$Gd&{+j)d)BmGKx+43U_!tik_YlN)>$7G! zhkE!s;%oku3;IwG3U^2kw?z+HM)jB{@zFhK8P#KMSytSthr+4!c(5c%+^UBn`0X*2 zy3(k600_CSZj?O$Qu%&$;|TGUJrptR(HzyIx>5E(2r{eA(<6t3e3I0B)7d6s7?Z5J zZ!rtKvA{MiEBm&KFtoifx>5P^Z=vl)95XJn()aS5%ad(s?4-=Tkis9IGu{`Fy8r+H07*qoM6N<$f20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU diff --git a/packages/health/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/health/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index 0ec303439225b78712f49115768196d8d76f6790..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 862 zcmV-k1EKthP)20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU diff --git a/packages/health/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/health/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index e9f5fea27c705180eb716271f41b582e76dcbd90..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1674 zcmV;526g#~P){YQnis^a@{&-nmRmq)<&%Mztj67_#M}W?l>kYSliK<%xAp;0j{!}J0!o7b zE>q9${Lb$D&h7k=+4=!ek^n+`0zq>LL1O?lVyea53S5x`Nqqo2YyeuIrQrJj9XjOp z{;T5qbj3}&1vg1VK~#9!?b~^C5-}JC@Pyrv-6dSEqJqT}#j9#dJ@GzT@B8}x zU&J@bBI>f6w6en+CeI)3^kC*U?}X%OD8$Fd$H&LV$H&LV$H&LV#|K5~mLYf|VqzOc zkc7qL~0sOYuM{tG`rYEDV{DWY`Z8&)kW*hc2VkBuY+^Yx&92j&StN}Wp=LD zxoGxXw6f&8sB^u})h@b@z0RBeD`K7RMR9deyL(ZJu#39Z>rT)^>v}Khq8U-IbIvT> z?4pV9qGj=2)TNH3d)=De<+^w;>S7m_eFKTvzeaBeir45xY!^m!FmxnljbSS_3o=g( z->^wC9%qkR{kbGnW8MfFew_o9h3(r55Is`L$8KI@d+*%{=Nx+FXJ98L0PjFIu;rGnnfY zn1R5Qnp<{Jq0M1vX=X&F8gtLmcWv$1*M@4ZfF^9``()#hGTeKeP`1!iED ztNE(TN}M5}3Bbc*d=FIv`DNv&@|C6yYj{sSqUj5oo$#*0$7pu|Dd2TLI>t5%I zIa4Dvr(iayb+5x=j*Vum9&irk)xV1`t509lnPO0%skL8_1c#Xbamh(2@f?4yUI zhhuT5<#8RJhGz4%b$`PJwKPAudsm|at?u;*hGgnA zU1;9gnxVBC)wA(BsB`AW54N{|qmikJR*%x0c`{LGsSfa|NK61pYH(r-UQ4_JXd!Rsz)=k zL{GMc5{h138)fF5CzHEDM>+FqY)$pdN3}Ml+riTgJOLN0F*Vh?{9ESR{SVVg>*>=# zix;VJHPtvFFCRY$Ks*F;VX~%*r9F)W`PmPE9F!(&s#x07n2<}?S{(ygpXgX-&B&OM zONY&BRQ(#%0%jeQs?oJ4P!p*R98>qCy5p8w>_gpuh39NcOlp)(wOoz0sY-Qz55eB~ z7OC-fKBaD1sE3$l-6QgBJO!n?QOTza`!S_YK z_v-lm^7{VO^8Q@M_^8F)09Ki6%=s?2_5eupee(w1FB%aqSweusQ-T+CH0Xt{` zFjMvW{@C&TB)k25()nh~_yJ9coBRL(0oO@HK~z}7?bm5j;y@69;bvlHb2tf!$ReA~x{22wTq550 z?f?Hnw(;m3ip30;QzdV~7pi!wyMYhDtXW#cO7T>|f=bdFhu+F!zMZ2UFj;GUKX7tI z;hv3{q~!*pMj75WP_c}>6)IWvg5_yyg<9Op()eD1hWC19M@?_9_MHec{Z8n3FaF{8 z;u`Mw0ly(uE>*CgQYv{be6ab2LWhlaH1^iLIM{olnag$78^Fd}%dR7;JECQ+hmk|o z!u2&!3MqPfP5ChDSkFSH8F2WVOEf0(E_M(JL17G}Y+fg0_IuW%WQ zG(mG&u?|->YSdk0;8rc{yw2@2Z&GA}z{Wb91Ooz9VhA{b2DYE7RmG zjL}?eq#iX%3#k;JWMx_{^2nNax`xPhByFiDX+a7uTGU|otOvIAUy|dEKkXOm-`aWS z27pUzD{a)Ct<6p{{3)+lq@i`t@%>-wT4r?*S}k)58e09WZYP0{{R3FC5Sl00039P)t-s|Ns9~ z#rP?<_5oL$Q^olD{r_0T`27C={r>*`|Nj71npVa5OTzc(_WfbW_({R{p56NV{r*M2 z_xt?)2V0#0NsfV0u>{42ctGP(8vQj-Btk1n|O0ZD=YLwd&R{Ko41Gr9H= zY@z@@bOAMB5Ltl$E>bJJ{>JP30ZxkmI%?eW{k`b?Wy<&gOo;dS`~CR$Vwb@XWtR|N zi~t=w02?-0&j0TD{>bb6sNwsK*!p?V`RMQUl(*DVjk-9Cx+-z1KXab|Ka2oXhX5f% z`$|e!000AhNklrxs)5QTeTVRiEmz~MKK1WAjCw(c-JK6eox;2O)?`? zTG`AHia671e^vgmp!llKp|=5sVHk#C7=~epA~VAf-~%aPC=%Qw01h8mnSZ|p?hz91 z7p83F3%LVu9;S$tSI$C^%^yud1dfTM_6p2|+5Ejp$bd`GDvbR|xit>i!ZD&F>@CJrPmu*UjD&?DfZs=$@e3FQA(vNiU+$A*%a} z?`XcG2jDxJ_ZQ#Md`H{4Lpf6QBDp81_KWZ6Tk#yCy1)32zO#3<7>b`eT7UyYH1eGz z;O(rH$=QR*L%%ZcBpc=eGua?N55nD^K(8<#gl2+pN_j~b2MHs4#mcLmv%DkspS-3< zpI1F=^9siI0s-;IN_IrA;5xm~3?3!StX}pUv0vkxMaqm+zxrg7X7(I&*N~&dEd0kD z-FRV|g=|QuUsuh>-xCI}vD2imzYIOIdcCVV=$Bz@*u0+Bs<|L^)32nN*=wu3n%Ynw z@1|eLG>!8ruU1pFXUfb`j>(=Gy~?Rn4QJ-c3%3T|(Frd!bI`9u&zAnyFYTqlG#&J7 zAkD(jpw|oZLNiA>;>hgp1KX7-wxC~31II47gc zHcehD6Uxlf%+M^^uN5Wc*G%^;>D5qT{>=uxUhX%WJu^Z*(_Wq9y}npFO{Hhb>s6<9 zNi0pHXWFaVZnb)1+RS&F)xOv6&aeILcI)`k#0YE+?e)5&#r7J#c`3Z7x!LpTc01dx zrdC3{Z;joZ^KN&))zB_i)I9fWedoN>Zl-6_Iz+^G&*ak2jpF07*qoM6N<$f;w%0(f|Me diff --git a/packages/health/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/health/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index 0467bf12aa4d28f374bb26596605a46dcbb3e7c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1418 zcmV;51$Fv~P)q zKfU)WzW*n(@|xWGCA9ScMt*e9`2kdxPQ&&>|-UCa7_51w+ zLUsW@ZzZSW0y$)Hp~e9%PvP|a03ks1`~K?q{u;6NC8*{AOqIUq{CL&;p56Lf$oQGq z^={4hPQv)y=I|4n+?>7Fim=dxt1 z2H+Dm+1+fh+IF>G0SjJMkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJT zkdTm&kdTm&kdTm&kdP`esgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>Tmf` zm1{eLgw!b{bXkjWbF%dTkTZEJWyWOb##Lfw4EK2}<0d6%>AGS{po>WCOy&f$Tay_> z?NBlkpo@s-O;0V%Y_Xa-G#_O08q5LR*~F%&)}{}r&L%Sbs8AS4t7Y0NEx*{soY=0MZExqA5XHQkqi#4gW3 zqODM^iyZl;dvf)-bOXtOru(s)Uc7~BFx{w-FK;2{`VA?(g&@3z&bfLFyctOH!cVsF z7IL=fo-qBndRUm;kAdXR4e6>k-z|21AaN%ubeVrHl*<|s&Ax@W-t?LR(P-24A5=>a z*R9#QvjzF8n%@1Nw@?CG@6(%>+-0ASK~jEmCV|&a*7-GKT72W<(TbSjf)&Eme6nGE z>Gkj4Sq&2e+-G%|+NM8OOm5zVl9{Z8Dd8A5z3y8mZ=4Bv4%>as_{9cN#bm~;h>62( zdqY93Zy}v&c4n($Vv!UybR8ocs7#zbfX1IY-*w~)p}XyZ-SFC~4w>BvMVr`dFbelV{lLL0bx7@*ZZdebr3`sP;? zVImji)kG)(6Juv0lz@q`F!k1FE;CQ(D0iG$wchPbKZQELlsZ#~rt8#90Y_Xh&3U-< z{s<&cCV_1`^TD^ia9!*mQDq& zn2{r`j};V|uV%_wsP!zB?m%;FeaRe+X47K0e+KE!8C{gAWF8)lCd1u1%~|M!XNRvw zvtqy3iz0WSpWdhn6$hP8PaRBmp)q`#PCA`Vd#Tc$@f1tAcM>f_I@bC)hkI9|o(Iqv zo}Piadq!j76}004RBio<`)70k^`K1NK)q>w?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF z34$0Z;QO!JOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUC YUoZo%k(ykuW&i*H07*qoM6N<$f+CH{y8r+H diff --git a/packages/health/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/health/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json deleted file mode 100644 index 0bedcf2fd..000000000 --- a/packages/health/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "LaunchImage.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "LaunchImage@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "LaunchImage@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/packages/health/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/health/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png deleted file mode 100644 index 9da19eacad3b03bb08bbddbbf4ac48dd78b3d838..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v diff --git a/packages/health/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/health/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png deleted file mode 100644 index 9da19eacad3b03bb08bbddbbf4ac48dd78b3d838..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v diff --git a/packages/health/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/health/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png deleted file mode 100644 index 9da19eacad3b03bb08bbddbbf4ac48dd78b3d838..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v diff --git a/packages/health/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/health/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md deleted file mode 100644 index 89c2725b7..000000000 --- a/packages/health/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Launch Screen Assets - -You can customize the launch screen with your own desired assets by replacing the image files in this directory. - -You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/packages/health/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/health/example/ios/Runner/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index f2e259c7c..000000000 --- a/packages/health/example/ios/Runner/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/health/example/ios/Runner/Base.lproj/Main.storyboard b/packages/health/example/ios/Runner/Base.lproj/Main.storyboard deleted file mode 100644 index f3c28516f..000000000 --- a/packages/health/example/ios/Runner/Base.lproj/Main.storyboard +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/health/example/ios/Runner/Info.plist b/packages/health/example/ios/Runner/Info.plist deleted file mode 100644 index 59c5529bb..000000000 --- a/packages/health/example/ios/Runner/Info.plist +++ /dev/null @@ -1,53 +0,0 @@ - - - - - CADisableMinimumFrameDurationOnPhone - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - Health Example - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - health_example - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleSignature - ???? - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSRequiresIPhoneOS - - UIApplicationSupportsIndirectInputEvents - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - NSHealthShareUsageDescription - We will sync your data with the Apple Health app to give you better insights - NSHealthUpdateUsageDescription - We will sync your data with the Apple Health app to give you better insights - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - - diff --git a/packages/health/example/ios/Runner/Runner-Bridging-Header.h b/packages/health/example/ios/Runner/Runner-Bridging-Header.h deleted file mode 100644 index 308a2a560..000000000 --- a/packages/health/example/ios/Runner/Runner-Bridging-Header.h +++ /dev/null @@ -1 +0,0 @@ -#import "GeneratedPluginRegistrant.h" diff --git a/packages/health/example/ios/Runner/Runner.entitlements b/packages/health/example/ios/Runner/Runner.entitlements deleted file mode 100644 index e10f4302d..000000000 --- a/packages/health/example/ios/Runner/Runner.entitlements +++ /dev/null @@ -1,8 +0,0 @@ - - - - - com.apple.developer.healthkit - - - diff --git a/packages/health/example/ios/RunnerTests/RunnerTests.swift b/packages/health/example/ios/RunnerTests/RunnerTests.swift deleted file mode 100644 index 86a7c3b1b..000000000 --- a/packages/health/example/ios/RunnerTests/RunnerTests.swift +++ /dev/null @@ -1,12 +0,0 @@ -import Flutter -import UIKit -import XCTest - -class RunnerTests: XCTestCase { - - func testExample() { - // If you add code to the Runner application, consider adding tests here. - // See https://developer.apple.com/documentation/xctest for more information about using XCTest. - } - -} diff --git a/packages/health/example/lib/main.dart b/packages/health/example/lib/main.dart deleted file mode 100644 index ffaeec3bf..000000000 --- a/packages/health/example/lib/main.dart +++ /dev/null @@ -1,1003 +0,0 @@ -import 'dart:async'; -import 'dart:io'; - -import 'package:flutter/material.dart'; -import 'package:health/health.dart'; -import 'package:health_example/util.dart'; -import 'package:permission_handler/permission_handler.dart'; -import 'package:carp_serializable/carp_serializable.dart'; - -// Global Health instance -final health = Health(); - -void main() => runApp(HealthApp()); - -class HealthApp extends StatefulWidget { - const HealthApp({super.key}); - - @override - HealthAppState createState() => HealthAppState(); -} - -enum AppState { - DATA_NOT_FETCHED, - FETCHING_DATA, - DATA_READY, - NO_DATA, - AUTHORIZED, - AUTH_NOT_GRANTED, - DATA_ADDED, - DATA_DELETED, - DATA_NOT_ADDED, - DATA_NOT_DELETED, - STEPS_READY, - HEALTH_CONNECT_STATUS, - PERMISSIONS_REVOKING, - PERMISSIONS_REVOKED, - PERMISSIONS_NOT_REVOKED, -} - -class HealthAppState extends State { - List _healthDataList = []; - AppState _state = AppState.DATA_NOT_FETCHED; - int _nofSteps = 0; - List recordingMethodsToFilter = []; - - // All types available depending on platform (iOS ot Android). - List get types => (Platform.isAndroid) - ? dataTypesAndroid - : (Platform.isIOS) - ? dataTypesIOS - : []; - - // // Or specify specific types - // static final types = [ - // HealthDataType.WEIGHT, - // HealthDataType.STEPS, - // HealthDataType.HEIGHT, - // HealthDataType.BLOOD_GLUCOSE, - // HealthDataType.WORKOUT, - // HealthDataType.BLOOD_PRESSURE_DIASTOLIC, - // HealthDataType.BLOOD_PRESSURE_SYSTOLIC, - // // Uncomment this line on iOS - only available on iOS - // // HealthDataType.AUDIOGRAM - // ]; - - // Set up corresponding permissions - - // READ only - // List get permissions => - // types.map((e) => HealthDataAccess.READ).toList(); - - // Or both READ and WRITE - List get permissions => types - .map((type) => - // can only request READ permissions to the following list of types on iOS - [ - HealthDataType.GENDER, - HealthDataType.BLOOD_TYPE, - HealthDataType.BIRTH_DATE, - HealthDataType.APPLE_MOVE_TIME, - HealthDataType.APPLE_STAND_HOUR, - HealthDataType.APPLE_STAND_TIME, - HealthDataType.WALKING_HEART_RATE, - HealthDataType.ELECTROCARDIOGRAM, - HealthDataType.HIGH_HEART_RATE_EVENT, - HealthDataType.LOW_HEART_RATE_EVENT, - HealthDataType.IRREGULAR_HEART_RATE_EVENT, - HealthDataType.EXERCISE_TIME, - ].contains(type) - ? HealthDataAccess.READ - : HealthDataAccess.READ_WRITE) - .toList(); - - @override - void initState() { - // configure the health plugin before use and check the Health Connect status - health.configure(); - health.getHealthConnectSdkStatus(); - - super.initState(); - } - - /// Install Google Health Connect on this phone. - Future installHealthConnect() async => - await health.installHealthConnect(); - - /// Authorize, i.e. get permissions to access relevant health data. - Future authorize() async { - // If we are trying to read Step Count, Workout, Sleep or other data that requires - // the ACTIVITY_RECOGNITION permission, we need to request the permission first. - // This requires a special request authorization call. - // - // The location permission is requested for Workouts using the Distance information. - await Permission.activityRecognition.request(); - await Permission.location.request(); - - // Check if we have health permissions - bool? hasPermissions = - await health.hasPermissions(types, permissions: permissions); - - // hasPermissions = false because the hasPermission cannot disclose if WRITE access exists. - // Hence, we have to request with WRITE as well. - hasPermissions = false; - - bool authorized = false; - if (!hasPermissions) { - // requesting access to the data types before reading them - try { - authorized = - await health.requestAuthorization(types, permissions: permissions); - - // request access to read historic data - await health.requestHealthDataHistoryAuthorization(); - - // request access in background - await health.requestHealthDataInBackgroundAuthorization(); - } catch (error) { - debugPrint("Exception in authorize: $error"); - } - } - - setState(() => _state = - (authorized) ? AppState.AUTHORIZED : AppState.AUTH_NOT_GRANTED); - } - - /// Gets the Health Connect status on Android. - Future getHealthConnectSdkStatus() async { - assert(Platform.isAndroid, "This is only available on Android"); - - final status = await health.getHealthConnectSdkStatus(); - - setState(() { - _contentHealthConnectStatus = - Text('Health Connect Status: ${status?.name.toUpperCase()}'); - _state = AppState.HEALTH_CONNECT_STATUS; - }); - } - - /// Fetch data points from the health plugin and show them in the app. - Future fetchData() async { - setState(() => _state = AppState.FETCHING_DATA); - - // get data within the last 24 hours - final now = DateTime.now(); - final yesterday = now.subtract(const Duration(hours: 24)); - - // Clear old data points - _healthDataList.clear(); - - try { - // fetch health data - List healthData = await health.getHealthDataFromTypes( - types: types, - startTime: yesterday, - endTime: now, - recordingMethodsToFilter: recordingMethodsToFilter, - ); - - debugPrint('Total number of data points: ${healthData.length}. ' - '${healthData.length > 100 ? 'Only showing the first 100.' : ''}'); - - // sort the data points by date - healthData.sort((a, b) => b.dateTo.compareTo(a.dateTo)); - - // save all the new data points (only the first 100) - _healthDataList.addAll( - (healthData.length < 100) ? healthData : healthData.sublist(0, 100)); - } catch (error) { - debugPrint("Exception in getHealthDataFromTypes: $error"); - } - - // filter out duplicates - _healthDataList = health.removeDuplicates(_healthDataList); - - for (var data in _healthDataList) { - debugPrint(data.toJson().toString()); - } - - // update the UI to display the results - setState(() { - _state = _healthDataList.isEmpty ? AppState.NO_DATA : AppState.DATA_READY; - }); - } - - /// Fetch single data point by UUID and type. - Future fetchDataByUUID( - BuildContext context, { - required String uuid, - required HealthDataType type, - }) async { - try { - // fetch health data - HealthDataPoint? healthPoint = await health.getHealthDataByUUID( - uuid: uuid, - type: type, - ); - - if (healthPoint != null) { - // save all the new data points (only the first 100) - if (context.mounted) openDetailBottomSheet(context, healthPoint); - } - } catch (error) { - debugPrint("Exception in getHealthDataByUUID: $error"); - } - } - - /// Add some random health data. - /// Note that you should ensure that you have permissions to add the - /// following data types. - Future addData() async { - final now = DateTime.now(); - final earlier = now.subtract(const Duration(minutes: 20)); - - // Add data for supported types - // NOTE: These are only the ones supported on Androids new API Health Connect. - // Both Android's Health Connect and iOS' HealthKit have more types that we support in the enum list [HealthDataType] - // Add more - like AUDIOGRAM, HEADACHE_SEVERE etc. to try them. - bool success = true; - - // misc. health data examples using the writeHealthData() method - success &= await health.writeHealthData( - value: 1.925, - type: HealthDataType.HEIGHT, - startTime: earlier, - endTime: now, - recordingMethod: RecordingMethod.manual); - success &= await health.writeHealthData( - value: 90, - type: HealthDataType.WEIGHT, - startTime: now, - recordingMethod: RecordingMethod.manual); - success &= await health.writeHealthData( - value: 90, - type: HealthDataType.HEART_RATE, - startTime: earlier, - endTime: now, - recordingMethod: RecordingMethod.manual); - success &= await health.writeHealthData( - value: 90, - type: HealthDataType.STEPS, - startTime: earlier, - endTime: now, - recordingMethod: RecordingMethod.manual); - success &= await health.writeHealthData( - value: 200, - type: HealthDataType.ACTIVE_ENERGY_BURNED, - startTime: earlier, - endTime: now, - clientRecordId: "uniqueID1234", - clientRecordVersion: 1); - success &= await health.writeHealthData( - value: 70, - type: HealthDataType.HEART_RATE, - startTime: earlier, - endTime: now); - success &= await health.writeHealthData( - value: 37, - type: HealthDataType.BODY_TEMPERATURE, - startTime: earlier, - endTime: now); - success &= await health.writeHealthData( - value: 105, - type: HealthDataType.BLOOD_GLUCOSE, - startTime: earlier, - endTime: now); - success &= await health.writeHealthData( - value: 1.8, - type: HealthDataType.WATER, - startTime: earlier, - endTime: now); - - // different types of sleep - success &= await health.writeHealthData( - value: 0.0, - type: HealthDataType.SLEEP_REM, - startTime: earlier, - endTime: now); - success &= await health.writeHealthData( - value: 0.0, - type: HealthDataType.SLEEP_ASLEEP, - startTime: earlier, - endTime: now); - success &= await health.writeHealthData( - value: 0.0, - type: HealthDataType.SLEEP_AWAKE, - startTime: earlier, - endTime: now); - success &= await health.writeHealthData( - value: 0.0, - type: HealthDataType.SLEEP_DEEP, - startTime: earlier, - endTime: now); - success &= await health.writeHealthData( - value: 22, - type: HealthDataType.LEAN_BODY_MASS, - startTime: earlier, - endTime: now); - - // specialized write methods - success &= await health.writeBloodOxygen( - saturation: 98, - startTime: earlier, - endTime: now, - ); - success &= await health.writeWorkoutData( - activityType: HealthWorkoutActivityType.AMERICAN_FOOTBALL, - title: "Random workout name that shows up in Health Connect", - start: now.subtract(const Duration(minutes: 15)), - end: now, - totalDistance: 2430, - totalEnergyBurned: 400, - ); - success &= await health.writeBloodPressure( - systolic: 90, - diastolic: 80, - startTime: now, - clientRecordId: "uniqueID1234", - clientRecordVersion: 2); - success &= await health.writeMeal( - mealType: MealType.SNACK, - clientRecordId: "uniqueID1234", - clientRecordVersion: 1.4, - startTime: earlier, - endTime: now, - caloriesConsumed: 1000, - carbohydrates: 50, - protein: 25, - fatTotal: 50, - name: "Banana", - caffeine: 0.002, - vitaminA: 0.001, - vitaminC: 0.002, - vitaminD: 0.003, - vitaminE: 0.004, - vitaminK: 0.005, - b1Thiamin: 0.006, - b2Riboflavin: 0.007, - b3Niacin: 0.008, - b5PantothenicAcid: 0.009, - b6Pyridoxine: 0.010, - b7Biotin: 0.011, - b9Folate: 0.012, - b12Cobalamin: 0.013, - calcium: 0.015, - copper: 0.016, - iodine: 0.017, - iron: 0.018, - magnesium: 0.019, - manganese: 0.020, - phosphorus: 0.021, - potassium: 0.022, - selenium: 0.023, - sodium: 0.024, - zinc: 0.025, - water: 0.026, - molybdenum: 0.027, - chloride: 0.028, - chromium: 0.029, - cholesterol: 0.030, - fiber: 0.031, - fatMonounsaturated: 0.032, - fatPolyunsaturated: 0.033, - fatUnsaturated: 0.065, - fatTransMonoenoic: 0.65, - fatSaturated: 066, - sugar: 0.067, - recordingMethod: RecordingMethod.manual); - - // Store an Audiogram - only available on iOS - // const frequencies = [125.0, 500.0, 1000.0, 2000.0, 4000.0, 8000.0]; - // const leftEarSensitivities = [49.0, 54.0, 89.0, 52.0, 77.0, 35.0]; - // const rightEarSensitivities = [76.0, 66.0, 90.0, 22.0, 85.0, 44.5]; - // success &= await health.writeAudiogram( - // frequencies, - // leftEarSensitivities, - // rightEarSensitivities, - // now, - // now, - // metadata: { - // "HKExternalUUID": "uniqueID", - // "HKDeviceName": "bluetooth headphone", - // }, - // ); - - success &= await health.writeMenstruationFlow( - flow: MenstrualFlow.medium, - isStartOfCycle: true, - startTime: earlier, - endTime: now, - ); - - if (Platform.isIOS) { - success &= await health.writeInsulinDelivery( - 5, InsulinDeliveryReason.BOLUS, earlier, now); - success &= await health.writeHealthData( - value: 30, - type: HealthDataType.HEART_RATE_VARIABILITY_SDNN, - startTime: earlier, - endTime: now); - success &= await health.writeHealthData( - value: 1.5, // 1.5 m/s (typical walking speed) - type: HealthDataType.WALKING_SPEED, - startTime: earlier, - endTime: now, - recordingMethod: RecordingMethod.manual); - } else { - success &= await health.writeHealthData( - value: 2.0, // 2.0 m/s (typical jogging speed) - type: HealthDataType.SPEED, - startTime: earlier, - endTime: now, - recordingMethod: RecordingMethod.manual); - success &= await health.writeHealthData( - value: 30, - type: HealthDataType.HEART_RATE_VARIABILITY_RMSSD, - startTime: earlier, - endTime: now); - - // Mindfulness value should be counted based on start and end time - success &= await health.writeHealthData( - value: 10, - type: HealthDataType.MINDFULNESS, - startTime: earlier, - endTime: now, - recordingMethod: RecordingMethod.automatic, - ); - } - - // Available on iOS or iOS 16.0+ only - if (Platform.isIOS) { - success &= await health.writeHealthData( - value: 22, - type: HealthDataType.WATER_TEMPERATURE, - startTime: earlier, - endTime: now, - recordingMethod: RecordingMethod.manual); - - success &= await health.writeHealthData( - value: 55, - type: HealthDataType.UNDERWATER_DEPTH, - startTime: earlier, - endTime: now, - recordingMethod: RecordingMethod.manual); - success &= await health.writeHealthData( - value: 4.3, - type: HealthDataType.UV_INDEX, - startTime: earlier, - endTime: now, - recordingMethod: RecordingMethod.manual); - } - - setState(() { - _state = success ? AppState.DATA_ADDED : AppState.DATA_NOT_ADDED; - }); - } - - /// Delete some random health data. - Future deleteData() async { - final now = DateTime.now(); - final earlier = now.subtract(const Duration(hours: 24)); - - bool success = true; - for (HealthDataType type in types) { - success &= await health.delete( - type: type, - startTime: earlier, - endTime: now, - ); - } - - // To delete a record by UUID - call the `health.deleteByUUID` method: - /** - List healthData = await health.getHealthDataFromTypes( - types: [HealthDataType.STEPS], - startTime: startDate, - endTime: endDate, - ); - - if (healthData.isNotEmpty) { - print("DELETING: ${healthData.first.toJson()}"); - String uuid = healthData.first.uuid; - - success &= await health.deleteByUUID( - type: HealthDataType.STEPS, - uuid: uuid, - ); - - } - */ - - setState(() { - _state = success ? AppState.DATA_DELETED : AppState.DATA_NOT_DELETED; - }); - } - - /// Fetch steps from the health plugin and show them in the app. - Future fetchStepData() async { - int? steps; - - // get steps for today (i.e., since midnight) - final now = DateTime.now(); - final midnight = DateTime(now.year, now.month, now.day); - - bool stepsPermission = - await health.hasPermissions([HealthDataType.STEPS]) ?? false; - if (!stepsPermission) { - stepsPermission = - await health.requestAuthorization([HealthDataType.STEPS]); - } - - if (stepsPermission) { - try { - steps = await health.getTotalStepsInInterval(midnight, now, - includeManualEntry: - !recordingMethodsToFilter.contains(RecordingMethod.manual)); - } catch (error) { - debugPrint("Exception in getTotalStepsInInterval: $error"); - } - - debugPrint('Total number of steps: $steps'); - - setState(() { - _nofSteps = (steps == null) ? 0 : steps; - _state = (steps == null) ? AppState.NO_DATA : AppState.STEPS_READY; - }); - } else { - debugPrint("Authorization not granted - error in authorization"); - setState(() => _state = AppState.DATA_NOT_FETCHED); - } - } - - /// Revoke access to health data. Note, this only has an effect on Android. - Future revokeAccess() async { - setState(() => _state = AppState.PERMISSIONS_REVOKING); - - bool success = false; - - try { - await health.revokePermissions(); - success = true; - } catch (error) { - debugPrint("Exception in revokeAccess: $error"); - } - - setState(() { - _state = success - ? AppState.PERMISSIONS_REVOKED - : AppState.PERMISSIONS_NOT_REVOKED; - }); - } - - Future getIntervalBasedData() async { - final startDate = DateTime.now().subtract(const Duration(days: 7)); - final endDate = DateTime.now(); - - List healthDataResponse = - await health.getHealthIntervalDataFromTypes( - startDate: startDate, - endDate: endDate, - types: [HealthDataType.BLOOD_OXYGEN, HealthDataType.STEPS], - interval: 86400, // 86400 seconds = 1 day - // recordingMethodsToFilter: recordingMethodsToFilter, - ); - debugPrint( - 'Total number of interval data points: ${healthDataResponse.length}. ' - '${healthDataResponse.length > 100 ? 'Only showing the first 100.' : ''}'); - - debugPrint("Interval data points: "); - for (var data in healthDataResponse) { - debugPrint(toJsonString(data)); - } - healthDataResponse.sort((a, b) => b.dateTo.compareTo(a.dateTo)); - - _healthDataList.clear(); - _healthDataList.addAll((healthDataResponse.length < 100) - ? healthDataResponse - : healthDataResponse.sublist(0, 100)); - - for (var data in _healthDataList) { - debugPrint(toJsonString(data)); - } - - setState(() { - _state = _healthDataList.isEmpty ? AppState.NO_DATA : AppState.DATA_READY; - }); - } - - /// Display bottom sheet dialog of selected HealthDataPoint - void openDetailBottomSheet( - BuildContext context, - HealthDataPoint? healthPoint, - ) { - showModalBottomSheet( - context: context, - isScrollControlled: true, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical(top: Radius.circular(16)), - ), - builder: (BuildContext context) => _detailedBottomSheet( - healthPoint: healthPoint, - ), - ); - } - - // UI building below - - @override - Widget build(BuildContext context) { - return MaterialApp( - home: Scaffold( - appBar: AppBar( - title: const Text('Health Example'), - ), - body: Column( - children: [ - Wrap( - spacing: 10, - children: [ - if (Platform.isAndroid) - TextButton( - onPressed: getHealthConnectSdkStatus, - style: const ButtonStyle( - backgroundColor: WidgetStatePropertyAll(Colors.blue)), - child: const Text("Check Health Connect Status", - style: TextStyle(color: Colors.white))), - if (Platform.isAndroid && - health.healthConnectSdkStatus != - HealthConnectSdkStatus.sdkAvailable) - TextButton( - onPressed: installHealthConnect, - style: const ButtonStyle( - backgroundColor: WidgetStatePropertyAll(Colors.blue)), - child: const Text("Install Health Connect", - style: TextStyle(color: Colors.white))), - if (Platform.isIOS || - Platform.isAndroid && - health.healthConnectSdkStatus == - HealthConnectSdkStatus.sdkAvailable) - Wrap(spacing: 10, children: [ - TextButton( - onPressed: authorize, - style: const ButtonStyle( - backgroundColor: - WidgetStatePropertyAll(Colors.blue)), - child: const Text("Authenticate", - style: TextStyle(color: Colors.white))), - TextButton( - onPressed: fetchData, - style: const ButtonStyle( - backgroundColor: - WidgetStatePropertyAll(Colors.blue)), - child: const Text("Fetch Data", - style: TextStyle(color: Colors.white))), - TextButton( - onPressed: addData, - style: const ButtonStyle( - backgroundColor: - WidgetStatePropertyAll(Colors.blue)), - child: const Text("Add Data", - style: TextStyle(color: Colors.white))), - TextButton( - onPressed: deleteData, - style: const ButtonStyle( - backgroundColor: - WidgetStatePropertyAll(Colors.blue)), - child: const Text("Delete Data", - style: TextStyle(color: Colors.white))), - TextButton( - onPressed: fetchStepData, - style: const ButtonStyle( - backgroundColor: - WidgetStatePropertyAll(Colors.blue)), - child: const Text("Fetch Step Data", - style: TextStyle(color: Colors.white))), - TextButton( - onPressed: revokeAccess, - style: const ButtonStyle( - backgroundColor: - WidgetStatePropertyAll(Colors.blue)), - child: const Text("Revoke Access", - style: TextStyle(color: Colors.white))), - TextButton( - onPressed: getIntervalBasedData, - style: const ButtonStyle( - backgroundColor: - WidgetStatePropertyAll(Colors.blue)), - child: const Text('Get Interval Data (7 days)', - style: TextStyle(color: Colors.white))), - ]), - ], - ), - const Divider(thickness: 3), - if (_state == AppState.DATA_READY) _dataFiltration, - if (_state == AppState.STEPS_READY) _stepsFiltration, - Expanded(child: Center(child: _content)) - ], - ), - ), - ); - } - - Widget get _dataFiltration => Column( - children: [ - Wrap( - children: [ - for (final method in Platform.isAndroid - ? [ - RecordingMethod.manual, - RecordingMethod.automatic, - RecordingMethod.active, - RecordingMethod.unknown, - ] - : [ - RecordingMethod.automatic, - RecordingMethod.manual, - ]) - SizedBox( - width: 150, - child: CheckboxListTile( - title: Text( - '${method.name[0].toUpperCase()}${method.name.substring(1)} entries'), - value: !recordingMethodsToFilter.contains(method), - onChanged: (value) { - setState(() { - if (value!) { - recordingMethodsToFilter.remove(method); - } else { - recordingMethodsToFilter.add(method); - } - fetchData(); - }); - }, - controlAffinity: ListTileControlAffinity.leading, - contentPadding: EdgeInsets.zero, - dense: true, - ), - ), - // Add other entries here if needed - ], - ), - const Divider(thickness: 3), - ], - ); - - Widget get _stepsFiltration => Column( - children: [ - Wrap( - children: [ - for (final method in [ - RecordingMethod.manual, - ]) - SizedBox( - width: 150, - child: CheckboxListTile( - title: Text( - '${method.name[0].toUpperCase()}${method.name.substring(1)} entries'), - value: !recordingMethodsToFilter.contains(method), - onChanged: (value) { - setState(() { - if (value!) { - recordingMethodsToFilter.remove(method); - } else { - recordingMethodsToFilter.add(method); - } - fetchStepData(); - }); - }, - controlAffinity: ListTileControlAffinity.leading, - contentPadding: EdgeInsets.zero, - dense: true, - ), - ), - // Add other entries here if needed - ], - ), - const Divider(thickness: 3), - ], - ); - - Widget get _permissionsRevoking => Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - padding: const EdgeInsets.all(20), - child: const CircularProgressIndicator( - strokeWidth: 10, - )), - const Text('Revoking permissions...') - ], - ); - - Widget get _permissionsRevoked => const Text('Permissions revoked.'); - - Widget get _permissionsNotRevoked => - const Text('Failed to revoke permissions'); - - Widget get _contentFetchingData => Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - padding: const EdgeInsets.all(20), - child: const CircularProgressIndicator( - strokeWidth: 10, - )), - const Text('Fetching data...') - ], - ); - - Widget get _contentDataReady => Builder(builder: (context) { - return ListView.builder( - itemCount: _healthDataList.length, - itemBuilder: (_, index) { - // filter out manual entires if not wanted - if (recordingMethodsToFilter - .contains(_healthDataList[index].recordingMethod)) { - return Container(); - } - - HealthDataPoint p = _healthDataList[index]; - if (p.value is AudiogramHealthValue) { - return ListTile( - title: Text("${p.typeString}: ${p.value}"), - trailing: Text(p.unitString), - subtitle: - Text('${p.dateFrom} - ${p.dateTo}\n${p.recordingMethod}'), - onTap: () { - fetchDataByUUID( - context, - uuid: p.uuid, - type: p.type, - ); - }, - ); - } - if (p.value is WorkoutHealthValue) { - return ListTile( - title: Text( - "${p.typeString}: ${(p.value as WorkoutHealthValue).totalEnergyBurned} ${(p.value as WorkoutHealthValue).totalEnergyBurnedUnit?.name}"), - trailing: Text( - (p.value as WorkoutHealthValue).workoutActivityType.name), - subtitle: - Text('${p.dateFrom} - ${p.dateTo}\n${p.recordingMethod}'), - onTap: () { - fetchDataByUUID( - context, - uuid: p.uuid, - type: p.type, - ); - }, - ); - } - if (p.value is NutritionHealthValue) { - return ListTile( - title: Text( - "${p.typeString} ${(p.value as NutritionHealthValue).mealType}: ${(p.value as NutritionHealthValue).name}"), - trailing: Text( - '${(p.value as NutritionHealthValue).calories} kcal'), - subtitle: - Text('${p.dateFrom} - ${p.dateTo}\n${p.recordingMethod}'), - onTap: () { - fetchDataByUUID( - context, - uuid: p.uuid, - type: p.type, - ); - }, - ); - } - return ListTile( - title: Text("${p.typeString}: ${p.value}"), - trailing: Text(p.unitString), - subtitle: - Text('${p.dateFrom} - ${p.dateTo}\n${p.recordingMethod}'), - onTap: () { - fetchDataByUUID( - context, - uuid: p.uuid, - type: p.type, - ); - }, - ); - }); - }); - - final Widget _contentNoData = const Text('No Data to show'); - - final Widget _contentNotFetched = - const Column(mainAxisAlignment: MainAxisAlignment.center, children: [ - Text("Press 'Auth' to get permissions to access health data."), - Text("Press 'Fetch Dat' to get health data."), - Text("Press 'Add Data' to add some random health data."), - Text("Press 'Delete Data' to remove some random health data."), - ]); - - final Widget _authorized = const Text('Authorization granted!'); - - final Widget _authorizationNotGranted = const Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text('Authorization not given.'), - Text( - 'For Google Health Connect please check if you have added the right permissions and services to the manifest file.'), - Text('For Apple Health check your permissions in Apple Health.'), - ], - ); - - Widget _contentHealthConnectStatus = const Text( - 'No status, click getHealthConnectSdkStatus to get the status.'); - - final Widget _dataAdded = const Text('Data points inserted successfully.'); - - final Widget _dataDeleted = const Text('Data points deleted successfully.'); - - Widget get _stepsFetched => Text('Total number of steps: $_nofSteps.'); - - final Widget _dataNotAdded = - const Text('Failed to add data.\nDo you have permissions to add data?'); - - final Widget _dataNotDeleted = const Text('Failed to delete data'); - - Widget get _content => switch (_state) { - AppState.DATA_READY => _contentDataReady, - AppState.DATA_NOT_FETCHED => _contentNotFetched, - AppState.FETCHING_DATA => _contentFetchingData, - AppState.NO_DATA => _contentNoData, - AppState.AUTHORIZED => _authorized, - AppState.AUTH_NOT_GRANTED => _authorizationNotGranted, - AppState.DATA_ADDED => _dataAdded, - AppState.DATA_DELETED => _dataDeleted, - AppState.DATA_NOT_ADDED => _dataNotAdded, - AppState.DATA_NOT_DELETED => _dataNotDeleted, - AppState.STEPS_READY => _stepsFetched, - AppState.HEALTH_CONNECT_STATUS => _contentHealthConnectStatus, - AppState.PERMISSIONS_REVOKING => _permissionsRevoking, - AppState.PERMISSIONS_REVOKED => _permissionsRevoked, - AppState.PERMISSIONS_NOT_REVOKED => _permissionsNotRevoked, - }; - - Widget _detailedBottomSheet({HealthDataPoint? healthPoint}) { - return DraggableScrollableSheet( - expand: false, - initialChildSize: 0.5, - minChildSize: 0.3, - maxChildSize: 0.9, - builder: (BuildContext listContext, scrollController) { - return Container( - padding: const EdgeInsets.all(16), - child: Column( - children: [ - const Text( - "Health Data Details", - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 10), - healthPoint == null - ? const Text('UUID Not Found!') - : Expanded( - child: ListView.builder( - controller: scrollController, - itemCount: healthPoint.toJson().entries.length, - itemBuilder: (context, index) { - String key = - healthPoint.toJson().keys.elementAt(index); - var value = healthPoint.toJson()[key]; - - return ListTile( - title: Text( - key.replaceAll('_', ' ').toUpperCase(), - style: - const TextStyle(fontWeight: FontWeight.bold), - ), - subtitle: Text(value.toString()), - ); - }, - ), - ), - ], - ), - ); - }, - ); - } -} diff --git a/packages/health/example/lib/util.dart b/packages/health/example/lib/util.dart deleted file mode 100644 index a3e9a6961..000000000 --- a/packages/health/example/lib/util.dart +++ /dev/null @@ -1,109 +0,0 @@ -import 'package:health/health.dart'; - -/// Data types available on iOS via Apple Health. -const List dataTypesIOS = [ - HealthDataType.ACTIVE_ENERGY_BURNED, - HealthDataType.APPLE_STAND_TIME, - HealthDataType.APPLE_STAND_HOUR, - HealthDataType.APPLE_MOVE_TIME, - HealthDataType.AUDIOGRAM, - HealthDataType.BASAL_ENERGY_BURNED, - HealthDataType.BLOOD_GLUCOSE, - HealthDataType.BLOOD_OXYGEN, - HealthDataType.BLOOD_PRESSURE_DIASTOLIC, - HealthDataType.BLOOD_PRESSURE_SYSTOLIC, - HealthDataType.BODY_FAT_PERCENTAGE, - HealthDataType.BODY_MASS_INDEX, - HealthDataType.BODY_TEMPERATURE, - HealthDataType.DIETARY_CARBS_CONSUMED, - HealthDataType.DIETARY_CAFFEINE, - HealthDataType.DIETARY_ENERGY_CONSUMED, - HealthDataType.DIETARY_FATS_CONSUMED, - HealthDataType.DIETARY_PROTEIN_CONSUMED, - HealthDataType.ELECTRODERMAL_ACTIVITY, - HealthDataType.FORCED_EXPIRATORY_VOLUME, - HealthDataType.HEART_RATE, - HealthDataType.HEART_RATE_VARIABILITY_SDNN, - HealthDataType.HEIGHT, - HealthDataType.INSULIN_DELIVERY, - HealthDataType.RESPIRATORY_RATE, - HealthDataType.PERIPHERAL_PERFUSION_INDEX, - HealthDataType.STEPS, - HealthDataType.WAIST_CIRCUMFERENCE, - HealthDataType.WEIGHT, - HealthDataType.FLIGHTS_CLIMBED, - HealthDataType.DISTANCE_WALKING_RUNNING, - HealthDataType.WALKING_SPEED, - HealthDataType.MINDFULNESS, - HealthDataType.SLEEP_AWAKE, - HealthDataType.SLEEP_ASLEEP, - HealthDataType.SLEEP_IN_BED, - HealthDataType.SLEEP_LIGHT, - HealthDataType.SLEEP_DEEP, - HealthDataType.SLEEP_REM, - HealthDataType.WATER, - HealthDataType.EXERCISE_TIME, - HealthDataType.WORKOUT, - HealthDataType.HEADACHE_NOT_PRESENT, - HealthDataType.HEADACHE_MILD, - HealthDataType.HEADACHE_MODERATE, - HealthDataType.HEADACHE_SEVERE, - HealthDataType.HEADACHE_UNSPECIFIED, - HealthDataType.LEAN_BODY_MASS, - - // note that a phone cannot write these ECG-based types - only read them - HealthDataType.ELECTROCARDIOGRAM, - // HealthDataType.HIGH_HEART_RATE_EVENT, - // HealthDataType.IRREGULAR_HEART_RATE_EVENT, - // HealthDataType.LOW_HEART_RATE_EVENT, - // HealthDataType.RESTING_HEART_RATE, - // HealthDataType.WALKING_HEART_RATE, - // HealthDataType.ATRIAL_FIBRILLATION_BURDEN, - - HealthDataType.NUTRITION, - HealthDataType.GENDER, - HealthDataType.BLOOD_TYPE, - HealthDataType.BIRTH_DATE, - HealthDataType.MENSTRUATION_FLOW, - HealthDataType.WATER_TEMPERATURE, - HealthDataType.UNDERWATER_DEPTH, - HealthDataType.UV_INDEX, -]; - -/// Data types available on Android via the Google Health Connect API. -const List dataTypesAndroid = [ - HealthDataType.ACTIVE_ENERGY_BURNED, - HealthDataType.BASAL_ENERGY_BURNED, - HealthDataType.BLOOD_GLUCOSE, - HealthDataType.BLOOD_OXYGEN, - HealthDataType.BLOOD_PRESSURE_DIASTOLIC, - HealthDataType.BLOOD_PRESSURE_SYSTOLIC, - HealthDataType.BODY_FAT_PERCENTAGE, - HealthDataType.HEIGHT, - HealthDataType.WEIGHT, - HealthDataType.LEAN_BODY_MASS, - HealthDataType.BODY_MASS_INDEX, - HealthDataType.BODY_TEMPERATURE, - HealthDataType.HEART_RATE, - HealthDataType.HEART_RATE_VARIABILITY_RMSSD, - HealthDataType.STEPS, - HealthDataType.DISTANCE_DELTA, - HealthDataType.SPEED, - HealthDataType.RESPIRATORY_RATE, - HealthDataType.SLEEP_ASLEEP, - HealthDataType.SLEEP_AWAKE_IN_BED, - HealthDataType.SLEEP_AWAKE, - HealthDataType.SLEEP_DEEP, - HealthDataType.SLEEP_LIGHT, - HealthDataType.SLEEP_OUT_OF_BED, - HealthDataType.SLEEP_REM, - HealthDataType.SLEEP_UNKNOWN, - HealthDataType.SLEEP_SESSION, - HealthDataType.WATER, - HealthDataType.WORKOUT, - HealthDataType.RESTING_HEART_RATE, - HealthDataType.FLIGHTS_CLIMBED, - HealthDataType.NUTRITION, - HealthDataType.TOTAL_CALORIES_BURNED, - HealthDataType.MENSTRUATION_FLOW, -]; diff --git a/packages/health/example/pubspec.yaml b/packages/health/example/pubspec.yaml deleted file mode 100644 index 581869077..000000000 --- a/packages/health/example/pubspec.yaml +++ /dev/null @@ -1,25 +0,0 @@ -name: health_example -description: Demonstrates how to use the health plugin. -publish_to: "none" -version: 4.5.0 - -environment: - sdk: ">=3.8.0 <4.0.0" - flutter: ">=3.6.0" - -dependencies: - flutter: - sdk: flutter - cupertino_icons: ^1.0.2 - permission_handler: ^11.3.1 - carp_serializable: ^2.0.0 # polymorphic json serialization - health: - path: ../ - -dev_dependencies: - flutter_lints: any - flutter_test: - sdk: flutter - -flutter: - uses-material-design: true diff --git a/packages/health/ios/Assets/.gitkeep b/packages/health/ios/Assets/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/health/ios/Classes/HealthConstants.swift b/packages/health/ios/Classes/HealthConstants.swift deleted file mode 100644 index 444096fd8..000000000 --- a/packages/health/ios/Classes/HealthConstants.swift +++ /dev/null @@ -1,216 +0,0 @@ -import HealthKit - -/// Constants used across the Health plugin -enum HealthConstants { - // Recording methods - enum RecordingMethod: Int { - case unknown = 0 // RECORDING_METHOD_UNKNOWN (not supported on iOS) - case active = 1 // RECORDING_METHOD_ACTIVELY_RECORDED (not supported on iOS) - case automatic = 2 // RECORDING_METHOD_AUTOMATICALLY_RECORDED - case manual = 3 // RECORDING_METHOD_MANUAL_ENTRY - } - - // Health Data Type Keys - static let ACTIVE_ENERGY_BURNED = "ACTIVE_ENERGY_BURNED" - static let ATRIAL_FIBRILLATION_BURDEN = "ATRIAL_FIBRILLATION_BURDEN" - static let AUDIOGRAM = "AUDIOGRAM" - static let APPLE_STAND_HOUR = "APPLE_STAND_HOUR" - static let APPLE_MOVE_TIME = "APPLE_MOVE_TIME" - static let APPLE_STAND_TIME = "APPLE_STAND_TIME" - static let BASAL_ENERGY_BURNED = "BASAL_ENERGY_BURNED" - static let BLOOD_GLUCOSE = "BLOOD_GLUCOSE" - static let BLOOD_OXYGEN = "BLOOD_OXYGEN" - static let BLOOD_PRESSURE_DIASTOLIC = "BLOOD_PRESSURE_DIASTOLIC" - static let BLOOD_PRESSURE_SYSTOLIC = "BLOOD_PRESSURE_SYSTOLIC" - static let BODY_FAT_PERCENTAGE = "BODY_FAT_PERCENTAGE" - static let LEAN_BODY_MASS = "LEAN_BODY_MASS" - static let BODY_MASS_INDEX = "BODY_MASS_INDEX" - static let BODY_TEMPERATURE = "BODY_TEMPERATURE" - - // Nutrition - static let DIETARY_CARBS_CONSUMED = "DIETARY_CARBS_CONSUMED" - static let DIETARY_ENERGY_CONSUMED = "DIETARY_ENERGY_CONSUMED" - static let DIETARY_FATS_CONSUMED = "DIETARY_FATS_CONSUMED" - static let DIETARY_PROTEIN_CONSUMED = "DIETARY_PROTEIN_CONSUMED" - static let DIETARY_CAFFEINE = "DIETARY_CAFFEINE" - static let DIETARY_FIBER = "DIETARY_FIBER" - static let DIETARY_SUGAR = "DIETARY_SUGAR" - static let DIETARY_FAT_MONOUNSATURATED = "DIETARY_FAT_MONOUNSATURATED" - static let DIETARY_FAT_POLYUNSATURATED = "DIETARY_FAT_POLYUNSATURATED" - static let DIETARY_FAT_SATURATED = "DIETARY_FAT_SATURATED" - static let DIETARY_CHOLESTEROL = "DIETARY_CHOLESTEROL" - static let DIETARY_VITAMIN_A = "DIETARY_VITAMIN_A" - static let DIETARY_THIAMIN = "DIETARY_THIAMIN" - static let DIETARY_RIBOFLAVIN = "DIETARY_RIBOFLAVIN" - static let DIETARY_NIACIN = "DIETARY_NIACIN" - static let DIETARY_PANTOTHENIC_ACID = "DIETARY_PANTOTHENIC_ACID" - static let DIETARY_VITAMIN_B6 = "DIETARY_VITAMIN_B6" - static let DIETARY_BIOTIN = "DIETARY_BIOTIN" - static let DIETARY_VITAMIN_B12 = "DIETARY_VITAMIN_B12" - static let DIETARY_VITAMIN_C = "DIETARY_VITAMIN_C" - static let DIETARY_VITAMIN_D = "DIETARY_VITAMIN_D" - static let DIETARY_VITAMIN_E = "DIETARY_VITAMIN_E" - static let DIETARY_VITAMIN_K = "DIETARY_VITAMIN_K" - static let DIETARY_FOLATE = "DIETARY_FOLATE" - static let DIETARY_CALCIUM = "DIETARY_CALCIUM" - static let DIETARY_CHLORIDE = "DIETARY_CHLORIDE" - static let DIETARY_IRON = "DIETARY_IRON" - static let DIETARY_MAGNESIUM = "DIETARY_MAGNESIUM" - static let DIETARY_PHOSPHORUS = "DIETARY_PHOSPHORUS" - static let DIETARY_POTASSIUM = "DIETARY_POTASSIUM" - static let DIETARY_SODIUM = "DIETARY_SODIUM" - static let DIETARY_ZINC = "DIETARY_ZINC" - static let DIETARY_WATER = "WATER" - static let DIETARY_CHROMIUM = "DIETARY_CHROMIUM" - static let DIETARY_COPPER = "DIETARY_COPPER" - static let DIETARY_IODINE = "DIETARY_IODINE" - static let DIETARY_MANGANESE = "DIETARY_MANGANESE" - static let DIETARY_MOLYBDENUM = "DIETARY_MOLYBDENUM" - static let DIETARY_SELENIUM = "DIETARY_SELENIUM" - - static let NUTRITION_KEYS: [String: HKQuantityTypeIdentifier] = [ - "calories": .dietaryEnergyConsumed, - "protein": .dietaryProtein, - "carbs": .dietaryCarbohydrates, - "fat": .dietaryFatTotal, - "caffeine": .dietaryCaffeine, - "vitamin_a": .dietaryVitaminA, - "b1_thiamine": .dietaryThiamin, - "b2_riboflavin": .dietaryRiboflavin, - "b3_niacin" : .dietaryNiacin, - "b5_pantothenic_acid" : .dietaryPantothenicAcid, - "b6_pyridoxine" : .dietaryVitaminB6, - "b7_biotin" : .dietaryBiotin, - "b9_folate" : .dietaryFolate, - "b12_cobalamin": .dietaryVitaminB12, - "vitamin_c": .dietaryVitaminC, - "vitamin_d": .dietaryVitaminD, - "vitamin_e": .dietaryVitaminE, - "vitamin_k": .dietaryVitaminK, - "calcium": .dietaryCalcium, - "chloride": .dietaryChloride, - "cholesterol": .dietaryCholesterol, - "chromium": .dietaryChromium, - "copper": .dietaryCopper, - "fat_unsaturated": .dietaryFatMonounsaturated, - "fat_monounsaturated": .dietaryFatMonounsaturated, - "fat_polyunsaturated": .dietaryFatPolyunsaturated, - "fat_saturated": .dietaryFatSaturated, - // "fat_trans_monoenoic": .dietaryFatTransMonoenoic, - "fiber": .dietaryFiber, - "iodine": .dietaryIodine, - "iron": .dietaryIron, - "magnesium": .dietaryMagnesium, - "manganese": .dietaryManganese, - "molybdenum": .dietaryMolybdenum, - "phosphorus": .dietaryPhosphorus, - "potassium": .dietaryPotassium, - "selenium": .dietarySelenium, - "sodium": .dietarySodium, - "sugar": .dietarySugar, - "water": .dietaryWater, - "zinc": .dietaryZinc, - ] - - static let ELECTRODERMAL_ACTIVITY = "ELECTRODERMAL_ACTIVITY" - static let FORCED_EXPIRATORY_VOLUME = "FORCED_EXPIRATORY_VOLUME" - static let HEART_RATE = "HEART_RATE" - static let HEART_RATE_VARIABILITY_SDNN = "HEART_RATE_VARIABILITY_SDNN" - static let HEIGHT = "HEIGHT" - static let INSULIN_DELIVERY = "INSULIN_DELIVERY" - static let HIGH_HEART_RATE_EVENT = "HIGH_HEART_RATE_EVENT" - static let IRREGULAR_HEART_RATE_EVENT = "IRREGULAR_HEART_RATE_EVENT" - static let LOW_HEART_RATE_EVENT = "LOW_HEART_RATE_EVENT" - static let RESTING_HEART_RATE = "RESTING_HEART_RATE" - static let RESPIRATORY_RATE = "RESPIRATORY_RATE" - static let PERIPHERAL_PERFUSION_INDEX = "PERIPHERAL_PERFUSION_INDEX" - static let STEPS = "STEPS" - static let WAIST_CIRCUMFERENCE = "WAIST_CIRCUMFERENCE" - static let WALKING_HEART_RATE = "WALKING_HEART_RATE" - static let WEIGHT = "WEIGHT" - static let DISTANCE_WALKING_RUNNING = "DISTANCE_WALKING_RUNNING" - static let DISTANCE_SWIMMING = "DISTANCE_SWIMMING" - static let DISTANCE_CYCLING = "DISTANCE_CYCLING" - static let WALKING_SPEED = "WALKING_SPEED" - static let FLIGHTS_CLIMBED = "FLIGHTS_CLIMBED" - static let MINDFULNESS = "MINDFULNESS" - static let SLEEP_ASLEEP = "SLEEP_ASLEEP" - static let SLEEP_AWAKE = "SLEEP_AWAKE" - static let SLEEP_DEEP = "SLEEP_DEEP" - static let SLEEP_IN_BED = "SLEEP_IN_BED" - static let SLEEP_LIGHT = "SLEEP_LIGHT" - static let SLEEP_REM = "SLEEP_REM" - - static let EXERCISE_TIME = "EXERCISE_TIME" - static let WORKOUT = "WORKOUT" - static let HEADACHE_UNSPECIFIED = "HEADACHE_UNSPECIFIED" - static let HEADACHE_NOT_PRESENT = "HEADACHE_NOT_PRESENT" - static let HEADACHE_MILD = "HEADACHE_MILD" - static let HEADACHE_MODERATE = "HEADACHE_MODERATE" - static let HEADACHE_SEVERE = "HEADACHE_SEVERE" - static let ELECTROCARDIOGRAM = "ELECTROCARDIOGRAM" - static let NUTRITION = "NUTRITION" - static let BIRTH_DATE = "BIRTH_DATE" - static let GENDER = "GENDER" - static let BLOOD_TYPE = "BLOOD_TYPE" - static let MENSTRUATION_FLOW = "MENSTRUATION_FLOW" - static let WATER_TEMPERATURE = "WATER_TEMPERATURE" - static let UNDERWATER_DEPTH = "UNDERWATER_DEPTH" - static let UV_INDEX = "UV_INDEX" - - // Health Unit types - static let GRAM = "GRAM" - static let KILOGRAM = "KILOGRAM" - static let OUNCE = "OUNCE" - static let POUND = "POUND" - static let STONE = "STONE" - static let METER = "METER" - static let INCH = "INCH" - static let FOOT = "FOOT" - static let YARD = "YARD" - static let MILE = "MILE" - static let LITER = "LITER" - static let MILLILITER = "MILLILITER" - static let FLUID_OUNCE_US = "FLUID_OUNCE_US" - static let FLUID_OUNCE_IMPERIAL = "FLUID_OUNCE_IMPERIAL" - static let CUP_US = "CUP_US" - static let CUP_IMPERIAL = "CUP_IMPERIAL" - static let PINT_US = "PINT_US" - static let PINT_IMPERIAL = "PINT_IMPERIAL" - static let PASCAL = "PASCAL" - static let MILLIMETER_OF_MERCURY = "MILLIMETER_OF_MERCURY" - static let INCHES_OF_MERCURY = "INCHES_OF_MERCURY" - static let CENTIMETER_OF_WATER = "CENTIMETER_OF_WATER" - static let ATMOSPHERE = "ATMOSPHERE" - static let DECIBEL_A_WEIGHTED_SOUND_PRESSURE_LEVEL = "DECIBEL_A_WEIGHTED_SOUND_PRESSURE_LEVEL" - static let SECOND = "SECOND" - static let MILLISECOND = "MILLISECOND" - static let MINUTE = "MINUTE" - static let HOUR = "HOUR" - static let DAY = "DAY" - static let JOULE = "JOULE" - static let KILOCALORIE = "KILOCALORIE" - static let LARGE_CALORIE = "LARGE_CALORIE" - static let SMALL_CALORIE = "SMALL_CALORIE" - static let DEGREE_CELSIUS = "DEGREE_CELSIUS" - static let DEGREE_FAHRENHEIT = "DEGREE_FAHRENHEIT" - static let KELVIN = "KELVIN" - static let DECIBEL_HEARING_LEVEL = "DECIBEL_HEARING_LEVEL" - static let HERTZ = "HERTZ" - static let SIEMEN = "SIEMEN" - static let VOLT = "VOLT" - static let INTERNATIONAL_UNIT = "INTERNATIONAL_UNIT" - static let COUNT = "COUNT" - static let PERCENT = "PERCENT" - static let BEATS_PER_MINUTE = "BEATS_PER_MINUTE" - static let RESPIRATIONS_PER_MINUTE = "RESPIRATIONS_PER_MINUTE" - static let MILLIGRAM_PER_DECILITER = "MILLIGRAM_PER_DECILITER" - static let METER_PER_SECOND = "METER_PER_SECOND" - static let UNKNOWN_UNIT = "UNKNOWN_UNIT" - static let NO_UNIT = "NO_UNIT" -} - -/// Error structure used throughout the plugin -struct PluginError: Error { - let message: String -} diff --git a/packages/health/ios/Classes/HealthDataOperations.swift b/packages/health/ios/Classes/HealthDataOperations.swift deleted file mode 100644 index b4e111da5..000000000 --- a/packages/health/ios/Classes/HealthDataOperations.swift +++ /dev/null @@ -1,323 +0,0 @@ -import Flutter -import HealthKit - -/// Class for managing health data permissions and deletion operations -class HealthDataOperations { - let healthStore: HKHealthStore - let dataTypesDict: [String: HKSampleType] - let characteristicsTypesDict: [String: HKCharacteristicType] - let nutritionList: [String] - - /// - Parameters: - /// - healthStore: The HealthKit store - /// - dataTypesDict: Dictionary of data types - /// - characteristicsTypesDict: Dictionary of characteristic types - /// - nutritionList: List of nutrition data types - init( - healthStore: HKHealthStore, - dataTypesDict: [String: HKSampleType], - characteristicsTypesDict: [String: HKCharacteristicType], - nutritionList: [String] - ) { - self.healthStore = healthStore - self.dataTypesDict = dataTypesDict - self.characteristicsTypesDict = characteristicsTypesDict - self.nutritionList = nutritionList - } - - /// Check if HealthKit is available on the device - /// - Parameters: - /// - call: Flutter method call - /// - result: Flutter result callback - func checkIfHealthDataAvailable(call: FlutterMethodCall, result: @escaping FlutterResult) { - result(HKHealthStore.isHealthDataAvailable()) - } - - /// Check if we have required permissions - /// - Parameters: - /// - call: Flutter method call - /// - result: Flutter result callback - func hasPermissions(call: FlutterMethodCall, result: @escaping FlutterResult) throws { - let arguments = call.arguments as? NSDictionary - guard var types = arguments?["types"] as? [String], - var permissions = arguments?["permissions"] as? [Int], - types.count == permissions.count - else { - throw PluginError(message: "Invalid Arguments!") - } - - if let nutritionIndex = types.firstIndex(of: HealthConstants.NUTRITION) { - types.remove(at: nutritionIndex) - let nutritionPermission = permissions[nutritionIndex] - permissions.remove(at: nutritionIndex) - - for nutritionType in nutritionList { - types.append(nutritionType) - permissions.append(nutritionPermission) - } - } - - for (index, type) in types.enumerated() { - guard let sampleType = dataTypesDict[type] else { - print("Warning: Health data type '\(type)' not found in dataTypesDict") - result(false) - return - } - - let success = hasPermission(type: sampleType, access: permissions[index]) - if success == nil || success == false { - result(success) - return - } - if let characteristicType = characteristicsTypesDict[type] { - let characteristicSuccess = hasPermission( - type: characteristicType, access: permissions[index]) - if characteristicSuccess == nil || characteristicSuccess == false { - result(characteristicSuccess) - return - } - } - } - - result(true) - } - - /// Check if we have permission for a specific type - /// - Parameters: - /// - type: The object type to check - /// - access: Access level (0: read, 1: write, other: read/write) - /// - Returns: Bool or nil depending on permission status - private func hasPermission(type: HKObjectType, access: Int) -> Bool? { - if #available(iOS 13.0, *) { - let status = healthStore.authorizationStatus(for: type) - switch access { - case 0: // READ - return nil - case 1: // WRITE - return (status == HKAuthorizationStatus.sharingAuthorized) - default: // READ_WRITE - return nil - } - } else { - return nil - } - } - - /// Request authorization for health data - /// - Parameters: - /// - call: Flutter method call - /// - result: Flutter result callback - func requestAuthorization(call: FlutterMethodCall, result: @escaping FlutterResult) throws { - guard let arguments = call.arguments as? NSDictionary, - let types = arguments["types"] as? [String], - let permissions = arguments["permissions"] as? [Int], - permissions.count == types.count - else { - throw PluginError(message: "Invalid Arguments!") - } - - var typesToRead = Set() - var typesToWrite = Set() - - for (index, key) in types.enumerated() { - if key == HealthConstants.NUTRITION { - for nutritionType in nutritionList { - guard let nutritionData = dataTypesDict[nutritionType] else { - print( - "Warning: Nutrition data type '\(nutritionType)' not found in dataTypesDict" - ) - continue - } - let access = permissions[index] - switch access { - case 0: - typesToRead.insert(nutritionData) - case 1: - typesToWrite.insert(nutritionData) - default: - typesToRead.insert(nutritionData) - typesToWrite.insert(nutritionData) - } - - } - } else { - let access = permissions[index] - - if let dataType = dataTypesDict[key] { - switch access { - case 0: - typesToRead.insert(dataType) - case 1: - typesToWrite.insert(dataType) - default: - typesToRead.insert(dataType) - typesToWrite.insert(dataType) - } - } - - if let characteristicsType = characteristicsTypesDict[key] { - switch access { - case 0: - typesToRead.insert(characteristicsType) - case 1: - throw PluginError( - message: - "Cannot request write permission for characteristic type \(characteristicsType)" - ) - default: - typesToRead.insert(characteristicsType) - } - } - - if dataTypesDict[key] == nil && characteristicsTypesDict[key] == nil { - print( - "Warning: Health data type '\(key)' not found in dataTypesDict or characteristicsTypesDict" - ) - } - - } - } - - healthStore.requestAuthorization(toShare: typesToWrite, read: typesToRead) { - (success, error) in - DispatchQueue.main.async { - result(success) - } - } - - } - - /// Delete health data by date range - /// - Parameters: - /// - call: Flutter method call - /// - result: Flutter result callback - func delete(call: FlutterMethodCall, result: @escaping FlutterResult) { - guard let arguments = call.arguments as? NSDictionary, - let dataTypeKey = arguments["dataTypeKey"] as? String - else { - print("Error: Missing dataTypeKey in arguments") - result(false) - return - } - - // Check if it's a characteristic type - these cannot be deleted - if characteristicsTypesDict[dataTypeKey] != nil { - print( - "Info: Cannot delete characteristic type '\(dataTypeKey)' - these are read-only system values" - ) - result(false) - return - } - - let startTime = (arguments["startTime"] as? NSNumber) ?? 0 - let endTime = (arguments["endTime"] as? NSNumber) ?? 0 - - let dateFrom = HealthUtilities.dateFromMilliseconds(startTime.doubleValue) - let dateTo = HealthUtilities.dateFromMilliseconds(endTime.doubleValue) - - guard let dataType = dataTypesDict[dataTypeKey] else { - print("Warning: Health data type '\(dataTypeKey)' not found in dataTypesDict") - result(false) - return - } - - let samplePredicate = HKQuery.predicateForSamples( - withStart: dateFrom, end: dateTo, options: .strictStartDate) - let ownerPredicate = HKQuery.predicateForObjects(from: HKSource.default()) - let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false) - - let deleteQuery = HKSampleQuery( - sampleType: dataType, - predicate: NSCompoundPredicate(andPredicateWithSubpredicates: [ - samplePredicate, ownerPredicate, - ]), - limit: HKObjectQueryNoLimit, - sortDescriptors: [sortDescriptor] - ) { [weak self] x, samplesOrNil, error in - guard let self = self else { return } - - guard let samplesOrNil = samplesOrNil, error == nil else { - print( - "Error querying \(dataType) samples: \(error?.localizedDescription ?? "Unknown error")" - ) - DispatchQueue.main.async { - result(false) - } - return - } - - // Chcek if there are any samples to delete - if samplesOrNil.isEmpty { - print("Info: No \(dataType) samples found in the specified date range.") - DispatchQueue.main.async { - result(true) - } - return - } - - // Delete the retrieved objects from the HealthKit store - self.healthStore.delete(samplesOrNil) { (success, error) in - if let err = error { - print("Error deleting \(dataType) Sample: \(err.localizedDescription)") - } - DispatchQueue.main.async { - result(success) - } - } - } - - healthStore.execute(deleteQuery) - } - - /// Delete health data by UUID - /// - Parameters: - /// - call: Flutter method call - /// - result: Flutter result callback - func deleteByUUID(call: FlutterMethodCall, result: @escaping FlutterResult) throws { - guard let arguments = call.arguments as? NSDictionary, - let uuidarg = arguments["uuid"] as? String, - let dataTypeKey = arguments["dataTypeKey"] as? String - else { - throw PluginError(message: "Invalid Arguments - UUID or DataTypeKey invalid") - } - - guard let dataTypeToRemove = dataTypesDict[dataTypeKey] else { - print("Warning: Health data type '\(dataTypeKey)' not found in dataTypesDict") - result(false) - return - } - - guard let uuid = UUID(uuidString: uuidarg) else { - result(false) - return - } - let predicate = HKQuery.predicateForObjects(with: [uuid]) - - let query = HKSampleQuery( - sampleType: dataTypeToRemove, - predicate: predicate, - limit: 1, - sortDescriptors: nil - ) { [weak self] query, samplesOrNil, error in - guard let self = self else { return } - - guard let samples = samplesOrNil, !samples.isEmpty else { - DispatchQueue.main.async { - result(false) - } - return - } - - self.healthStore.delete(samples) { success, error in - if let error = error { - print("Error deleting sample with UUID \(uuid): \(error.localizedDescription)") - } - DispatchQueue.main.async { - result(success) - } - } - } - - healthStore.execute(query) - } -} diff --git a/packages/health/ios/Classes/HealthDataReader.swift b/packages/health/ios/Classes/HealthDataReader.swift deleted file mode 100644 index c1b80183b..000000000 --- a/packages/health/ios/Classes/HealthDataReader.swift +++ /dev/null @@ -1,925 +0,0 @@ -import Flutter -import HealthKit - -/// Class responsible for reading health data from HealthKit -class HealthDataReader { - let healthStore: HKHealthStore - let dataTypesDict: [String: HKSampleType] - let dataQuantityTypesDict: [String: HKQuantityType] - let unitDict: [String: HKUnit] - let workoutActivityTypeMap: [String: HKWorkoutActivityType] - let characteristicsTypesDict: [String: HKCharacteristicType] - - /// - Parameters: - /// - healthStore: The HealthKit store - /// - dataTypesDict: Dictionary of data types - /// - dataQuantityTypesDict: Dictionary of quantity types - /// - unitDict: Dictionary of units - /// - workoutActivityTypeMap: Dictionary of workout activity types - /// - characteristicsTypesDict: Dictionary of characteristic types - init( - healthStore: HKHealthStore, - dataTypesDict: [String: HKSampleType], - dataQuantityTypesDict: [String: HKQuantityType], - unitDict: [String: HKUnit], - workoutActivityTypeMap: [String: HKWorkoutActivityType], - characteristicsTypesDict: [String: HKCharacteristicType] - ) { - self.healthStore = healthStore - self.dataTypesDict = dataTypesDict - self.dataQuantityTypesDict = dataQuantityTypesDict - self.unitDict = unitDict - self.workoutActivityTypeMap = workoutActivityTypeMap - self.characteristicsTypesDict = characteristicsTypesDict - } - - /// Gets health data - /// - Parameters: - /// - call: Flutter method call - /// - result: Flutter result callback - func getData(call: FlutterMethodCall, result: @escaping FlutterResult) { - guard let arguments = call.arguments as? NSDictionary, - let dataTypeKey = arguments["dataTypeKey"] as? String - else { - DispatchQueue.main.async { - result( - FlutterError( - code: "ARGUMENT_ERROR", - message: "Missing required dataTypeKey argument", - details: nil)) - } - return - } - - let dataUnitKey = arguments["dataUnitKey"] as? String - let startTime = (arguments["startTime"] as? NSNumber) ?? 0 - let endTime = (arguments["endTime"] as? NSNumber) ?? 0 - let limit = (arguments["limit"] as? Int) ?? HKObjectQueryNoLimit - let recordingMethodsToFilter = (arguments["recordingMethodsToFilter"] as? [Int]) ?? [] - let includeManualEntry = !recordingMethodsToFilter.contains( - HealthConstants.RecordingMethod.manual.rawValue) - - // convert from milliseconds to Date() - let dateFrom = HealthUtilities.dateFromMilliseconds(startTime.doubleValue) - let dateTo = HealthUtilities.dateFromMilliseconds(endTime.doubleValue) - - let sourceIdForCharacteristic = "com.apple.Health" - let sourceNameForCharacteristic = "Health" - - // characteristic types checks (like GENDER, BLOOD_TYPE, etc.) - switch dataTypeKey { - case HealthConstants.BIRTH_DATE: - let dateOfBirth = getBirthDate() - result([ - [ - "value": dateOfBirth?.timeIntervalSince1970, - "date_from": Int(dateFrom.timeIntervalSince1970 * 1000), - "date_to": Int(dateTo.timeIntervalSince1970 * 1000), - "source_id": sourceIdForCharacteristic, - "source_name": sourceNameForCharacteristic, - "recording_method": HealthConstants.RecordingMethod.manual.rawValue, - ] - ]) - return - case HealthConstants.GENDER: - let gender = getGender() - result([ - [ - "value": gender?.rawValue, - "date_from": Int(dateFrom.timeIntervalSince1970 * 1000), - "date_to": Int(dateTo.timeIntervalSince1970 * 1000), - "source_id": sourceIdForCharacteristic, - "source_name": sourceNameForCharacteristic, - "recording_method": HealthConstants.RecordingMethod.manual.rawValue, - ] - ]) - return - case HealthConstants.BLOOD_TYPE: - let bloodType = getBloodType() - result([ - [ - "value": bloodType?.rawValue, - "date_from": Int(dateFrom.timeIntervalSince1970 * 1000), - "date_to": Int(dateTo.timeIntervalSince1970 * 1000), - "source_id": sourceIdForCharacteristic, - "source_name": sourceNameForCharacteristic, - "recording_method": HealthConstants.RecordingMethod.manual.rawValue, - ] - ]) - return - default: - break - } - - guard let dataType = dataTypesDict[dataTypeKey] else { - DispatchQueue.main.async { - result( - FlutterError( - code: "INVALID_TYPE", - message: "Invalid dataTypeKey: \(dataTypeKey)", - details: nil)) - } - return - } - - var unit: HKUnit? - if let dataUnitKey = dataUnitKey { - unit = unitDict[dataUnitKey] - } - - var predicate = HKQuery.predicateForSamples( - withStart: dateFrom, end: dateTo, options: .strictStartDate) - if !includeManualEntry { - let manualPredicate = NSPredicate( - format: "metadata.%K != YES", HKMetadataKeyWasUserEntered) - predicate = NSCompoundPredicate( - type: .and, subpredicates: [predicate, manualPredicate]) - } - let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false) - - let query = HKSampleQuery( - sampleType: dataType, predicate: predicate, limit: limit, - sortDescriptors: [sortDescriptor] - ) { x, samplesOrNil, error in - - guard error == nil else { - DispatchQueue.main.async { - result( - FlutterError( - code: "HEALTH_ERROR", - message: "Error getting health data: \(error!.localizedDescription)", - details: nil)) - } - return - } - - guard let samples = samplesOrNil else { - DispatchQueue.main.async { - result([]) - } - return - } - - if let quantitySamples = samples as? [HKQuantitySample] { - let dictionaries = quantitySamples.map { sample -> NSDictionary in - return [ - "uuid": "\(sample.uuid)", - "value": sample.quantity.doubleValue( - for: unit ?? HKUnit.internationalUnit()), - "date_from": Int(sample.startDate.timeIntervalSince1970 * 1000), - "date_to": Int(sample.endDate.timeIntervalSince1970 * 1000), - "source_id": sample.sourceRevision.source.bundleIdentifier, - "source_name": sample.sourceRevision.source.name, - "recording_method": - (sample.metadata?[HKMetadataKeyWasUserEntered] as? Bool == true) - ? HealthConstants.RecordingMethod.manual.rawValue - : HealthConstants.RecordingMethod.automatic.rawValue, - "dataUnitKey": unit?.unitString, - "metadata": HealthUtilities.sanitizeMetadata(sample.metadata), - ] - } - DispatchQueue.main.async { - result(dictionaries) - } - } else if var categorySamples = samples as? [HKCategorySample] { - // filter category samples based on dataTypeKey - switch dataTypeKey { - case HealthConstants.SLEEP_IN_BED: - categorySamples = categorySamples.filter { $0.value == 0 } - case HealthConstants.SLEEP_ASLEEP: - categorySamples = categorySamples.filter { $0.value == 1 } - case HealthConstants.SLEEP_AWAKE: - categorySamples = categorySamples.filter { $0.value == 2 } - case HealthConstants.SLEEP_LIGHT: - categorySamples = categorySamples.filter { $0.value == 3 } - case HealthConstants.SLEEP_DEEP: - categorySamples = categorySamples.filter { $0.value == 4 } - case HealthConstants.SLEEP_REM: - categorySamples = categorySamples.filter { $0.value == 5 } - case HealthConstants.HEADACHE_UNSPECIFIED: - categorySamples = categorySamples.filter { $0.value == 0 } - case HealthConstants.HEADACHE_NOT_PRESENT: - categorySamples = categorySamples.filter { $0.value == 1 } - case HealthConstants.HEADACHE_MILD: - categorySamples = categorySamples.filter { $0.value == 2 } - case HealthConstants.HEADACHE_MODERATE: - categorySamples = categorySamples.filter { $0.value == 3 } - case HealthConstants.HEADACHE_SEVERE: - categorySamples = categorySamples.filter { $0.value == 4 } - default: - break - } - - let categories = categorySamples.map { sample -> NSDictionary in - return [ - "uuid": "\(sample.uuid)", - "value": sample.value, - "date_from": Int(sample.startDate.timeIntervalSince1970 * 1000), - "date_to": Int(sample.endDate.timeIntervalSince1970 * 1000), - "source_id": sample.sourceRevision.source.bundleIdentifier, - "source_name": sample.sourceRevision.source.name, - "recording_method": - (sample.metadata?[HKMetadataKeyWasUserEntered] as? Bool == true) - ? HealthConstants.RecordingMethod.manual.rawValue - : HealthConstants.RecordingMethod.automatic.rawValue, - "metadata": HealthUtilities.sanitizeMetadata(sample.metadata), - ] - } - DispatchQueue.main.async { - result(categories) - } - } else if let workoutSamples = samples as? [HKWorkout] { - let dictionaries = workoutSamples.map { sample -> NSDictionary in - return [ - "uuid": "\(sample.uuid)", - "workoutActivityType": self.workoutActivityTypeMap.first(where: { - $0.value == sample.workoutActivityType - })?.key, - "totalEnergyBurned": sample.totalEnergyBurned?.doubleValue( - for: HKUnit.kilocalorie()), - "totalEnergyBurnedUnit": "KILOCALORIE", - "totalDistance": sample.totalDistance?.doubleValue(for: HKUnit.meter()), - "totalDistanceUnit": "METER", - "date_from": Int(sample.startDate.timeIntervalSince1970 * 1000), - "date_to": Int(sample.endDate.timeIntervalSince1970 * 1000), - "source_id": sample.sourceRevision.source.bundleIdentifier, - "source_name": sample.sourceRevision.source.name, - "recording_method": - (sample.metadata?[HKMetadataKeyWasUserEntered] as? Bool == true) - ? HealthConstants.RecordingMethod.manual.rawValue - : HealthConstants.RecordingMethod.automatic.rawValue, - "workout_type": HKWorkoutActivityType.toString(sample.workoutActivityType), - "total_distance": sample.totalDistance != nil - ? Int(sample.totalDistance!.doubleValue(for: HKUnit.meter())) : 0, - "total_energy_burned": sample.totalEnergyBurned != nil - ? Int(sample.totalEnergyBurned!.doubleValue(for: HKUnit.kilocalorie())) - : 0, - ] - } - - DispatchQueue.main.async { - result(dictionaries) - } - } else if let audiogramSamples = samples as? [HKAudiogramSample] { - let dictionaries = audiogramSamples.map { sample -> NSDictionary in - var frequencies = [Double]() - var leftEarSensitivities = [Double]() - var rightEarSensitivities = [Double]() - for samplePoint in sample.sensitivityPoints { - frequencies.append(samplePoint.frequency.doubleValue(for: HKUnit.hertz())) - leftEarSensitivities.append( - samplePoint.leftEarSensitivity!.doubleValue( - for: HKUnit.decibelHearingLevel())) - rightEarSensitivities.append( - samplePoint.rightEarSensitivity!.doubleValue( - for: HKUnit.decibelHearingLevel())) - } - return [ - "uuid": "\(sample.uuid)", - "frequencies": frequencies, - "leftEarSensitivities": leftEarSensitivities, - "rightEarSensitivities": rightEarSensitivities, - "date_from": Int(sample.startDate.timeIntervalSince1970 * 1000), - "date_to": Int(sample.endDate.timeIntervalSince1970 * 1000), - "source_id": sample.sourceRevision.source.bundleIdentifier, - "source_name": sample.sourceRevision.source.name, - ] - } - DispatchQueue.main.async { - result(dictionaries) - } - } else if let nutritionSamples = samples as? [HKCorrelation] { - var foods: [[String: Any?]] = [] - for food in nutritionSamples { - let name = food.metadata?[HKMetadataKeyFoodType] as? String - let mealType = food.metadata?["HKFoodMeal"] - let samples = food.objects - if let sample = samples.first as? HKQuantitySample { - var sampleDict = [ - "uuid": "\(sample.uuid)", - "name": name, - "meal_type": mealType, - "date_from": Int(sample.startDate.timeIntervalSince1970 * 1000), - "date_to": Int(sample.endDate.timeIntervalSince1970 * 1000), - "source_id": sample.sourceRevision.source.bundleIdentifier, - "source_name": sample.sourceRevision.source.name, - "recording_method": - (sample.metadata?[HKMetadataKeyWasUserEntered] as? Bool == true) - ? HealthConstants.RecordingMethod.manual.rawValue - : HealthConstants.RecordingMethod.automatic.rawValue, - ] - for sample in samples { - if let quantitySample = sample as? HKQuantitySample { - for (key, identifier) in HealthConstants.NUTRITION_KEYS { - if quantitySample.quantityType - == HKObjectType.quantityType(forIdentifier: identifier) - { - let unit = - key == "calories" - ? HKUnit.kilocalorie() - : key == "water" - ? HKUnit.literUnit(with: .milli) : HKUnit.gram() - sampleDict[key] = quantitySample.quantity.doubleValue( - for: unit) - } - } - } - } - foods.append(sampleDict as! [String: Any?]) - } - } - - DispatchQueue.main.async { - result(foods) - } - } else { - if #available(iOS 14.0, *), let ecgSamples = samples as? [HKElectrocardiogram] { - self.fetchEcgMeasurements(ecgSamples, result) - } else { - DispatchQueue.main.async { - print("Error getting ECG - only available on iOS 14.0 and above!") - result(nil) - } - } - - } - } - - healthStore.execute(query) - } - - /// Gets single health data by UUID - /// - Parameters: - /// - call: Flutter method call - /// - result: Flutter result callback - func getDataByUUID(call: FlutterMethodCall, result: @escaping FlutterResult) { - - guard let arguments = call.arguments as? NSDictionary, - let uuidarg = arguments["uuid"] as? String, - let dataTypeKey = arguments["dataTypeKey"] as? String - else { - DispatchQueue.main.async { - result( - FlutterError( - code: "HEALTH_ERROR", - message: "Invalid Arguments - UUID or DataTypeKey invalid", - details: nil)) - } - return - } - - let dataUnitKey = arguments["dataUnitKey"] as? String - var unit: HKUnit? - if let dataUnitKey = dataUnitKey { - unit = unitDict[dataUnitKey] // Ensure unitDict exists and contains the key - } - - guard let dataType = dataTypesDict[dataTypeKey] else { - DispatchQueue.main.async { - result( - FlutterError( - code: "INVALID_TYPE", - message: "Invalid dataTypeKey: \(dataTypeKey)", - details: nil)) - } - return - } - - guard let uuid = UUID(uuidString: uuidarg) else { - result(nil) - return - } - - var predicate = HKQuery.predicateForObjects(with: [uuid]) - - let sourceIdForCharacteristic = "com.apple.Health" - let sourceNameForCharacteristic = "Health" - - let query = HKSampleQuery( - sampleType: dataType, - predicate: predicate, - limit: 1, - sortDescriptors: nil - ) { - [self] - x, samplesOrNil, error in - - guard error == nil else { - DispatchQueue.main.async { - result( - FlutterError( - code: "HEALTH_ERROR", - message: - "Error getting health data by UUID: \(error!.localizedDescription)", - details: nil)) - } - return - } - - guard let samples = samplesOrNil else { - DispatchQueue.main.async { - result(nil) - } - return - } - - if let quantitySamples = samples as? [HKQuantitySample] { - let dictionaries = quantitySamples.map { sample -> NSDictionary in - return [ - "uuid": "\(sample.uuid)", - "value": sample.quantity.doubleValue( - for: unit ?? HKUnit.internationalUnit()), - "date_from": Int(sample.startDate.timeIntervalSince1970 * 1000), - "date_to": Int(sample.endDate.timeIntervalSince1970 * 1000), - "source_id": sample.sourceRevision.source.bundleIdentifier, - "source_name": sample.sourceRevision.source.name, - "recording_method": - (sample.metadata?[HKMetadataKeyWasUserEntered] as? Bool == true) - ? HealthConstants.RecordingMethod.manual.rawValue - : HealthConstants.RecordingMethod.automatic.rawValue, - "dataUnitKey": unit?.unitString, - "metadata": HealthUtilities.sanitizeMetadata(sample.metadata), - ] - } - DispatchQueue.main.async { - result(dictionaries.first) - } - } else if var categorySamples = samples as? [HKCategorySample] { - // filter category samples based on dataTypeKey - switch dataTypeKey { - case HealthConstants.SLEEP_IN_BED: - categorySamples = categorySamples.filter { $0.value == 0 } - case HealthConstants.SLEEP_ASLEEP: - categorySamples = categorySamples.filter { $0.value == 1 } - case HealthConstants.SLEEP_AWAKE: - categorySamples = categorySamples.filter { $0.value == 2 } - case HealthConstants.SLEEP_LIGHT: - categorySamples = categorySamples.filter { $0.value == 3 } - case HealthConstants.SLEEP_DEEP: - categorySamples = categorySamples.filter { $0.value == 4 } - case HealthConstants.SLEEP_REM: - categorySamples = categorySamples.filter { $0.value == 5 } - case HealthConstants.HEADACHE_UNSPECIFIED: - categorySamples = categorySamples.filter { $0.value == 0 } - case HealthConstants.HEADACHE_NOT_PRESENT: - categorySamples = categorySamples.filter { $0.value == 1 } - case HealthConstants.HEADACHE_MILD: - categorySamples = categorySamples.filter { $0.value == 2 } - case HealthConstants.HEADACHE_MODERATE: - categorySamples = categorySamples.filter { $0.value == 3 } - case HealthConstants.HEADACHE_SEVERE: - categorySamples = categorySamples.filter { $0.value == 4 } - default: - break - } - - let categories = categorySamples.map { sample -> NSDictionary in - return [ - "uuid": "\(sample.uuid)", - "value": sample.value, - "date_from": Int(sample.startDate.timeIntervalSince1970 * 1000), - "date_to": Int(sample.endDate.timeIntervalSince1970 * 1000), - "source_id": sample.sourceRevision.source.bundleIdentifier, - "source_name": sample.sourceRevision.source.name, - "recording_method": - (sample.metadata?[HKMetadataKeyWasUserEntered] as? Bool == true) - ? HealthConstants.RecordingMethod.manual.rawValue - : HealthConstants.RecordingMethod.automatic.rawValue, - "metadata": HealthUtilities.sanitizeMetadata(sample.metadata), - ] - } - DispatchQueue.main.async { - result(categories.first) - } - } else if let workoutSamples = samples as? [HKWorkout] { - let dictionaries = workoutSamples.map { sample -> NSDictionary in - return [ - "uuid": "\(sample.uuid)", - "workoutActivityType": self.workoutActivityTypeMap.first(where: { - $0.value == sample.workoutActivityType - })?.key, - "totalEnergyBurned": sample.totalEnergyBurned?.doubleValue( - for: HKUnit.kilocalorie()), - "totalEnergyBurnedUnit": "KILOCALORIE", - "totalDistance": sample.totalDistance?.doubleValue(for: HKUnit.meter()), - "totalDistanceUnit": "METER", - "date_from": Int(sample.startDate.timeIntervalSince1970 * 1000), - "date_to": Int(sample.endDate.timeIntervalSince1970 * 1000), - "source_id": sample.sourceRevision.source.bundleIdentifier, - "source_name": sample.sourceRevision.source.name, - "recording_method": - (sample.metadata?[HKMetadataKeyWasUserEntered] as? Bool == true) - ? HealthConstants.RecordingMethod.manual.rawValue - : HealthConstants.RecordingMethod.automatic.rawValue, - "workout_type": HKWorkoutActivityType.toString(sample.workoutActivityType), - "total_distance": sample.totalDistance != nil - ? Int(sample.totalDistance!.doubleValue(for: HKUnit.meter())) : 0, - "total_energy_burned": sample.totalEnergyBurned != nil - ? Int(sample.totalEnergyBurned!.doubleValue(for: HKUnit.kilocalorie())) - : 0, - ] - } - - DispatchQueue.main.async { - result(dictionaries.first) - } - } else if let audiogramSamples = samples as? [HKAudiogramSample] { - let dictionaries = audiogramSamples.map { sample -> NSDictionary in - var frequencies = [Double]() - var leftEarSensitivities = [Double]() - var rightEarSensitivities = [Double]() - for samplePoint in sample.sensitivityPoints { - frequencies.append(samplePoint.frequency.doubleValue(for: HKUnit.hertz())) - leftEarSensitivities.append( - samplePoint.leftEarSensitivity!.doubleValue( - for: HKUnit.decibelHearingLevel())) - rightEarSensitivities.append( - samplePoint.rightEarSensitivity!.doubleValue( - for: HKUnit.decibelHearingLevel())) - } - return [ - "uuid": "\(sample.uuid)", - "frequencies": frequencies, - "leftEarSensitivities": leftEarSensitivities, - "rightEarSensitivities": rightEarSensitivities, - "date_from": Int(sample.startDate.timeIntervalSince1970 * 1000), - "date_to": Int(sample.endDate.timeIntervalSince1970 * 1000), - "source_id": sample.sourceRevision.source.bundleIdentifier, - "source_name": sample.sourceRevision.source.name, - ] - } - DispatchQueue.main.async { - result(dictionaries.first) - } - } else if let nutritionSamples = samples as? [HKCorrelation] { - var foods: [[String: Any?]] = [] - for food in nutritionSamples { - let name = food.metadata?[HKMetadataKeyFoodType] as? String - let mealType = food.metadata?["HKFoodMeal"] - let samples = food.objects - if let sample = samples.first as? HKQuantitySample { - var sampleDict = [ - "uuid": "\(sample.uuid)", - "name": name, - "meal_type": mealType, - "date_from": Int(sample.startDate.timeIntervalSince1970 * 1000), - "date_to": Int(sample.endDate.timeIntervalSince1970 * 1000), - "source_id": sample.sourceRevision.source.bundleIdentifier, - "source_name": sample.sourceRevision.source.name, - "recording_method": - (sample.metadata?[HKMetadataKeyWasUserEntered] as? Bool == true) - ? HealthConstants.RecordingMethod.manual.rawValue - : HealthConstants.RecordingMethod.automatic.rawValue, - ] - for sample in samples { - if let quantitySample = sample as? HKQuantitySample { - for (key, identifier) in HealthConstants.NUTRITION_KEYS { - if quantitySample.quantityType - == HKObjectType.quantityType(forIdentifier: identifier) - { - let unit = - key == "calories" - ? HKUnit.kilocalorie() - : key == "water" - ? HKUnit.literUnit(with: .milli) : HKUnit.gram() - sampleDict[key] = quantitySample.quantity.doubleValue( - for: unit) - } - } - } - } - foods.append(sampleDict) - } - } - - DispatchQueue.main.async { - result(foods.first) - } - } else { - if #available(iOS 14.0, *), let ecgSamples = samples as? [HKElectrocardiogram] { - self.fetchEcgMeasurements(ecgSamples) { ecgDictionaries in - DispatchQueue.main.async { - if let dictionaries = ecgDictionaries as? [NSDictionary] { - result(dictionaries.first) - } else { - result(nil) - } - } - } - } else { - DispatchQueue.main.async { - print("Error getting ECG - only available on iOS 14.0 and above!") - result(nil) - } - } - } - } - - healthStore.execute(query) - } - - /// Gets interval health data - /// - Parameters: - /// - call: Flutter method call - /// - result: Flutter result callback - func getIntervalData(call: FlutterMethodCall, result: @escaping FlutterResult) { - let arguments = call.arguments as? NSDictionary - let dataTypeKey = (arguments?["dataTypeKey"] as? String) ?? "DEFAULT" - let dataUnitKey = (arguments?["dataUnitKey"] as? String) - let startDate = (arguments?["startTime"] as? NSNumber) ?? 0 - let endDate = (arguments?["endTime"] as? NSNumber) ?? 0 - let intervalInSecond = (arguments?["interval"] as? Int) ?? 1 - let recordingMethodsToFilter = (arguments?["recordingMethodsToFilter"] as? [Int]) ?? [] - let includeManualEntry = !recordingMethodsToFilter.contains( - HealthConstants.RecordingMethod.manual.rawValue) - - // interval in seconds - var interval = DateComponents() - interval.second = intervalInSecond - - let dateFrom = HealthUtilities.dateFromMilliseconds(startDate.doubleValue) - let dateTo = HealthUtilities.dateFromMilliseconds(endDate.doubleValue) - - guard let quantityType = dataQuantityTypesDict[dataTypeKey] else { - DispatchQueue.main.async { - result( - FlutterError( - code: "INVALID_TYPE", - message: "Invalid dataTypeKey for interval query: \(dataTypeKey)", - details: nil)) - } - return - } - - var predicate = HKQuery.predicateForSamples(withStart: dateFrom, end: dateTo, options: []) - if !includeManualEntry { - let manualPredicate = NSPredicate( - format: "metadata.%K != YES", HKMetadataKeyWasUserEntered) - predicate = NSCompoundPredicate( - type: .and, subpredicates: [predicate, manualPredicate]) - } - - let statisticsOptions = statisticsOption(for: dataTypeKey) - - let query = HKStatisticsCollectionQuery( - quantityType: quantityType, - quantitySamplePredicate: predicate, - options: statisticsOptions, - anchorDate: dateFrom, - intervalComponents: interval - ) - - query.initialResultsHandler = { [weak self] _, statisticCollectionOrNil, error in - guard let self = self else { - DispatchQueue.main.async { - result( - FlutterError( - code: "INTERNAL_ERROR", - message: "Internal instance reference lost", - details: nil)) - } - return - } - - if let error = error { - DispatchQueue.main.async { - result( - FlutterError( - code: "STATISTICS_ERROR", - message: "Error getting statistics: \(error.localizedDescription)", - details: nil)) - } - return - } - - guard let collection = statisticCollectionOrNil else { - DispatchQueue.main.async { - result(nil) - } - return - } - - var dictionaries = [[String: Any]]() - collection.enumerateStatistics(from: dateFrom, to: dateTo) { - [weak self] statisticData, _ in - guard let self = self else { return } - if let dataUnitKey = dataUnitKey, let unit = self.unitDict[dataUnitKey] { - var value: Double? = nil - switch statisticsOptions { - case .cumulativeSum: - value = statisticData.sumQuantity()?.doubleValue(for: unit) - case .discreteAverage: - value = statisticData.averageQuantity()?.doubleValue(for: unit) - case .discreteMin: - value = statisticData.minimumQuantity()?.doubleValue(for: unit) - case .discreteMax: - value = statisticData.maximumQuantity()?.doubleValue(for: unit) - default: - value = statisticData.sumQuantity()?.doubleValue(for: unit) - } - if let value = value { - let dict = [ - "value": value, - "date_from": Int(statisticData.startDate.timeIntervalSince1970 * 1000), - "date_to": Int(statisticData.endDate.timeIntervalSince1970 * 1000), - "source_id": statisticData.sources?.first?.bundleIdentifier ?? "", - "source_name": statisticData.sources?.first?.name ?? "", - ] - dictionaries.append(dict) - } - } - } - DispatchQueue.main.async { - result(dictionaries) - } - } - healthStore.execute(query) - } - - /// Helper to select correct HKStatisticsOptions for a given dataTypeKey - private func statisticsOption(for dataTypeKey: String) -> HKStatisticsOptions { - guard let quantityType = dataQuantityTypesDict[dataTypeKey] else { - // Default to cumulativeSum for backward compatibility - return .cumulativeSum - } - switch quantityType.aggregationStyle { - case .cumulative: - return .cumulativeSum - case .discreteArithmetic: - return .discreteAverage - case .discrete: - // Other options are .discreteAverage, .discreteMin, or .discreteMax - return .discreteAverage - @unknown default: - return .cumulativeSum - } - } - - /// Gets total steps in interval - /// - Parameters: - /// - call: Flutter method call - /// - result: Flutter result callback - func getTotalStepsInInterval(call: FlutterMethodCall, result: @escaping FlutterResult) { - let arguments = call.arguments as? NSDictionary - let startTime = (arguments?["startTime"] as? NSNumber) ?? 0 - let endTime = (arguments?["endTime"] as? NSNumber) ?? 0 - let recordingMethodsToFilter = (arguments?["recordingMethodsToFilter"] as? [Int]) ?? [] - let includeManualEntry = !recordingMethodsToFilter.contains( - HealthConstants.RecordingMethod.manual.rawValue) - - // Convert dates from milliseconds to Date() - let dateFrom = HealthUtilities.dateFromMilliseconds(startTime.doubleValue) - let dateTo = HealthUtilities.dateFromMilliseconds(endTime.doubleValue) - - let sampleType = HKQuantityType.quantityType(forIdentifier: .stepCount)! - var predicate = HKQuery.predicateForSamples( - withStart: dateFrom, end: dateTo, options: .strictStartDate) - if !includeManualEntry { - let manualPredicate = NSPredicate( - format: "metadata.%K != YES", HKMetadataKeyWasUserEntered) - predicate = NSCompoundPredicate( - type: .and, subpredicates: [predicate, manualPredicate]) - } - - let query = HKStatisticsCollectionQuery( - quantityType: sampleType, - quantitySamplePredicate: predicate, - options: .cumulativeSum, - anchorDate: dateFrom, - intervalComponents: DateComponents(day: 1) - ) - query.initialResultsHandler = { query, results, error in - guard let results = results else { - let errorMessage = error?.localizedDescription ?? "Unknown error" - DispatchQueue.main.async { - result( - FlutterError( - code: "STEPS_ERROR", - message: "Error getting step count: \(errorMessage)", - details: nil)) - } - return - } - - var totalSteps = 0.0 - results.enumerateStatistics(from: dateFrom, to: dateTo) { statistics, stop in - if let quantity = statistics.sumQuantity() { - let unit = HKUnit.count() - totalSteps += quantity.doubleValue(for: unit) - } - } - - DispatchQueue.main.async { - result(Int(totalSteps)) - } - } - - healthStore.execute(query) - } - - /// Gets birth date from HealthKit - /// - Returns: Birth date - private func getBirthDate() -> Date? { - var dob: Date? - do { - dob = try healthStore.dateOfBirthComponents().date - } catch { - dob = nil - print("Error retrieving date of birth: \(error)") - } - return dob - } - - /// Gets gender from HealthKit - /// - Returns: Biological sex - private func getGender() -> HKBiologicalSex? { - var bioSex: HKBiologicalSex? - do { - bioSex = try healthStore.biologicalSex().biologicalSex - } catch { - bioSex = nil - print("Error retrieving biologicalSex: \(error)") - } - return bioSex - } - - /// Gets blood type from HealthKit - /// - Returns: Blood type - private func getBloodType() -> HKBloodType? { - var bloodType: HKBloodType? - do { - bloodType = try healthStore.bloodType().bloodType - } catch { - bloodType = nil - print("Error retrieving blood type: \(error)") - } - return bloodType - } - - /// Fetch ECG measurements from an HKElectrocardiogram sample - /// - Parameter sample: ECG sample - /// - Returns: Dictionary with ECG data - @available(iOS 14.0, *) - private func fetchEcgMeasurements( - _ ecgSample: [HKElectrocardiogram], _ result: @escaping FlutterResult - ) { - let group = DispatchGroup() - var dictionaries = [NSDictionary]() - let lock = NSLock() - - for ecg in ecgSample { - group.enter() - - var voltageValues = [[String: Any]]() - let expected = Int(ecg.numberOfVoltageMeasurements) - if expected > 0 { - voltageValues.reserveCapacity(expected) - } - - let q = HKElectrocardiogramQuery(ecg) { _, res in - switch res { - case .measurement(let m): - if let v = m.quantity(for: .appleWatchSimilarToLeadI)? - .doubleValue(for: HKUnit.volt()) - { - voltageValues.append([ - "voltage": v, - "timeSinceSampleStart": m.timeSinceSampleStart, - ]) - } - case .done: - let dict: NSDictionary = [ - "uuid": "\(ecg.uuid)", - "voltageValues": voltageValues, - "averageHeartRate": ecg.averageHeartRate? - .doubleValue( - for: HKUnit.count() - .unitDivided(by: HKUnit.minute())), - "samplingFrequency": ecg.samplingFrequency? - .doubleValue(for: HKUnit.hertz()), - "classification": ecg.classification.rawValue, - "date_from": Int(ecg.startDate.timeIntervalSince1970 * 1000), - "date_to": Int(ecg.endDate.timeIntervalSince1970 * 1000), - "source_id": ecg.sourceRevision.source.bundleIdentifier, - "source_name": ecg.sourceRevision.source.name, - ] - lock.lock() - dictionaries.append(dict) - lock.unlock() - group.leave() - case .error(let e): - print("ECG query error: \(e)") - group.leave() - @unknown default: - print("ECG query unknown result") - group.leave() - } - } - self.healthStore.execute(q) - } - - group.notify(queue: .main) { - result(dictionaries) - } - } -} diff --git a/packages/health/ios/Classes/HealthDataWriter.swift b/packages/health/ios/Classes/HealthDataWriter.swift deleted file mode 100644 index df19a505e..000000000 --- a/packages/health/ios/Classes/HealthDataWriter.swift +++ /dev/null @@ -1,440 +0,0 @@ -import Flutter -import HealthKit - -/// Class responsible for writing health data to HealthKit -class HealthDataWriter { - let healthStore: HKHealthStore - let dataTypesDict: [String: HKSampleType] - let unitDict: [String: HKUnit] - let workoutActivityTypeMap: [String: HKWorkoutActivityType] - - /// - Parameters: - /// - healthStore: The HealthKit store - /// - dataTypesDict: Dictionary of data types - /// - unitDict: Dictionary of units - /// - workoutActivityTypeMap: Dictionary of workout activity types - init( - healthStore: HKHealthStore, dataTypesDict: [String: HKSampleType], - unitDict: [String: HKUnit], workoutActivityTypeMap: [String: HKWorkoutActivityType] - ) { - self.healthStore = healthStore - self.dataTypesDict = dataTypesDict - self.unitDict = unitDict - self.workoutActivityTypeMap = workoutActivityTypeMap - } - - /// Writes general health data - /// - Parameters: - /// - call: Flutter method call - /// - result: Flutter result callback - func writeData(call: FlutterMethodCall, result: @escaping FlutterResult) throws { - guard let arguments = call.arguments as? NSDictionary, - let value = (arguments["value"] as? Double), - let type = (arguments["dataTypeKey"] as? String), - let unit = (arguments["dataUnitKey"] as? String), - let startTime = (arguments["startTime"] as? NSNumber), - let endTime = (arguments["endTime"] as? NSNumber), - let recordingMethod = (arguments["recordingMethod"] as? Int) - else { - throw PluginError(message: "Invalid Arguments") - } - - // Handle mindfulness sessions specifically - if type == HealthConstants.MINDFULNESS { - try writeMindfulness(call: call, result: result) - return - } - - let dateFrom = HealthUtilities.dateFromMilliseconds(startTime.doubleValue) - let dateTo = HealthUtilities.dateFromMilliseconds(endTime.doubleValue) - - let isManualEntry = recordingMethod == HealthConstants.RecordingMethod.manual.rawValue - let metadata: [String: Any] = [ - HKMetadataKeyWasUserEntered: NSNumber(value: isManualEntry) - ] - - let sample: HKObject - - if dataTypesDict[type]!.isKind(of: HKCategoryType.self) { - sample = HKCategorySample( - type: dataTypesDict[type] as! HKCategoryType, value: Int(value), start: dateFrom, - end: dateTo, metadata: metadata) - } else { - let quantity = HKQuantity(unit: unitDict[unit]!, doubleValue: value) - sample = HKQuantitySample( - type: dataTypesDict[type] as! HKQuantityType, quantity: quantity, start: dateFrom, - end: dateTo, metadata: metadata) - } - - healthStore.save( - sample, - withCompletion: { (success, error) in - if let err = error { - print("Error Saving \(type) Sample: \(err.localizedDescription)") - } - DispatchQueue.main.async { - result(success) - } - }) - } - - /// Writes audiogram data - /// - Parameters: - /// - call: Flutter method call - /// - result: Flutter result callback - func writeAudiogram(call: FlutterMethodCall, result: @escaping FlutterResult) throws { - guard let arguments = call.arguments as? NSDictionary, - let frequencies = (arguments["frequencies"] as? [Double]), - let leftEarSensitivities = (arguments["leftEarSensitivities"] as? [Double]), - let rightEarSensitivities = (arguments["rightEarSensitivities"] as? [Double]), - let startTime = (arguments["startTime"] as? NSNumber), - let endTime = (arguments["endTime"] as? NSNumber) - else { - throw PluginError(message: "Invalid Arguments") - } - - let dateFrom = HealthUtilities.dateFromMilliseconds(startTime.doubleValue) - let dateTo = HealthUtilities.dateFromMilliseconds(endTime.doubleValue) - - var sensitivityPoints = [HKAudiogramSensitivityPoint]() - - for index in 0...frequencies.count - 1 { - let frequency = HKQuantity(unit: HKUnit.hertz(), doubleValue: frequencies[index]) - let dbUnit = HKUnit.decibelHearingLevel() - let left = HKQuantity(unit: dbUnit, doubleValue: leftEarSensitivities[index]) - let right = HKQuantity(unit: dbUnit, doubleValue: rightEarSensitivities[index]) - let sensitivityPoint = try HKAudiogramSensitivityPoint( - frequency: frequency, leftEarSensitivity: left, rightEarSensitivity: right) - sensitivityPoints.append(sensitivityPoint) - } - - let audiogram: HKAudiogramSample - let metadataReceived = (arguments["metadata"] as? [String: Any]?) - - if (metadataReceived) != nil { - guard let deviceName = metadataReceived?!["HKDeviceName"] as? String else { return } - guard let externalUUID = metadataReceived?!["HKExternalUUID"] as? String else { return } - - audiogram = HKAudiogramSample( - sensitivityPoints: sensitivityPoints, start: dateFrom, end: dateTo, - metadata: [ - HKMetadataKeyDeviceName: deviceName, HKMetadataKeyExternalUUID: externalUUID, - ]) - - } else { - audiogram = HKAudiogramSample( - sensitivityPoints: sensitivityPoints, start: dateFrom, end: dateTo, metadata: nil) - } - - healthStore.save( - audiogram, - withCompletion: { (success, error) in - if let err = error { - print("Error Saving Audiogram. Sample: \(err.localizedDescription)") - } - DispatchQueue.main.async { - result(success) - } - }) - } - - /// Writes blood pressure data - /// - Parameters: - /// - call: Flutter method call - /// - result: Flutter result callback - func writeBloodPressure(call: FlutterMethodCall, result: @escaping FlutterResult) throws { - guard let arguments = call.arguments as? NSDictionary, - let systolic = (arguments["systolic"] as? Double), - let diastolic = (arguments["diastolic"] as? Double), - let startTime = (arguments["startTime"] as? NSNumber), - let endTime = (arguments["endTime"] as? NSNumber), - let recordingMethod = (arguments["recordingMethod"] as? Int) - else { - throw PluginError(message: "Invalid Arguments") - } - let dateFrom = HealthUtilities.dateFromMilliseconds(startTime.doubleValue) - let dateTo = HealthUtilities.dateFromMilliseconds(endTime.doubleValue) - - let isManualEntry = recordingMethod == HealthConstants.RecordingMethod.manual.rawValue - let metadata = [ - HKMetadataKeyWasUserEntered: NSNumber(value: isManualEntry) - ] - - let systolic_sample = HKQuantitySample( - type: HKSampleType.quantityType(forIdentifier: .bloodPressureSystolic)!, - quantity: HKQuantity(unit: HKUnit.millimeterOfMercury(), doubleValue: systolic), - start: dateFrom, end: dateTo, metadata: metadata) - let diastolic_sample = HKQuantitySample( - type: HKSampleType.quantityType(forIdentifier: .bloodPressureDiastolic)!, - quantity: HKQuantity(unit: HKUnit.millimeterOfMercury(), doubleValue: diastolic), - start: dateFrom, end: dateTo, metadata: metadata) - let bpCorrelationType = HKCorrelationType.correlationType(forIdentifier: .bloodPressure)! - let bpCorrelation = Set(arrayLiteral: systolic_sample, diastolic_sample) - let blood_pressure_sample = HKCorrelation( - type: bpCorrelationType, start: dateFrom, end: dateTo, objects: bpCorrelation) - - healthStore.save( - [blood_pressure_sample], - withCompletion: { (success, error) in - if let err = error { - print("Error Saving Blood Pressure Sample: \(err.localizedDescription)") - } - DispatchQueue.main.async { - result(success) - } - }) - } - - /// Writes meal nutrition data - /// - Parameters: - /// - call: Flutter method call - /// - result: Flutter result callback - func writeMeal(call: FlutterMethodCall, result: @escaping FlutterResult) throws { - guard let arguments = call.arguments as? NSDictionary, - let name = (arguments["name"] as? String?), - let startTime = (arguments["start_time"] as? NSNumber), - let endTime = (arguments["end_time"] as? NSNumber), - let mealType = (arguments["meal_type"] as? String?), - let recordingMethod = arguments["recordingMethod"] as? Int - else { - throw PluginError(message: "Invalid Arguments") - } - - let dateFrom = HealthUtilities.dateFromMilliseconds(startTime.doubleValue) - let dateTo = HealthUtilities.dateFromMilliseconds(endTime.doubleValue) - - let mealTypeString = mealType ?? "UNKNOWN" - - let isManualEntry = recordingMethod == HealthConstants.RecordingMethod.manual.rawValue - - var metadata = - [ - "HKFoodMeal": mealTypeString, - HKMetadataKeyWasUserEntered: NSNumber(value: isManualEntry), - ] as [String: Any] - if name != nil { - metadata[HKMetadataKeyFoodType] = "\(name!)" - } - - var nutrition = Set() - for (key, identifier) in HealthConstants.NUTRITION_KEYS { - let value = arguments[key] as? Double - guard let unwrappedValue = value else { continue } - let unit = - key == "calories" - ? HKUnit.kilocalorie() - : key == "water" ? HKUnit.literUnit(with: .milli) : HKUnit.gram() - let nutritionSample = HKQuantitySample( - type: HKSampleType.quantityType(forIdentifier: identifier)!, - quantity: HKQuantity(unit: unit, doubleValue: unwrappedValue), start: dateFrom, - end: dateTo, metadata: metadata) - nutrition.insert(nutritionSample) - } - - if #available(iOS 15.0, *) { - let type = HKCorrelationType.correlationType( - forIdentifier: HKCorrelationTypeIdentifier.food)! - let meal = HKCorrelation( - type: type, start: dateFrom, end: dateTo, objects: nutrition, metadata: metadata) - - healthStore.save( - meal, - withCompletion: { (success, error) in - if let err = error { - print("Error Saving Meal Sample: \(err.localizedDescription)") - } - DispatchQueue.main.async { - result(success) - } - }) - } else { - result(false) - } - } - - /// Writes insulin delivery data - /// - Parameters: - /// - call: Flutter method call - /// - result: Flutter result callback - func writeInsulinDelivery(call: FlutterMethodCall, result: @escaping FlutterResult) throws { - guard let arguments = call.arguments as? NSDictionary, - let units = (arguments["units"] as? Double), - let reason = (arguments["reason"] as? NSNumber), - let startTime = (arguments["startTime"] as? NSNumber), - let endTime = (arguments["endTime"] as? NSNumber) - else { - throw PluginError(message: "Invalid Arguments") - } - let dateFrom = HealthUtilities.dateFromMilliseconds(startTime.doubleValue) - let dateTo = HealthUtilities.dateFromMilliseconds(endTime.doubleValue) - - let type = HKSampleType.quantityType(forIdentifier: .insulinDelivery)! - let quantity = HKQuantity(unit: HKUnit.internationalUnit(), doubleValue: units) - let metadata = [HKMetadataKeyInsulinDeliveryReason: reason] - - let insulin_sample = HKQuantitySample( - type: type, quantity: quantity, start: dateFrom, end: dateTo, metadata: metadata) - - healthStore.save( - insulin_sample, - withCompletion: { (success, error) in - if let err = error { - print("Error Saving Insulin Delivery Sample: \(err.localizedDescription)") - } - DispatchQueue.main.async { - result(success) - } - }) - } - - /// Writes menstruation flow data - /// - Parameters: - /// - call: Flutter method call - /// - result: Flutter result callback - func writeMenstruationFlow(call: FlutterMethodCall, result: @escaping FlutterResult) throws { - guard let arguments = call.arguments as? NSDictionary, - let flow = (arguments["value"] as? Int), - let endTime = (arguments["endTime"] as? NSNumber), - let isStartOfCycle = (arguments["isStartOfCycle"] as? NSNumber), - let recordingMethod = (arguments["recordingMethod"] as? Int) - else { - throw PluginError( - message: "Invalid Arguments - value, startTime, endTime or isStartOfCycle invalid") - } - guard let menstrualFlowType = HKCategoryValueMenstrualFlow(rawValue: flow) else { - throw PluginError(message: "Invalid Menstrual Flow Type") - } - - let dateTime = HealthUtilities.dateFromMilliseconds(endTime.doubleValue) - - let isManualEntry = recordingMethod == HealthConstants.RecordingMethod.manual.rawValue - - guard let categoryType = HKSampleType.categoryType(forIdentifier: .menstrualFlow) else { - throw PluginError(message: "Invalid Menstrual Flow Type") - } - - let metadata = - [ - HKMetadataKeyMenstrualCycleStart: isStartOfCycle, - HKMetadataKeyWasUserEntered: NSNumber(value: isManualEntry), - ] as [String: Any] - - let sample = HKCategorySample( - type: categoryType, - value: menstrualFlowType.rawValue, - start: dateTime, - end: dateTime, - metadata: metadata - ) - - healthStore.save( - sample, - withCompletion: { (success, error) in - if let err = error { - print("Error Saving Menstruation Flow Sample: \(err.localizedDescription)") - } - DispatchQueue.main.async { - result(success) - } - }) - } - - /// Writes mindfulness session data - /// - Parameters: - /// - call: Flutter method call - /// - result: Flutter result callback - func writeMindfulness(call: FlutterMethodCall, result: @escaping FlutterResult) throws { - guard let arguments = call.arguments as? NSDictionary, - let startTime = (arguments["startTime"] as? NSNumber), - let endTime = (arguments["endTime"] as? NSNumber), - let recordingMethod = (arguments["recordingMethod"] as? Int) - else { - throw PluginError(message: "Invalid Arguments") - } - - let dateFrom = HealthUtilities.dateFromMilliseconds(startTime.doubleValue) - let dateTo = HealthUtilities.dateFromMilliseconds(endTime.doubleValue) - - let isManualEntry = recordingMethod == HealthConstants.RecordingMethod.manual.rawValue - let metadata: [String: Any] = [ - HKMetadataKeyWasUserEntered: NSNumber(value: isManualEntry) - ] - - guard let categoryType = HKSampleType.categoryType(forIdentifier: .mindfulSession) else { - throw PluginError(message: "Invalid Mindfulness Session Type") - } - - // The duration is tracked by the start and end dates, not by the value - let sample = HKCategorySample( - type: categoryType, - value: HKCategoryValue.notApplicable.rawValue, - start: dateFrom, - end: dateTo, - metadata: metadata - ) - - healthStore.save( - sample, - withCompletion: { (success, error) in - if let err = error { - print("Error Saving Mindfulness Session: \(err.localizedDescription)") - } - DispatchQueue.main.async { - result(success) - } - }) - } - - /// Writes workout data - /// - Parameters: - /// - call: Flutter method call - /// - result: Flutter result callback - func writeWorkoutData(call: FlutterMethodCall, result: @escaping FlutterResult) throws { - guard let arguments = call.arguments as? NSDictionary, - let activityType = (arguments["activityType"] as? String), - let startTime = (arguments["startTime"] as? NSNumber), - let endTime = (arguments["endTime"] as? NSNumber), - let activityTypeValue = workoutActivityTypeMap[activityType] - else { - throw PluginError( - message: "Invalid Arguments - activityType, startTime or endTime invalid") - } - - var totalEnergyBurned: HKQuantity? - var totalDistance: HKQuantity? = nil - - // Handle optional arguments - if let teb = (arguments["totalEnergyBurned"] as? Double) { - totalEnergyBurned = HKQuantity( - unit: unitDict[(arguments["totalEnergyBurnedUnit"] as! String)]!, doubleValue: teb) - } - if let td = (arguments["totalDistance"] as? Double) { - totalDistance = HKQuantity( - unit: unitDict[(arguments["totalDistanceUnit"] as! String)]!, doubleValue: td) - } - - let dateFrom = HealthUtilities.dateFromMilliseconds(startTime.doubleValue) - let dateTo = HealthUtilities.dateFromMilliseconds(endTime.doubleValue) - - let workout = HKWorkout( - activityType: activityTypeValue, - start: dateFrom, - end: dateTo, - duration: dateTo.timeIntervalSince(dateFrom), - totalEnergyBurned: totalEnergyBurned ?? nil, - totalDistance: totalDistance ?? nil, - metadata: nil - ) - - healthStore.save( - workout, - withCompletion: { (success, error) in - if let err = error { - print("Error Saving Workout. Sample: \(err.localizedDescription)") - } - DispatchQueue.main.async { - result(success) - } - }) - } -} diff --git a/packages/health/ios/Classes/HealthPlugin.h b/packages/health/ios/Classes/HealthPlugin.h deleted file mode 100644 index b56a4b64c..000000000 --- a/packages/health/ios/Classes/HealthPlugin.h +++ /dev/null @@ -1,4 +0,0 @@ -#import - -@interface HealthPlugin : NSObject -@end diff --git a/packages/health/ios/Classes/HealthPlugin.m b/packages/health/ios/Classes/HealthPlugin.m deleted file mode 100644 index 9341c739d..000000000 --- a/packages/health/ios/Classes/HealthPlugin.m +++ /dev/null @@ -1,8 +0,0 @@ -#import "HealthPlugin.h" -#import - -@implementation HealthPlugin -+ (void)registerWithRegistrar:(NSObject*)registrar { - [SwiftHealthPlugin registerWithRegistrar:registrar]; -} -@end diff --git a/packages/health/ios/Classes/HealthUtilities.swift b/packages/health/ios/Classes/HealthUtilities.swift deleted file mode 100644 index 6f4f961e9..000000000 --- a/packages/health/ios/Classes/HealthUtilities.swift +++ /dev/null @@ -1,153 +0,0 @@ -import HealthKit - -/// Utilities class containing helper methods for data manipulation -class HealthUtilities { - - /// Sanitize metadata to make it Flutter-friendly - /// - Parameter metadata: The metadata dictionary to sanitize - /// - Returns: A dictionary with sanitized values - static func sanitizeMetadata(_ metadata: [String: Any]?) -> [String: Any] { - guard let metadata = metadata else { return [:] } - - var sanitized = [String: Any]() - - for (key, value) in metadata { - switch value { - case let stringValue as String: - sanitized[key] = stringValue - case let numberValue as NSNumber: - sanitized[key] = numberValue - case let boolValue as Bool: - sanitized[key] = boolValue - case let arrayValue as [Any]: - sanitized[key] = sanitizeArray(arrayValue) - case let mapValue as [String: Any]: - sanitized[key] = sanitizeMetadata(mapValue) - default: - continue - } - } - - return sanitized - } - - /// Sanitize an array to make it Flutter-friendly - /// - Parameter array: The array to sanitize - /// - Returns: An array with sanitized values - static func sanitizeArray(_ array: [Any]) -> [Any] { - var sanitizedArray: [Any] = [] - - for value in array { - switch value { - case let stringValue as String: - sanitizedArray.append(stringValue) - case let numberValue as NSNumber: - sanitizedArray.append(numberValue) - case let boolValue as Bool: - sanitizedArray.append(boolValue) - case let arrayValue as [Any]: - sanitizedArray.append(sanitizeArray(arrayValue)) - case let mapValue as [String: Any]: - sanitizedArray.append(sanitizeMetadata(mapValue)) - default: - continue - } - } - - return sanitizedArray - } - - /// Convert milliseconds since epoch to Date - /// - Parameter milliseconds: Milliseconds since epoch - /// - Returns: Date object - static func dateFromMilliseconds(_ milliseconds: Double) -> Date { - return Date(timeIntervalSince1970: milliseconds / 1000) - } -} - -/// Extension to provide type conversion helpers for HKWorkoutActivityType -extension HKWorkoutActivityType { - /// Convert HKWorkoutActivityType to string - /// - Parameter type: The workout activity type - /// - Returns: String representation of the activity type - static func toString(_ type: HKWorkoutActivityType) -> String { - switch type { - case .americanFootball: return "americanFootball" - case .archery: return "archery" - case .australianFootball: return "australianFootball" - case .badminton: return "badminton" - case .baseball: return "baseball" - case .basketball: return "basketball" - case .bowling: return "bowling" - case .boxing: return "boxing" - case .climbing: return "climbing" - case .cricket: return "cricket" - case .crossTraining: return "crossTraining" - case .curling: return "curling" - case .cycling: return "cycling" - case .dance: return "dance" - case .danceInspiredTraining: return "danceInspiredTraining" - case .elliptical: return "elliptical" - case .equestrianSports: return "equestrianSports" - case .fencing: return "fencing" - case .fishing: return "fishing" - case .functionalStrengthTraining: return "functionalStrengthTraining" - case .golf: return "golf" - case .gymnastics: return "gymnastics" - case .handball: return "handball" - case .hiking: return "hiking" - case .hockey: return "hockey" - case .hunting: return "hunting" - case .lacrosse: return "lacrosse" - case .martialArts: return "martialArts" - case .mindAndBody: return "mindAndBody" - case .mixedMetabolicCardioTraining: return "mixedMetabolicCardioTraining" - case .paddleSports: return "paddleSports" - case .play: return "play" - case .preparationAndRecovery: return "preparationAndRecovery" - case .racquetball: return "racquetball" - case .rowing: return "rowing" - case .rugby: return "rugby" - case .running: return "running" - case .sailing: return "sailing" - case .skatingSports: return "skatingSports" - case .snowSports: return "snowSports" - case .soccer: return "soccer" - case .softball: return "softball" - case .squash: return "squash" - case .stairClimbing: return "stairClimbing" - case .surfingSports: return "surfingSports" - case .swimming: return "swimming" - case .tableTennis: return "tableTennis" - case .tennis: return "tennis" - case .trackAndField: return "trackAndField" - case .traditionalStrengthTraining: return "traditionalStrengthTraining" - case .volleyball: return "volleyball" - case .walking: return "walking" - case .waterFitness: return "waterFitness" - case .waterPolo: return "waterPolo" - case .waterSports: return "waterSports" - case .wrestling: return "wrestling" - case .yoga: return "yoga" - case .barre: return "barre" - case .coreTraining: return "coreTraining" - case .crossCountrySkiing: return "crossCountrySkiing" - case .downhillSkiing: return "downhillSkiing" - case .flexibility: return "flexibility" - case .highIntensityIntervalTraining: return "highIntensityIntervalTraining" - case .jumpRope: return "jumpRope" - case .kickboxing: return "kickboxing" - case .pilates: return "pilates" - case .snowboarding: return "snowboarding" - case .stairs: return "stairs" - case .stepTraining: return "stepTraining" - case .wheelchairWalkPace: return "wheelchairWalkPace" - case .wheelchairRunPace: return "wheelchairRunPace" - case .taiChi: return "taiChi" - case .mixedCardio: return "mixedCardio" - case .handCycling: return "handCycling" - case .underwaterDiving: return "underwaterDiving" - default: return "other" - } - } -} diff --git a/packages/health/ios/Classes/SwiftHealthPlugin.swift b/packages/health/ios/Classes/SwiftHealthPlugin.swift deleted file mode 100644 index d87e3cc09..000000000 --- a/packages/health/ios/Classes/SwiftHealthPlugin.swift +++ /dev/null @@ -1,635 +0,0 @@ -import Flutter -import HealthKit -import UIKit - -/// Main plugin class that coordinates health data operations -public class SwiftHealthPlugin: NSObject, FlutterPlugin { - - // Health store and type dictionaries - let healthStore = HKHealthStore() - var healthDataTypes = [HKSampleType]() - var healthDataQuantityTypes = [HKQuantityType]() - var characteristicsDataTypes = [HKCharacteristicType]() - var heartRateEventTypes = Set() - var headacheType = Set() - var allDataTypes = Set() - var dataTypesDict: [String: HKSampleType] = [:] - var dataQuantityTypesDict: [String: HKQuantityType] = [:] - var unitDict: [String: HKUnit] = [:] - var workoutActivityTypeMap: [String: HKWorkoutActivityType] = [:] - var characteristicsTypesDict: [String: HKCharacteristicType] = [:] - var nutritionList: [String] = [] - - // Service classes - private lazy var healthDataReader: HealthDataReader = { - return HealthDataReader( - healthStore: healthStore, - dataTypesDict: dataTypesDict, - dataQuantityTypesDict: dataQuantityTypesDict, - unitDict: unitDict, - workoutActivityTypeMap: workoutActivityTypeMap, - characteristicsTypesDict: characteristicsTypesDict - ) - }() - - private lazy var healthDataWriter: HealthDataWriter = { - return HealthDataWriter( - healthStore: healthStore, - dataTypesDict: dataTypesDict, - unitDict: unitDict, - workoutActivityTypeMap: workoutActivityTypeMap - ) - }() - - private lazy var healthDataOperations: HealthDataOperations = { - return HealthDataOperations( - healthStore: healthStore, - dataTypesDict: dataTypesDict, - characteristicsTypesDict: characteristicsTypesDict, - nutritionList: nutritionList - ) - }() - - public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel( - name: "flutter_health", binaryMessenger: registrar.messenger()) - let instance = SwiftHealthPlugin() - registrar.addMethodCallDelegate(instance, channel: channel) - } - - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - - initializeTypes() - - switch call.method { - case "checkIfHealthDataAvailable": - healthDataOperations.checkIfHealthDataAvailable(call: call, result: result) - - case "requestAuthorization": - do { - try healthDataOperations.requestAuthorization(call: call, result: result) - } catch { - result(FlutterError(code: "REQUEST_AUTH_ERROR", - message: "Error requesting authorization: \(error.localizedDescription)", - details: nil)) - } - - case "getData": - healthDataReader.getData(call: call, result: result) - - case "getDataByUUID": - healthDataReader.getDataByUUID(call: call, result: result) - - case "getIntervalData": - healthDataReader.getIntervalData(call: call, result: result) - - case "getTotalStepsInInterval": - healthDataReader.getTotalStepsInInterval(call: call, result: result) - - case "writeData": - do { - try healthDataWriter.writeData(call: call, result: result) - } catch { - result(FlutterError(code: "WRITE_ERROR", - message: "Error writing data: \(error.localizedDescription)", - details: nil)) - } - - case "writeAudiogram": - do { - try healthDataWriter.writeAudiogram(call: call, result: result) - } catch { - result(FlutterError(code: "WRITE_ERROR", - message: "Error writing audiogram: \(error.localizedDescription)", - details: nil)) - } - - case "writeBloodPressure": - do { - try healthDataWriter.writeBloodPressure(call: call, result: result) - } catch { - result(FlutterError(code: "WRITE_ERROR", - message: "Error writing blood pressure: \(error.localizedDescription)", - details: nil)) - } - - case "writeMeal": - do { - try healthDataWriter.writeMeal(call: call, result: result) - } catch { - result(FlutterError(code: "WRITE_ERROR", - message: "Error writing meal: \(error.localizedDescription)", - details: nil)) - } - - case "writeInsulinDelivery": - do { - try healthDataWriter.writeInsulinDelivery(call: call, result: result) - } catch { - result(FlutterError(code: "WRITE_ERROR", - message: "Error writing insulin delivery: \(error.localizedDescription)", - details: nil)) - } - - case "writeWorkoutData": - do { - try healthDataWriter.writeWorkoutData(call: call, result: result) - } catch { - result(FlutterError(code: "WRITE_ERROR", - message: "Error writing workout: \(error.localizedDescription)", - details: nil)) - } - - case "writeMenstruationFlow": - do { - try healthDataWriter.writeMenstruationFlow(call: call, result: result) - } catch { - result(FlutterError(code: "WRITE_ERROR", - message: "Error writing menstruation flow: \(error.localizedDescription)", - details: nil)) - } - - case "hasPermissions": - do { - try healthDataOperations.hasPermissions(call: call, result: result) - } catch { - result(FlutterError(code: "PERMISSION_ERROR", - message: "Error checking permissions: \(error.localizedDescription)", - details: nil)) - } - - case "delete": - do { - healthDataOperations.delete(call: call, result: result) - } catch { - result(FlutterError(code: "DELETE_ERROR", - message: "Error deleting data: \(error.localizedDescription)", - details: nil)) - } - - case "deleteByUUID": - do { - try healthDataOperations.deleteByUUID(call: call, result: result) - } catch { - result(FlutterError(code: "DELETE_ERROR", - message: "Error deleting data by UUID: \(error.localizedDescription)", - details: nil)) - } - - default: - result(FlutterMethodNotImplemented) - } - } - - /// Initialize all the health data types, unit dictionaries, and other required data structures - func initializeTypes() { - // init units - unitDict[HealthConstants.GRAM] = HKUnit.gram() - unitDict[HealthConstants.KILOGRAM] = HKUnit.gramUnit(with: .kilo) - unitDict[HealthConstants.OUNCE] = HKUnit.ounce() - unitDict[HealthConstants.POUND] = HKUnit.pound() - unitDict[HealthConstants.STONE] = HKUnit.stone() - unitDict[HealthConstants.METER] = HKUnit.meter() - unitDict[HealthConstants.INCH] = HKUnit.inch() - unitDict[HealthConstants.FOOT] = HKUnit.foot() - unitDict[HealthConstants.YARD] = HKUnit.yard() - unitDict[HealthConstants.MILE] = HKUnit.mile() - unitDict[HealthConstants.LITER] = HKUnit.liter() - unitDict[HealthConstants.MILLILITER] = HKUnit.literUnit(with: .milli) - unitDict[HealthConstants.FLUID_OUNCE_US] = HKUnit.fluidOunceUS() - unitDict[HealthConstants.FLUID_OUNCE_IMPERIAL] = HKUnit.fluidOunceImperial() - unitDict[HealthConstants.CUP_US] = HKUnit.cupUS() - unitDict[HealthConstants.CUP_IMPERIAL] = HKUnit.cupImperial() - unitDict[HealthConstants.PINT_US] = HKUnit.pintUS() - unitDict[HealthConstants.PINT_IMPERIAL] = HKUnit.pintImperial() - unitDict[HealthConstants.PASCAL] = HKUnit.pascal() - unitDict[HealthConstants.MILLIMETER_OF_MERCURY] = HKUnit.millimeterOfMercury() - unitDict[HealthConstants.CENTIMETER_OF_WATER] = HKUnit.centimeterOfWater() - unitDict[HealthConstants.ATMOSPHERE] = HKUnit.atmosphere() - unitDict[HealthConstants.DECIBEL_A_WEIGHTED_SOUND_PRESSURE_LEVEL] = HKUnit.decibelAWeightedSoundPressureLevel() - unitDict[HealthConstants.SECOND] = HKUnit.second() - unitDict[HealthConstants.MILLISECOND] = HKUnit.secondUnit(with: .milli) - unitDict[HealthConstants.MINUTE] = HKUnit.minute() - unitDict[HealthConstants.HOUR] = HKUnit.hour() - unitDict[HealthConstants.DAY] = HKUnit.day() - unitDict[HealthConstants.JOULE] = HKUnit.joule() - unitDict[HealthConstants.KILOCALORIE] = HKUnit.kilocalorie() - unitDict[HealthConstants.LARGE_CALORIE] = HKUnit.largeCalorie() - unitDict[HealthConstants.SMALL_CALORIE] = HKUnit.smallCalorie() - unitDict[HealthConstants.DEGREE_CELSIUS] = HKUnit.degreeCelsius() - unitDict[HealthConstants.DEGREE_FAHRENHEIT] = HKUnit.degreeFahrenheit() - unitDict[HealthConstants.KELVIN] = HKUnit.kelvin() - unitDict[HealthConstants.DECIBEL_HEARING_LEVEL] = HKUnit.decibelHearingLevel() - unitDict[HealthConstants.HERTZ] = HKUnit.hertz() - unitDict[HealthConstants.SIEMEN] = HKUnit.siemen() - unitDict[HealthConstants.INTERNATIONAL_UNIT] = HKUnit.internationalUnit() - unitDict[HealthConstants.COUNT] = HKUnit.count() - unitDict[HealthConstants.PERCENT] = HKUnit.percent() - unitDict[HealthConstants.BEATS_PER_MINUTE] = HKUnit.init(from: "count/min") - unitDict[HealthConstants.RESPIRATIONS_PER_MINUTE] = HKUnit.init(from: "count/min") - unitDict[HealthConstants.MILLIGRAM_PER_DECILITER] = HKUnit.init(from: "mg/dL") - unitDict[HealthConstants.METER_PER_SECOND] = HKUnit.init(from: "m/s") - unitDict[HealthConstants.UNKNOWN_UNIT] = HKUnit.init(from: "") - unitDict[HealthConstants.NO_UNIT] = HKUnit.init(from: "") - - // init workout activity types - initializeWorkoutTypes() - - nutritionList = [ - HealthConstants.DIETARY_ENERGY_CONSUMED, - HealthConstants.DIETARY_CARBS_CONSUMED, - HealthConstants.DIETARY_PROTEIN_CONSUMED, - HealthConstants.DIETARY_FATS_CONSUMED, - HealthConstants.DIETARY_CAFFEINE, - HealthConstants.DIETARY_FIBER, - HealthConstants.DIETARY_SUGAR, - HealthConstants.DIETARY_FAT_MONOUNSATURATED, - HealthConstants.DIETARY_FAT_POLYUNSATURATED, - HealthConstants.DIETARY_FAT_SATURATED, - HealthConstants.DIETARY_CHOLESTEROL, - HealthConstants.DIETARY_VITAMIN_A, - HealthConstants.DIETARY_THIAMIN, - HealthConstants.DIETARY_RIBOFLAVIN, - HealthConstants.DIETARY_NIACIN, - HealthConstants.DIETARY_PANTOTHENIC_ACID, - HealthConstants.DIETARY_VITAMIN_B6, - HealthConstants.DIETARY_BIOTIN, - HealthConstants.DIETARY_VITAMIN_B12, - HealthConstants.DIETARY_VITAMIN_C, - HealthConstants.DIETARY_VITAMIN_D, - HealthConstants.DIETARY_VITAMIN_E, - HealthConstants.DIETARY_VITAMIN_K, - HealthConstants.DIETARY_FOLATE, - HealthConstants.DIETARY_CALCIUM, - HealthConstants.DIETARY_CHLORIDE, - HealthConstants.DIETARY_IRON, - HealthConstants.DIETARY_MAGNESIUM, - HealthConstants.DIETARY_PHOSPHORUS, - HealthConstants.DIETARY_POTASSIUM, - HealthConstants.DIETARY_SODIUM, - HealthConstants.DIETARY_ZINC, - HealthConstants.DIETARY_WATER, - HealthConstants.DIETARY_CHROMIUM, - HealthConstants.DIETARY_COPPER, - HealthConstants.DIETARY_IODINE, - HealthConstants.DIETARY_MANGANESE, - HealthConstants.DIETARY_MOLYBDENUM, - HealthConstants.DIETARY_SELENIUM, - ] - - // Set up iOS 11 specific types (ordinary health data quantity types) - if #available(iOS 11.0, *) { - initializeIOS11Types() - healthDataQuantityTypes = Array(dataQuantityTypesDict.values) - } - - // Set up heart rate data types specific to the apple watch, requires iOS 12 - if #available(iOS 12.2, *) { - initializeIOS12Types() - } - - // Set up iOS 13 specific types (ordinary health data types) - if #available(iOS 13.0, *) { - initializeIOS13Types() - healthDataTypes = Array(dataTypesDict.values) - characteristicsDataTypes = Array(characteristicsTypesDict.values) - } - - if #available(iOS 13.6, *) { - initializeIOS13_6Types() - } - - if #available(iOS 14.0, *) { - initializeIOS14Types() - } - - if #available(iOS 16.0, *) { - initializeIOS16Types() - } - - // Concatenate heart events, headache and health data types (both may be empty) - allDataTypes = Set(heartRateEventTypes + healthDataTypes) - allDataTypes = allDataTypes.union(headacheType) - } - - /// Initialize iOS 11 specific data types - @available(iOS 11.0, *) - private func initializeIOS11Types() { - dataTypesDict[HealthConstants.APPLE_STAND_HOUR] = HKSampleType.categoryType(forIdentifier: .appleStandHour)! - - dataQuantityTypesDict[HealthConstants.ACTIVE_ENERGY_BURNED] = HKQuantityType.quantityType(forIdentifier: .activeEnergyBurned)! - dataQuantityTypesDict[HealthConstants.BASAL_ENERGY_BURNED] = HKQuantityType.quantityType(forIdentifier: .basalEnergyBurned)! - dataQuantityTypesDict[HealthConstants.BLOOD_GLUCOSE] = HKQuantityType.quantityType(forIdentifier: .bloodGlucose)! - dataQuantityTypesDict[HealthConstants.BLOOD_OXYGEN] = HKQuantityType.quantityType(forIdentifier: .oxygenSaturation)! - dataQuantityTypesDict[HealthConstants.BLOOD_PRESSURE_DIASTOLIC] = HKQuantityType.quantityType(forIdentifier: .bloodPressureDiastolic)! - dataQuantityTypesDict[HealthConstants.BLOOD_PRESSURE_SYSTOLIC] = HKQuantityType.quantityType(forIdentifier: .bloodPressureSystolic)! - dataQuantityTypesDict[HealthConstants.BODY_FAT_PERCENTAGE] = HKQuantityType.quantityType(forIdentifier: .bodyFatPercentage)! - dataQuantityTypesDict[HealthConstants.LEAN_BODY_MASS] = HKSampleType.quantityType(forIdentifier: .leanBodyMass)! - dataQuantityTypesDict[HealthConstants.BODY_MASS_INDEX] = HKQuantityType.quantityType(forIdentifier: .bodyMassIndex)! - dataQuantityTypesDict[HealthConstants.BODY_TEMPERATURE] = HKQuantityType.quantityType(forIdentifier: .bodyTemperature)! - - // Initialize nutrition quantity types - initializeNutritionQuantityTypes() - - dataQuantityTypesDict[HealthConstants.ELECTRODERMAL_ACTIVITY] = HKQuantityType.quantityType(forIdentifier: .electrodermalActivity)! - dataQuantityTypesDict[HealthConstants.FORCED_EXPIRATORY_VOLUME] = HKQuantityType.quantityType(forIdentifier: .forcedExpiratoryVolume1)! - dataQuantityTypesDict[HealthConstants.HEART_RATE] = HKQuantityType.quantityType(forIdentifier: .heartRate)! - dataQuantityTypesDict[HealthConstants.HEART_RATE_VARIABILITY_SDNN] = HKQuantityType.quantityType(forIdentifier: .heartRateVariabilitySDNN)! - dataQuantityTypesDict[HealthConstants.HEIGHT] = HKQuantityType.quantityType(forIdentifier: .height)! - dataQuantityTypesDict[HealthConstants.RESTING_HEART_RATE] = HKQuantityType.quantityType(forIdentifier: .restingHeartRate)! - dataQuantityTypesDict[HealthConstants.STEPS] = HKQuantityType.quantityType(forIdentifier: .stepCount)! - dataQuantityTypesDict[HealthConstants.WAIST_CIRCUMFERENCE] = HKQuantityType.quantityType(forIdentifier: .waistCircumference)! - dataQuantityTypesDict[HealthConstants.WALKING_HEART_RATE] = HKQuantityType.quantityType(forIdentifier: .walkingHeartRateAverage)! - dataQuantityTypesDict[HealthConstants.WEIGHT] = HKQuantityType.quantityType(forIdentifier: .bodyMass)! - dataQuantityTypesDict[HealthConstants.DISTANCE_WALKING_RUNNING] = HKQuantityType.quantityType(forIdentifier: .distanceWalkingRunning)! - dataQuantityTypesDict[HealthConstants.DISTANCE_SWIMMING] = HKQuantityType.quantityType(forIdentifier: .distanceSwimming)! - dataQuantityTypesDict[HealthConstants.DISTANCE_CYCLING] = HKQuantityType.quantityType(forIdentifier: .distanceCycling)! - dataQuantityTypesDict[HealthConstants.FLIGHTS_CLIMBED] = HKQuantityType.quantityType(forIdentifier: .flightsClimbed)! - } - - /// Initialize nutrition quantity types - @available(iOS 11.0, *) - private func initializeNutritionQuantityTypes() { - dataQuantityTypesDict[HealthConstants.DIETARY_CARBS_CONSUMED] = HKSampleType.quantityType(forIdentifier: .dietaryCarbohydrates)! - dataQuantityTypesDict[HealthConstants.DIETARY_CAFFEINE] = HKSampleType.quantityType(forIdentifier: .dietaryCaffeine)! - dataQuantityTypesDict[HealthConstants.DIETARY_ENERGY_CONSUMED] = HKSampleType.quantityType(forIdentifier: .dietaryEnergyConsumed)! - dataQuantityTypesDict[HealthConstants.DIETARY_FATS_CONSUMED] = HKSampleType.quantityType(forIdentifier: .dietaryFatTotal)! - dataQuantityTypesDict[HealthConstants.DIETARY_PROTEIN_CONSUMED] = HKSampleType.quantityType(forIdentifier: .dietaryProtein)! - dataQuantityTypesDict[HealthConstants.DIETARY_FIBER] = HKSampleType.quantityType(forIdentifier: .dietaryFiber)! - dataQuantityTypesDict[HealthConstants.DIETARY_SUGAR] = HKSampleType.quantityType(forIdentifier: .dietarySugar)! - dataQuantityTypesDict[HealthConstants.DIETARY_FAT_MONOUNSATURATED] = HKSampleType.quantityType(forIdentifier: .dietaryFatMonounsaturated)! - dataQuantityTypesDict[HealthConstants.DIETARY_FAT_POLYUNSATURATED] = HKSampleType.quantityType(forIdentifier: .dietaryFatPolyunsaturated)! - dataQuantityTypesDict[HealthConstants.DIETARY_FAT_SATURATED] = HKSampleType.quantityType(forIdentifier: .dietaryFatSaturated)! - dataQuantityTypesDict[HealthConstants.DIETARY_CHOLESTEROL] = HKSampleType.quantityType(forIdentifier: .dietaryCholesterol)! - dataQuantityTypesDict[HealthConstants.DIETARY_VITAMIN_A] = HKSampleType.quantityType(forIdentifier: .dietaryVitaminA)! - dataQuantityTypesDict[HealthConstants.DIETARY_THIAMIN] = HKSampleType.quantityType(forIdentifier: .dietaryThiamin)! - dataQuantityTypesDict[HealthConstants.DIETARY_RIBOFLAVIN] = HKSampleType.quantityType(forIdentifier: .dietaryRiboflavin)! - dataQuantityTypesDict[HealthConstants.DIETARY_NIACIN] = HKSampleType.quantityType(forIdentifier: .dietaryNiacin)! - dataQuantityTypesDict[HealthConstants.DIETARY_PANTOTHENIC_ACID] = HKSampleType.quantityType(forIdentifier: .dietaryPantothenicAcid)! - dataQuantityTypesDict[HealthConstants.DIETARY_VITAMIN_B6] = HKSampleType.quantityType(forIdentifier: .dietaryVitaminB6)! - dataQuantityTypesDict[HealthConstants.DIETARY_BIOTIN] = HKSampleType.quantityType(forIdentifier: .dietaryBiotin)! - dataQuantityTypesDict[HealthConstants.DIETARY_VITAMIN_B12] = HKSampleType.quantityType(forIdentifier: .dietaryVitaminB12)! - dataQuantityTypesDict[HealthConstants.DIETARY_VITAMIN_C] = HKSampleType.quantityType(forIdentifier: .dietaryVitaminC)! - dataQuantityTypesDict[HealthConstants.DIETARY_VITAMIN_D] = HKSampleType.quantityType(forIdentifier: .dietaryVitaminD)! - dataQuantityTypesDict[HealthConstants.DIETARY_VITAMIN_E] = HKSampleType.quantityType(forIdentifier: .dietaryVitaminE)! - dataQuantityTypesDict[HealthConstants.DIETARY_VITAMIN_K] = HKSampleType.quantityType(forIdentifier: .dietaryVitaminK)! - dataQuantityTypesDict[HealthConstants.DIETARY_FOLATE] = HKSampleType.quantityType(forIdentifier: .dietaryFolate)! - dataQuantityTypesDict[HealthConstants.DIETARY_CALCIUM] = HKSampleType.quantityType(forIdentifier: .dietaryCalcium)! - dataQuantityTypesDict[HealthConstants.DIETARY_CHLORIDE] = HKSampleType.quantityType(forIdentifier: .dietaryChloride)! - dataQuantityTypesDict[HealthConstants.DIETARY_IRON] = HKSampleType.quantityType(forIdentifier: .dietaryIron)! - dataQuantityTypesDict[HealthConstants.DIETARY_MAGNESIUM] = HKSampleType.quantityType(forIdentifier: .dietaryMagnesium)! - dataQuantityTypesDict[HealthConstants.DIETARY_PHOSPHORUS] = HKSampleType.quantityType(forIdentifier: .dietaryPhosphorus)! - dataQuantityTypesDict[HealthConstants.DIETARY_POTASSIUM] = HKSampleType.quantityType(forIdentifier: .dietaryPotassium)! - dataQuantityTypesDict[HealthConstants.DIETARY_SODIUM] = HKSampleType.quantityType(forIdentifier: .dietarySodium)! - dataQuantityTypesDict[HealthConstants.DIETARY_ZINC] = HKSampleType.quantityType(forIdentifier: .dietaryZinc)! - dataQuantityTypesDict[HealthConstants.DIETARY_WATER] = HKSampleType.quantityType(forIdentifier: .dietaryWater)! - dataQuantityTypesDict[HealthConstants.DIETARY_CHROMIUM] = HKSampleType.quantityType(forIdentifier: .dietaryChromium)! - dataQuantityTypesDict[HealthConstants.DIETARY_COPPER] = HKSampleType.quantityType(forIdentifier: .dietaryCopper)! - dataQuantityTypesDict[HealthConstants.DIETARY_IODINE] = HKSampleType.quantityType(forIdentifier: .dietaryIodine)! - dataQuantityTypesDict[HealthConstants.DIETARY_MANGANESE] = HKSampleType.quantityType(forIdentifier: .dietaryManganese)! - dataQuantityTypesDict[HealthConstants.DIETARY_MOLYBDENUM] = HKSampleType.quantityType(forIdentifier: .dietaryMolybdenum)! - dataQuantityTypesDict[HealthConstants.DIETARY_SELENIUM] = HKSampleType.quantityType(forIdentifier: .dietarySelenium)! - } - - /// Initialize iOS 13 specific data types - @available(iOS 13.0, *) - private func initializeIOS13Types() { - dataTypesDict[HealthConstants.ACTIVE_ENERGY_BURNED] = HKSampleType.quantityType(forIdentifier: .activeEnergyBurned)! - dataTypesDict[HealthConstants.APPLE_STAND_TIME] = HKSampleType.quantityType(forIdentifier: .appleStandTime)! - dataTypesDict[HealthConstants.AUDIOGRAM] = HKSampleType.audiogramSampleType() - dataTypesDict[HealthConstants.BASAL_ENERGY_BURNED] = HKSampleType.quantityType(forIdentifier: .basalEnergyBurned)! - dataTypesDict[HealthConstants.BLOOD_GLUCOSE] = HKSampleType.quantityType(forIdentifier: .bloodGlucose)! - dataTypesDict[HealthConstants.BLOOD_OXYGEN] = HKSampleType.quantityType(forIdentifier: .oxygenSaturation)! - dataTypesDict[HealthConstants.RESPIRATORY_RATE] = HKSampleType.quantityType(forIdentifier: .respiratoryRate)! - dataTypesDict[HealthConstants.PERIPHERAL_PERFUSION_INDEX] = HKSampleType.quantityType(forIdentifier: .peripheralPerfusionIndex)! - - dataTypesDict[HealthConstants.BLOOD_PRESSURE_DIASTOLIC] = HKSampleType.quantityType(forIdentifier: .bloodPressureDiastolic)! - dataTypesDict[HealthConstants.BLOOD_PRESSURE_SYSTOLIC] = HKSampleType.quantityType(forIdentifier: .bloodPressureSystolic)! - dataTypesDict[HealthConstants.BODY_FAT_PERCENTAGE] = HKSampleType.quantityType(forIdentifier: .bodyFatPercentage)! - dataTypesDict[HealthConstants.LEAN_BODY_MASS] = HKSampleType.quantityType(forIdentifier: .leanBodyMass)! - dataTypesDict[HealthConstants.BODY_MASS_INDEX] = HKSampleType.quantityType(forIdentifier: .bodyMassIndex)! - dataTypesDict[HealthConstants.BODY_TEMPERATURE] = HKSampleType.quantityType(forIdentifier: .bodyTemperature)! - - // Initialize nutrition types - initializeNutritionTypes() - - dataTypesDict[HealthConstants.ELECTRODERMAL_ACTIVITY] = HKSampleType.quantityType(forIdentifier: .electrodermalActivity)! - dataTypesDict[HealthConstants.FORCED_EXPIRATORY_VOLUME] = HKSampleType.quantityType(forIdentifier: .forcedExpiratoryVolume1)! - dataTypesDict[HealthConstants.HEART_RATE] = HKSampleType.quantityType(forIdentifier: .heartRate)! - dataTypesDict[HealthConstants.HEART_RATE_VARIABILITY_SDNN] = HKSampleType.quantityType(forIdentifier: .heartRateVariabilitySDNN)! - dataTypesDict[HealthConstants.HEIGHT] = HKSampleType.quantityType(forIdentifier: .height)! - dataTypesDict[HealthConstants.INSULIN_DELIVERY] = HKSampleType.quantityType(forIdentifier: .insulinDelivery)! - dataTypesDict[HealthConstants.RESTING_HEART_RATE] = HKSampleType.quantityType(forIdentifier: .restingHeartRate)! - dataTypesDict[HealthConstants.STEPS] = HKSampleType.quantityType(forIdentifier: .stepCount)! - dataTypesDict[HealthConstants.WAIST_CIRCUMFERENCE] = HKSampleType.quantityType(forIdentifier: .waistCircumference)! - dataTypesDict[HealthConstants.WALKING_HEART_RATE] = HKSampleType.quantityType(forIdentifier: .walkingHeartRateAverage)! - dataTypesDict[HealthConstants.WEIGHT] = HKSampleType.quantityType(forIdentifier: .bodyMass)! - dataTypesDict[HealthConstants.DISTANCE_WALKING_RUNNING] = HKSampleType.quantityType(forIdentifier: .distanceWalkingRunning)! - dataTypesDict[HealthConstants.DISTANCE_SWIMMING] = HKSampleType.quantityType(forIdentifier: .distanceSwimming)! - dataTypesDict[HealthConstants.DISTANCE_CYCLING] = HKSampleType.quantityType(forIdentifier: .distanceCycling)! - dataTypesDict[HealthConstants.FLIGHTS_CLIMBED] = HKSampleType.quantityType(forIdentifier: .flightsClimbed)! - dataTypesDict[HealthConstants.MINDFULNESS] = HKSampleType.categoryType(forIdentifier: .mindfulSession)! - dataTypesDict[HealthConstants.SLEEP_AWAKE] = HKSampleType.categoryType(forIdentifier: .sleepAnalysis)! - dataTypesDict[HealthConstants.SLEEP_DEEP] = HKSampleType.categoryType(forIdentifier: .sleepAnalysis)! - dataTypesDict[HealthConstants.SLEEP_IN_BED] = HKSampleType.categoryType(forIdentifier: .sleepAnalysis)! - dataTypesDict[HealthConstants.SLEEP_LIGHT] = HKSampleType.categoryType(forIdentifier: .sleepAnalysis)! - dataTypesDict[HealthConstants.SLEEP_REM] = HKSampleType.categoryType(forIdentifier: .sleepAnalysis)! - dataTypesDict[HealthConstants.SLEEP_ASLEEP] = HKSampleType.categoryType(forIdentifier: .sleepAnalysis)! - dataTypesDict[HealthConstants.MENSTRUATION_FLOW] = HKSampleType.categoryType(forIdentifier: .menstrualFlow)! - - dataTypesDict[HealthConstants.EXERCISE_TIME] = HKSampleType.quantityType(forIdentifier: .appleExerciseTime)! - dataTypesDict[HealthConstants.WORKOUT] = HKSampleType.workoutType() - dataTypesDict[HealthConstants.NUTRITION] = HKSampleType.correlationType(forIdentifier: .food)! - - characteristicsTypesDict[HealthConstants.BIRTH_DATE] = HKObjectType.characteristicType(forIdentifier: .dateOfBirth)! - characteristicsTypesDict[HealthConstants.GENDER] = HKObjectType.characteristicType(forIdentifier: .biologicalSex)! - characteristicsTypesDict[HealthConstants.BLOOD_TYPE] = HKObjectType.characteristicType(forIdentifier: .bloodType)! - } - - /// Initialize nutrition types for iOS 13+ - @available(iOS 13.0, *) - private func initializeNutritionTypes() { - dataTypesDict[HealthConstants.DIETARY_CARBS_CONSUMED] = HKSampleType.quantityType(forIdentifier: .dietaryCarbohydrates)! - dataTypesDict[HealthConstants.DIETARY_CAFFEINE] = HKSampleType.quantityType(forIdentifier: .dietaryCaffeine)! - dataTypesDict[HealthConstants.DIETARY_ENERGY_CONSUMED] = HKSampleType.quantityType(forIdentifier: .dietaryEnergyConsumed)! - dataTypesDict[HealthConstants.DIETARY_FATS_CONSUMED] = HKSampleType.quantityType(forIdentifier: .dietaryFatTotal)! - dataTypesDict[HealthConstants.DIETARY_PROTEIN_CONSUMED] = HKSampleType.quantityType(forIdentifier: .dietaryProtein)! - dataTypesDict[HealthConstants.DIETARY_FIBER] = HKSampleType.quantityType(forIdentifier: .dietaryFiber)! - dataTypesDict[HealthConstants.DIETARY_SUGAR] = HKSampleType.quantityType(forIdentifier: .dietarySugar)! - dataTypesDict[HealthConstants.DIETARY_FAT_MONOUNSATURATED] = HKSampleType.quantityType(forIdentifier: .dietaryFatMonounsaturated)! - dataTypesDict[HealthConstants.DIETARY_FAT_POLYUNSATURATED] = HKSampleType.quantityType(forIdentifier: .dietaryFatPolyunsaturated)! - dataTypesDict[HealthConstants.DIETARY_FAT_SATURATED] = HKSampleType.quantityType(forIdentifier: .dietaryFatSaturated)! - dataTypesDict[HealthConstants.DIETARY_CHOLESTEROL] = HKSampleType.quantityType(forIdentifier: .dietaryCholesterol)! - dataTypesDict[HealthConstants.DIETARY_VITAMIN_A] = HKSampleType.quantityType(forIdentifier: .dietaryVitaminA)! - dataTypesDict[HealthConstants.DIETARY_THIAMIN] = HKSampleType.quantityType(forIdentifier: .dietaryThiamin)! - dataTypesDict[HealthConstants.DIETARY_RIBOFLAVIN] = HKSampleType.quantityType(forIdentifier: .dietaryRiboflavin)! - dataTypesDict[HealthConstants.DIETARY_NIACIN] = HKSampleType.quantityType(forIdentifier: .dietaryNiacin)! - dataTypesDict[HealthConstants.DIETARY_PANTOTHENIC_ACID] = HKSampleType.quantityType(forIdentifier: .dietaryPantothenicAcid)! - dataTypesDict[HealthConstants.DIETARY_VITAMIN_B6] = HKSampleType.quantityType(forIdentifier: .dietaryVitaminB6)! - dataTypesDict[HealthConstants.DIETARY_BIOTIN] = HKSampleType.quantityType(forIdentifier: .dietaryBiotin)! - dataTypesDict[HealthConstants.DIETARY_VITAMIN_B12] = HKSampleType.quantityType(forIdentifier: .dietaryVitaminB12)! - dataTypesDict[HealthConstants.DIETARY_VITAMIN_C] = HKSampleType.quantityType(forIdentifier: .dietaryVitaminC)! - dataTypesDict[HealthConstants.DIETARY_VITAMIN_D] = HKSampleType.quantityType(forIdentifier: .dietaryVitaminD)! - dataTypesDict[HealthConstants.DIETARY_VITAMIN_E] = HKSampleType.quantityType(forIdentifier: .dietaryVitaminE)! - dataTypesDict[HealthConstants.DIETARY_VITAMIN_K] = HKSampleType.quantityType(forIdentifier: .dietaryVitaminK)! - dataTypesDict[HealthConstants.DIETARY_FOLATE] = HKSampleType.quantityType(forIdentifier: .dietaryFolate)! - dataTypesDict[HealthConstants.DIETARY_CALCIUM] = HKSampleType.quantityType(forIdentifier: .dietaryCalcium)! - dataTypesDict[HealthConstants.DIETARY_CHLORIDE] = HKSampleType.quantityType(forIdentifier: .dietaryChloride)! - dataTypesDict[HealthConstants.DIETARY_IRON] = HKSampleType.quantityType(forIdentifier: .dietaryIron)! - dataTypesDict[HealthConstants.DIETARY_MAGNESIUM] = HKSampleType.quantityType(forIdentifier: .dietaryMagnesium)! - dataTypesDict[HealthConstants.DIETARY_PHOSPHORUS] = HKSampleType.quantityType(forIdentifier: .dietaryPhosphorus)! - dataTypesDict[HealthConstants.DIETARY_POTASSIUM] = HKSampleType.quantityType(forIdentifier: .dietaryPotassium)! - dataTypesDict[HealthConstants.DIETARY_SODIUM] = HKSampleType.quantityType(forIdentifier: .dietarySodium)! - dataTypesDict[HealthConstants.DIETARY_ZINC] = HKSampleType.quantityType(forIdentifier: .dietaryZinc)! - dataTypesDict[HealthConstants.DIETARY_WATER] = HKSampleType.quantityType(forIdentifier: .dietaryWater)! - dataTypesDict[HealthConstants.DIETARY_CHROMIUM] = HKSampleType.quantityType(forIdentifier: .dietaryChromium)! - dataTypesDict[HealthConstants.DIETARY_COPPER] = HKSampleType.quantityType(forIdentifier: .dietaryCopper)! - dataTypesDict[HealthConstants.DIETARY_IODINE] = HKSampleType.quantityType(forIdentifier: .dietaryIodine)! - dataTypesDict[HealthConstants.DIETARY_MANGANESE] = HKSampleType.quantityType(forIdentifier: .dietaryManganese)! - dataTypesDict[HealthConstants.DIETARY_MOLYBDENUM] = HKSampleType.quantityType(forIdentifier: .dietaryMolybdenum)! - dataTypesDict[HealthConstants.DIETARY_SELENIUM] = HKSampleType.quantityType(forIdentifier: .dietarySelenium)! - } - - /// Initialize iOS 12 specific data types - @available(iOS 12.2, *) - private func initializeIOS12Types() { - dataTypesDict[HealthConstants.HIGH_HEART_RATE_EVENT] = HKSampleType.categoryType(forIdentifier: .highHeartRateEvent)! - dataTypesDict[HealthConstants.LOW_HEART_RATE_EVENT] = HKSampleType.categoryType(forIdentifier: .lowHeartRateEvent)! - dataTypesDict[HealthConstants.IRREGULAR_HEART_RATE_EVENT] = HKSampleType.categoryType(forIdentifier: .irregularHeartRhythmEvent)! - - heartRateEventTypes = Set([ - HKSampleType.categoryType(forIdentifier: .highHeartRateEvent)!, - HKSampleType.categoryType(forIdentifier: .lowHeartRateEvent)!, - HKSampleType.categoryType(forIdentifier: .irregularHeartRhythmEvent)!, - ]) - } - - /// Initialize iOS 13.6 specific data types - @available(iOS 13.6, *) - private func initializeIOS13_6Types() { - dataTypesDict[HealthConstants.HEADACHE_UNSPECIFIED] = HKSampleType.categoryType(forIdentifier: .headache)! - dataTypesDict[HealthConstants.HEADACHE_NOT_PRESENT] = HKSampleType.categoryType(forIdentifier: .headache)! - dataTypesDict[HealthConstants.HEADACHE_MILD] = HKSampleType.categoryType(forIdentifier: .headache)! - dataTypesDict[HealthConstants.HEADACHE_MODERATE] = HKSampleType.categoryType(forIdentifier: .headache)! - dataTypesDict[HealthConstants.HEADACHE_SEVERE] = HKSampleType.categoryType(forIdentifier: .headache)! - - headacheType = Set([ - HKSampleType.categoryType(forIdentifier: .headache)! - ]) - } - - /// Initialize iOS 14 specific data types - @available(iOS 14.0, *) - private func initializeIOS14Types() { - dataTypesDict[HealthConstants.ELECTROCARDIOGRAM] = HKSampleType.electrocardiogramType() - dataTypesDict[HealthConstants.WALKING_SPEED] = HKSampleType.quantityType(forIdentifier: .walkingSpeed) - - unitDict[HealthConstants.VOLT] = HKUnit.volt() - unitDict[HealthConstants.INCHES_OF_MERCURY] = HKUnit.inchesOfMercury() - - workoutActivityTypeMap["CARDIO_DANCE"] = HKWorkoutActivityType.cardioDance - workoutActivityTypeMap["SOCIAL_DANCE"] = HKWorkoutActivityType.socialDance - workoutActivityTypeMap["PICKLEBALL"] = HKWorkoutActivityType.pickleball - workoutActivityTypeMap["COOLDOWN"] = HKWorkoutActivityType.cooldown - - if #available(iOS 14.5, *) { - dataTypesDict[HealthConstants.APPLE_MOVE_TIME] = HKSampleType.quantityType(forIdentifier: .appleMoveTime)! - } - } - - /// Initialize iOS 16 specific data types - @available(iOS 16.0, *) - private func initializeIOS16Types() { - dataTypesDict[HealthConstants.ATRIAL_FIBRILLATION_BURDEN] = HKQuantityType.quantityType(forIdentifier: .atrialFibrillationBurden)! - dataTypesDict[HealthConstants.WATER_TEMPERATURE] = HKQuantityType.quantityType(forIdentifier: .waterTemperature)! - dataTypesDict[HealthConstants.UNDERWATER_DEPTH] = HKQuantityType.quantityType(forIdentifier: .underwaterDepth)! - dataTypesDict[HealthConstants.UV_INDEX] = HKSampleType.quantityType(forIdentifier: .uvExposure)! - - dataQuantityTypesDict[HealthConstants.UV_INDEX] = HKQuantityType.quantityType(forIdentifier: .uvExposure)! - } - - /// Initialize workout activity types - private func initializeWorkoutTypes() { - workoutActivityTypeMap["ARCHERY"] = .archery - workoutActivityTypeMap["BOWLING"] = .bowling - workoutActivityTypeMap["FENCING"] = .fencing - workoutActivityTypeMap["GYMNASTICS"] = .gymnastics - workoutActivityTypeMap["TRACK_AND_FIELD"] = .trackAndField - workoutActivityTypeMap["AMERICAN_FOOTBALL"] = .americanFootball - workoutActivityTypeMap["AUSTRALIAN_FOOTBALL"] = .australianFootball - workoutActivityTypeMap["BASEBALL"] = .baseball - workoutActivityTypeMap["BASKETBALL"] = .basketball - workoutActivityTypeMap["CRICKET"] = .cricket - workoutActivityTypeMap["DISC_SPORTS"] = .discSports - workoutActivityTypeMap["HANDBALL"] = .handball - workoutActivityTypeMap["HOCKEY"] = .hockey - workoutActivityTypeMap["LACROSSE"] = .lacrosse - workoutActivityTypeMap["RUGBY"] = .rugby - workoutActivityTypeMap["SOCCER"] = .soccer - workoutActivityTypeMap["SOFTBALL"] = .softball - workoutActivityTypeMap["VOLLEYBALL"] = .volleyball - workoutActivityTypeMap["PREPARATION_AND_RECOVERY"] = .preparationAndRecovery - workoutActivityTypeMap["FLEXIBILITY"] = .flexibility - workoutActivityTypeMap["WALKING"] = .walking - workoutActivityTypeMap["RUNNING"] = .running - workoutActivityTypeMap["RUNNING_TREADMILL"] = .running // Supported due to combining with Android naming - workoutActivityTypeMap["WHEELCHAIR_WALK_PACE"] = .wheelchairWalkPace - workoutActivityTypeMap["WHEELCHAIR_RUN_PACE"] = .wheelchairRunPace - workoutActivityTypeMap["BIKING"] = .cycling - workoutActivityTypeMap["HAND_CYCLING"] = .handCycling - workoutActivityTypeMap["CORE_TRAINING"] = .coreTraining - workoutActivityTypeMap["ELLIPTICAL"] = .elliptical - workoutActivityTypeMap["FUNCTIONAL_STRENGTH_TRAINING"] = .functionalStrengthTraining - workoutActivityTypeMap["TRADITIONAL_STRENGTH_TRAINING"] = .traditionalStrengthTraining - workoutActivityTypeMap["CROSS_TRAINING"] = .crossTraining - workoutActivityTypeMap["MIXED_CARDIO"] = .mixedCardio - workoutActivityTypeMap["HIGH_INTENSITY_INTERVAL_TRAINING"] = .highIntensityIntervalTraining - workoutActivityTypeMap["JUMP_ROPE"] = .jumpRope - workoutActivityTypeMap["STAIR_CLIMBING"] = .stairClimbing - workoutActivityTypeMap["STAIRS"] = .stairs - workoutActivityTypeMap["STEP_TRAINING"] = .stepTraining - workoutActivityTypeMap["FITNESS_GAMING"] = .fitnessGaming - workoutActivityTypeMap["BARRE"] = .barre - workoutActivityTypeMap["YOGA"] = .yoga - workoutActivityTypeMap["MIND_AND_BODY"] = .mindAndBody - workoutActivityTypeMap["PILATES"] = .pilates - workoutActivityTypeMap["BADMINTON"] = .badminton - workoutActivityTypeMap["RACQUETBALL"] = .racquetball - workoutActivityTypeMap["SQUASH"] = .squash - workoutActivityTypeMap["TABLE_TENNIS"] = .tableTennis - workoutActivityTypeMap["TENNIS"] = .tennis - workoutActivityTypeMap["CLIMBING"] = .climbing - workoutActivityTypeMap["ROCK_CLIMBING"] = .climbing // Supported due to combining with Android naming - workoutActivityTypeMap["EQUESTRIAN_SPORTS"] = .equestrianSports - workoutActivityTypeMap["FISHING"] = .fishing - workoutActivityTypeMap["GOLF"] = .golf - workoutActivityTypeMap["HIKING"] = .hiking - workoutActivityTypeMap["HUNTING"] = .hunting - workoutActivityTypeMap["PLAY"] = .play - workoutActivityTypeMap["CROSS_COUNTRY_SKIING"] = .crossCountrySkiing - workoutActivityTypeMap["CURLING"] = .curling - workoutActivityTypeMap["DOWNHILL_SKIING"] = .downhillSkiing - workoutActivityTypeMap["SNOW_SPORTS"] = .snowSports - workoutActivityTypeMap["SNOWBOARDING"] = .snowboarding - workoutActivityTypeMap["SKATING"] = .skatingSports - workoutActivityTypeMap["PADDLE_SPORTS"] = .paddleSports - workoutActivityTypeMap["ROWING"] = .rowing - workoutActivityTypeMap["SAILING"] = .sailing - workoutActivityTypeMap["SURFING"] = .surfingSports - workoutActivityTypeMap["SWIMMING"] = .swimming - workoutActivityTypeMap["SWIMMING_OPEN_WATER"] = .swimming - workoutActivityTypeMap["SWIMMING_POOL"] = .swimming - workoutActivityTypeMap["WATER_FITNESS"] = .waterFitness - workoutActivityTypeMap["WATER_POLO"] = .waterPolo - workoutActivityTypeMap["WATER_SPORTS"] = .waterSports - workoutActivityTypeMap["BOXING"] = .boxing - workoutActivityTypeMap["KICKBOXING"] = .kickboxing - workoutActivityTypeMap["MARTIAL_ARTS"] = .martialArts - workoutActivityTypeMap["TAI_CHI"] = .taiChi - workoutActivityTypeMap["WRESTLING"] = .wrestling - workoutActivityTypeMap["OTHER"] = .other - if #available(iOS 17.0, *) { - workoutActivityTypeMap["UNDERWATER_DIVING"] = .underwaterDiving - } - } -} diff --git a/packages/health/ios/health.podspec b/packages/health/ios/health.podspec deleted file mode 100644 index a928ad6ad..000000000 --- a/packages/health/ios/health.podspec +++ /dev/null @@ -1,23 +0,0 @@ -# -# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html -# -Pod::Spec.new do |s| - s.name = 'health' - s.version = '13.2.0' - s.summary = 'Wrapper for Apple\'s HealthKit on iOS and Google\'s Health Connect on Android.' - s.description = <<-DESC -Wrapper for Apple's HealthKit on iOS and Google's Health Connect on Android. - DESC - s.homepage = 'https://pub.dev/packages/health' - s.license = { :file => '../LICENSE' } - s.author = { 'Copenhagen Research Platform at DTU' => 'support@carp.dk' } - s.source = { :path => '.' } - s.source_files = 'Classes/**/*' - s.public_header_files = 'Classes/**/*.h' - s.dependency 'Flutter' - - s.ios.deployment_target = '14.0' - s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } - s.swift_version = '5.0' -end - diff --git a/packages/health/lib/health.dart b/packages/health/lib/health.dart deleted file mode 100644 index af94d80e7..000000000 --- a/packages/health/lib/health.dart +++ /dev/null @@ -1,21 +0,0 @@ -library; - -import 'dart:async'; -import 'dart:collection'; -import 'dart:io' show Platform; - -import 'package:carp_serializable/carp_serializable.dart'; -import 'package:json_annotation/json_annotation.dart'; -import 'package:device_info_plus/device_info_plus.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; - -part 'src/heath_data_types.dart'; -part 'src/functions.dart'; -part 'src/health_data_point.dart'; -part 'src/health_value_types.dart'; -part 'src/health_plugin.dart'; -part 'src/workout_summary.dart'; - -part 'health.g.dart'; -part 'health.json.dart'; diff --git a/packages/health/lib/health.g.dart b/packages/health/lib/health.g.dart deleted file mode 100644 index 0d8302430..000000000 --- a/packages/health/lib/health.g.dart +++ /dev/null @@ -1,631 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'health.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -HealthDataPoint _$HealthDataPointFromJson( - Map json, -) => HealthDataPoint( - uuid: json['uuid'] as String, - value: HealthValue.fromJson(json['value'] as Map), - type: $enumDecode(_$HealthDataTypeEnumMap, json['type']), - unit: $enumDecode(_$HealthDataUnitEnumMap, json['unit']), - dateFrom: DateTime.parse(json['dateFrom'] as String), - dateTo: DateTime.parse(json['dateTo'] as String), - sourcePlatform: $enumDecode( - _$HealthPlatformTypeEnumMap, - json['sourcePlatform'], - ), - sourceDeviceId: json['sourceDeviceId'] as String, - sourceId: json['sourceId'] as String, - sourceName: json['sourceName'] as String, - recordingMethod: - $enumDecodeNullable(_$RecordingMethodEnumMap, json['recordingMethod']) ?? - RecordingMethod.unknown, - workoutSummary: json['workoutSummary'] == null - ? null - : WorkoutSummary.fromJson(json['workoutSummary'] as Map), - metadata: json['metadata'] as Map?, - deviceModel: json['deviceModel'] as String?, -); - -Map _$HealthDataPointToJson(HealthDataPoint instance) => - { - 'uuid': instance.uuid, - 'value': instance.value.toJson(), - 'type': _$HealthDataTypeEnumMap[instance.type]!, - 'unit': _$HealthDataUnitEnumMap[instance.unit]!, - 'dateFrom': instance.dateFrom.toIso8601String(), - 'dateTo': instance.dateTo.toIso8601String(), - 'sourcePlatform': _$HealthPlatformTypeEnumMap[instance.sourcePlatform]!, - 'sourceDeviceId': instance.sourceDeviceId, - 'sourceId': instance.sourceId, - 'sourceName': instance.sourceName, - 'recordingMethod': _$RecordingMethodEnumMap[instance.recordingMethod]!, - 'workoutSummary': ?instance.workoutSummary?.toJson(), - 'metadata': ?instance.metadata, - 'deviceModel': ?instance.deviceModel, - }; - -const _$HealthDataTypeEnumMap = { - HealthDataType.ACTIVE_ENERGY_BURNED: 'ACTIVE_ENERGY_BURNED', - HealthDataType.ATRIAL_FIBRILLATION_BURDEN: 'ATRIAL_FIBRILLATION_BURDEN', - HealthDataType.APPLE_STAND_HOUR: 'APPLE_STAND_HOUR', - HealthDataType.APPLE_MOVE_TIME: 'APPLE_MOVE_TIME', - HealthDataType.APPLE_STAND_TIME: 'APPLE_STAND_TIME', - HealthDataType.AUDIOGRAM: 'AUDIOGRAM', - HealthDataType.BASAL_ENERGY_BURNED: 'BASAL_ENERGY_BURNED', - HealthDataType.BLOOD_GLUCOSE: 'BLOOD_GLUCOSE', - HealthDataType.BLOOD_OXYGEN: 'BLOOD_OXYGEN', - HealthDataType.BLOOD_PRESSURE_DIASTOLIC: 'BLOOD_PRESSURE_DIASTOLIC', - HealthDataType.BLOOD_PRESSURE_SYSTOLIC: 'BLOOD_PRESSURE_SYSTOLIC', - HealthDataType.BODY_FAT_PERCENTAGE: 'BODY_FAT_PERCENTAGE', - HealthDataType.LEAN_BODY_MASS: 'LEAN_BODY_MASS', - HealthDataType.BODY_MASS_INDEX: 'BODY_MASS_INDEX', - HealthDataType.BODY_TEMPERATURE: 'BODY_TEMPERATURE', - HealthDataType.BODY_WATER_MASS: 'BODY_WATER_MASS', - HealthDataType.DIETARY_CARBS_CONSUMED: 'DIETARY_CARBS_CONSUMED', - HealthDataType.DIETARY_CAFFEINE: 'DIETARY_CAFFEINE', - HealthDataType.DIETARY_ENERGY_CONSUMED: 'DIETARY_ENERGY_CONSUMED', - HealthDataType.DIETARY_FATS_CONSUMED: 'DIETARY_FATS_CONSUMED', - HealthDataType.DIETARY_PROTEIN_CONSUMED: 'DIETARY_PROTEIN_CONSUMED', - HealthDataType.DIETARY_FIBER: 'DIETARY_FIBER', - HealthDataType.DIETARY_SUGAR: 'DIETARY_SUGAR', - HealthDataType.DIETARY_FAT_MONOUNSATURATED: 'DIETARY_FAT_MONOUNSATURATED', - HealthDataType.DIETARY_FAT_POLYUNSATURATED: 'DIETARY_FAT_POLYUNSATURATED', - HealthDataType.DIETARY_FAT_SATURATED: 'DIETARY_FAT_SATURATED', - HealthDataType.DIETARY_CHOLESTEROL: 'DIETARY_CHOLESTEROL', - HealthDataType.DIETARY_VITAMIN_A: 'DIETARY_VITAMIN_A', - HealthDataType.DIETARY_THIAMIN: 'DIETARY_THIAMIN', - HealthDataType.DIETARY_RIBOFLAVIN: 'DIETARY_RIBOFLAVIN', - HealthDataType.DIETARY_NIACIN: 'DIETARY_NIACIN', - HealthDataType.DIETARY_PANTOTHENIC_ACID: 'DIETARY_PANTOTHENIC_ACID', - HealthDataType.DIETARY_VITAMIN_B6: 'DIETARY_VITAMIN_B6', - HealthDataType.DIETARY_BIOTIN: 'DIETARY_BIOTIN', - HealthDataType.DIETARY_VITAMIN_B12: 'DIETARY_VITAMIN_B12', - HealthDataType.DIETARY_VITAMIN_C: 'DIETARY_VITAMIN_C', - HealthDataType.DIETARY_VITAMIN_D: 'DIETARY_VITAMIN_D', - HealthDataType.DIETARY_VITAMIN_E: 'DIETARY_VITAMIN_E', - HealthDataType.DIETARY_VITAMIN_K: 'DIETARY_VITAMIN_K', - HealthDataType.DIETARY_FOLATE: 'DIETARY_FOLATE', - HealthDataType.DIETARY_CALCIUM: 'DIETARY_CALCIUM', - HealthDataType.DIETARY_CHLORIDE: 'DIETARY_CHLORIDE', - HealthDataType.DIETARY_IRON: 'DIETARY_IRON', - HealthDataType.DIETARY_MAGNESIUM: 'DIETARY_MAGNESIUM', - HealthDataType.DIETARY_PHOSPHORUS: 'DIETARY_PHOSPHORUS', - HealthDataType.DIETARY_POTASSIUM: 'DIETARY_POTASSIUM', - HealthDataType.DIETARY_SODIUM: 'DIETARY_SODIUM', - HealthDataType.DIETARY_ZINC: 'DIETARY_ZINC', - HealthDataType.DIETARY_CHROMIUM: 'DIETARY_CHROMIUM', - HealthDataType.DIETARY_COPPER: 'DIETARY_COPPER', - HealthDataType.DIETARY_IODINE: 'DIETARY_IODINE', - HealthDataType.DIETARY_MANGANESE: 'DIETARY_MANGANESE', - HealthDataType.DIETARY_MOLYBDENUM: 'DIETARY_MOLYBDENUM', - HealthDataType.DIETARY_SELENIUM: 'DIETARY_SELENIUM', - HealthDataType.FORCED_EXPIRATORY_VOLUME: 'FORCED_EXPIRATORY_VOLUME', - HealthDataType.HEART_RATE: 'HEART_RATE', - HealthDataType.HEART_RATE_VARIABILITY_SDNN: 'HEART_RATE_VARIABILITY_SDNN', - HealthDataType.HEART_RATE_VARIABILITY_RMSSD: 'HEART_RATE_VARIABILITY_RMSSD', - HealthDataType.HEIGHT: 'HEIGHT', - HealthDataType.INSULIN_DELIVERY: 'INSULIN_DELIVERY', - HealthDataType.RESTING_HEART_RATE: 'RESTING_HEART_RATE', - HealthDataType.RESPIRATORY_RATE: 'RESPIRATORY_RATE', - HealthDataType.PERIPHERAL_PERFUSION_INDEX: 'PERIPHERAL_PERFUSION_INDEX', - HealthDataType.STEPS: 'STEPS', - HealthDataType.WAIST_CIRCUMFERENCE: 'WAIST_CIRCUMFERENCE', - HealthDataType.WALKING_HEART_RATE: 'WALKING_HEART_RATE', - HealthDataType.WEIGHT: 'WEIGHT', - HealthDataType.DISTANCE_WALKING_RUNNING: 'DISTANCE_WALKING_RUNNING', - HealthDataType.DISTANCE_SWIMMING: 'DISTANCE_SWIMMING', - HealthDataType.DISTANCE_CYCLING: 'DISTANCE_CYCLING', - HealthDataType.FLIGHTS_CLIMBED: 'FLIGHTS_CLIMBED', - HealthDataType.DISTANCE_DELTA: 'DISTANCE_DELTA', - HealthDataType.WALKING_SPEED: 'WALKING_SPEED', - HealthDataType.SPEED: 'SPEED', - HealthDataType.MINDFULNESS: 'MINDFULNESS', - HealthDataType.WATER: 'WATER', - HealthDataType.SLEEP_ASLEEP: 'SLEEP_ASLEEP', - HealthDataType.SLEEP_AWAKE_IN_BED: 'SLEEP_AWAKE_IN_BED', - HealthDataType.SLEEP_AWAKE: 'SLEEP_AWAKE', - HealthDataType.SLEEP_DEEP: 'SLEEP_DEEP', - HealthDataType.SLEEP_IN_BED: 'SLEEP_IN_BED', - HealthDataType.SLEEP_LIGHT: 'SLEEP_LIGHT', - HealthDataType.SLEEP_OUT_OF_BED: 'SLEEP_OUT_OF_BED', - HealthDataType.SLEEP_REM: 'SLEEP_REM', - HealthDataType.SLEEP_SESSION: 'SLEEP_SESSION', - HealthDataType.SLEEP_UNKNOWN: 'SLEEP_UNKNOWN', - HealthDataType.EXERCISE_TIME: 'EXERCISE_TIME', - HealthDataType.WORKOUT: 'WORKOUT', - HealthDataType.HEADACHE_NOT_PRESENT: 'HEADACHE_NOT_PRESENT', - HealthDataType.HEADACHE_MILD: 'HEADACHE_MILD', - HealthDataType.HEADACHE_MODERATE: 'HEADACHE_MODERATE', - HealthDataType.HEADACHE_SEVERE: 'HEADACHE_SEVERE', - HealthDataType.HEADACHE_UNSPECIFIED: 'HEADACHE_UNSPECIFIED', - HealthDataType.NUTRITION: 'NUTRITION', - HealthDataType.UV_INDEX: 'UV_INDEX', - HealthDataType.GENDER: 'GENDER', - HealthDataType.BIRTH_DATE: 'BIRTH_DATE', - HealthDataType.BLOOD_TYPE: 'BLOOD_TYPE', - HealthDataType.MENSTRUATION_FLOW: 'MENSTRUATION_FLOW', - HealthDataType.WATER_TEMPERATURE: 'WATER_TEMPERATURE', - HealthDataType.UNDERWATER_DEPTH: 'UNDERWATER_DEPTH', - HealthDataType.HIGH_HEART_RATE_EVENT: 'HIGH_HEART_RATE_EVENT', - HealthDataType.LOW_HEART_RATE_EVENT: 'LOW_HEART_RATE_EVENT', - HealthDataType.IRREGULAR_HEART_RATE_EVENT: 'IRREGULAR_HEART_RATE_EVENT', - HealthDataType.ELECTRODERMAL_ACTIVITY: 'ELECTRODERMAL_ACTIVITY', - HealthDataType.ELECTROCARDIOGRAM: 'ELECTROCARDIOGRAM', - HealthDataType.TOTAL_CALORIES_BURNED: 'TOTAL_CALORIES_BURNED', -}; - -const _$HealthDataUnitEnumMap = { - HealthDataUnit.GRAM: 'GRAM', - HealthDataUnit.KILOGRAM: 'KILOGRAM', - HealthDataUnit.OUNCE: 'OUNCE', - HealthDataUnit.POUND: 'POUND', - HealthDataUnit.STONE: 'STONE', - HealthDataUnit.METER: 'METER', - HealthDataUnit.CENTIMETER: 'CENTIMETER', - HealthDataUnit.INCH: 'INCH', - HealthDataUnit.FOOT: 'FOOT', - HealthDataUnit.YARD: 'YARD', - HealthDataUnit.MILE: 'MILE', - HealthDataUnit.LITER: 'LITER', - HealthDataUnit.MILLILITER: 'MILLILITER', - HealthDataUnit.FLUID_OUNCE_US: 'FLUID_OUNCE_US', - HealthDataUnit.FLUID_OUNCE_IMPERIAL: 'FLUID_OUNCE_IMPERIAL', - HealthDataUnit.CUP_US: 'CUP_US', - HealthDataUnit.CUP_IMPERIAL: 'CUP_IMPERIAL', - HealthDataUnit.PINT_US: 'PINT_US', - HealthDataUnit.PINT_IMPERIAL: 'PINT_IMPERIAL', - HealthDataUnit.PASCAL: 'PASCAL', - HealthDataUnit.MILLIMETER_OF_MERCURY: 'MILLIMETER_OF_MERCURY', - HealthDataUnit.INCHES_OF_MERCURY: 'INCHES_OF_MERCURY', - HealthDataUnit.CENTIMETER_OF_WATER: 'CENTIMETER_OF_WATER', - HealthDataUnit.ATMOSPHERE: 'ATMOSPHERE', - HealthDataUnit.DECIBEL_A_WEIGHTED_SOUND_PRESSURE_LEVEL: - 'DECIBEL_A_WEIGHTED_SOUND_PRESSURE_LEVEL', - HealthDataUnit.SECOND: 'SECOND', - HealthDataUnit.MILLISECOND: 'MILLISECOND', - HealthDataUnit.MINUTE: 'MINUTE', - HealthDataUnit.HOUR: 'HOUR', - HealthDataUnit.DAY: 'DAY', - HealthDataUnit.JOULE: 'JOULE', - HealthDataUnit.KILOCALORIE: 'KILOCALORIE', - HealthDataUnit.LARGE_CALORIE: 'LARGE_CALORIE', - HealthDataUnit.SMALL_CALORIE: 'SMALL_CALORIE', - HealthDataUnit.DEGREE_CELSIUS: 'DEGREE_CELSIUS', - HealthDataUnit.DEGREE_FAHRENHEIT: 'DEGREE_FAHRENHEIT', - HealthDataUnit.KELVIN: 'KELVIN', - HealthDataUnit.DECIBEL_HEARING_LEVEL: 'DECIBEL_HEARING_LEVEL', - HealthDataUnit.HERTZ: 'HERTZ', - HealthDataUnit.SIEMEN: 'SIEMEN', - HealthDataUnit.VOLT: 'VOLT', - HealthDataUnit.INTERNATIONAL_UNIT: 'INTERNATIONAL_UNIT', - HealthDataUnit.COUNT: 'COUNT', - HealthDataUnit.PERCENT: 'PERCENT', - HealthDataUnit.BEATS_PER_MINUTE: 'BEATS_PER_MINUTE', - HealthDataUnit.RESPIRATIONS_PER_MINUTE: 'RESPIRATIONS_PER_MINUTE', - HealthDataUnit.MILLIGRAM_PER_DECILITER: 'MILLIGRAM_PER_DECILITER', - HealthDataUnit.MILLIMOLES_PER_LITER: 'MILLIMOLES_PER_LITER', - HealthDataUnit.METER_PER_SECOND: 'METER_PER_SECOND', - HealthDataUnit.UNKNOWN_UNIT: 'UNKNOWN_UNIT', - HealthDataUnit.NO_UNIT: 'NO_UNIT', -}; - -const _$HealthPlatformTypeEnumMap = { - HealthPlatformType.appleHealth: 'appleHealth', - HealthPlatformType.googleHealthConnect: 'googleHealthConnect', -}; - -const _$RecordingMethodEnumMap = { - RecordingMethod.unknown: 'unknown', - RecordingMethod.active: 'active', - RecordingMethod.automatic: 'automatic', - RecordingMethod.manual: 'manual', -}; - -HealthValue _$HealthValueFromJson(Map json) => - HealthValue()..$type = json['__type'] as String?; - -Map _$HealthValueToJson(HealthValue instance) => - {'__type': ?instance.$type}; - -NumericHealthValue _$NumericHealthValueFromJson(Map json) => - NumericHealthValue(numericValue: json['numericValue'] as num) - ..$type = json['__type'] as String?; - -Map _$NumericHealthValueToJson(NumericHealthValue instance) => - { - '__type': ?instance.$type, - 'numericValue': instance.numericValue, - }; - -AudiogramHealthValue _$AudiogramHealthValueFromJson( - Map json, -) => AudiogramHealthValue( - frequencies: (json['frequencies'] as List) - .map((e) => e as num) - .toList(), - leftEarSensitivities: (json['leftEarSensitivities'] as List) - .map((e) => e as num) - .toList(), - rightEarSensitivities: (json['rightEarSensitivities'] as List) - .map((e) => e as num) - .toList(), -)..$type = json['__type'] as String?; - -Map _$AudiogramHealthValueToJson( - AudiogramHealthValue instance, -) => { - '__type': ?instance.$type, - 'frequencies': instance.frequencies, - 'leftEarSensitivities': instance.leftEarSensitivities, - 'rightEarSensitivities': instance.rightEarSensitivities, -}; - -WorkoutHealthValue _$WorkoutHealthValueFromJson(Map json) => - WorkoutHealthValue( - workoutActivityType: $enumDecode( - _$HealthWorkoutActivityTypeEnumMap, - json['workoutActivityType'], - ), - totalEnergyBurned: (json['totalEnergyBurned'] as num?)?.toInt(), - totalEnergyBurnedUnit: $enumDecodeNullable( - _$HealthDataUnitEnumMap, - json['totalEnergyBurnedUnit'], - ), - totalDistance: (json['totalDistance'] as num?)?.toInt(), - totalDistanceUnit: $enumDecodeNullable( - _$HealthDataUnitEnumMap, - json['totalDistanceUnit'], - ), - totalSteps: (json['totalSteps'] as num?)?.toInt(), - totalStepsUnit: $enumDecodeNullable( - _$HealthDataUnitEnumMap, - json['totalStepsUnit'], - ), - )..$type = json['__type'] as String?; - -Map _$WorkoutHealthValueToJson(WorkoutHealthValue instance) => - { - '__type': ?instance.$type, - 'workoutActivityType': - _$HealthWorkoutActivityTypeEnumMap[instance.workoutActivityType]!, - 'totalEnergyBurned': ?instance.totalEnergyBurned, - 'totalEnergyBurnedUnit': - ?_$HealthDataUnitEnumMap[instance.totalEnergyBurnedUnit], - 'totalDistance': ?instance.totalDistance, - 'totalDistanceUnit': ?_$HealthDataUnitEnumMap[instance.totalDistanceUnit], - 'totalSteps': ?instance.totalSteps, - 'totalStepsUnit': ?_$HealthDataUnitEnumMap[instance.totalStepsUnit], - }; - -const _$HealthWorkoutActivityTypeEnumMap = { - HealthWorkoutActivityType.AMERICAN_FOOTBALL: 'AMERICAN_FOOTBALL', - HealthWorkoutActivityType.ARCHERY: 'ARCHERY', - HealthWorkoutActivityType.AUSTRALIAN_FOOTBALL: 'AUSTRALIAN_FOOTBALL', - HealthWorkoutActivityType.BADMINTON: 'BADMINTON', - HealthWorkoutActivityType.BASEBALL: 'BASEBALL', - HealthWorkoutActivityType.BASKETBALL: 'BASKETBALL', - HealthWorkoutActivityType.BIKING: 'BIKING', - HealthWorkoutActivityType.BOXING: 'BOXING', - HealthWorkoutActivityType.CARDIO_DANCE: 'CARDIO_DANCE', - HealthWorkoutActivityType.CRICKET: 'CRICKET', - HealthWorkoutActivityType.CROSS_COUNTRY_SKIING: 'CROSS_COUNTRY_SKIING', - HealthWorkoutActivityType.CURLING: 'CURLING', - HealthWorkoutActivityType.DOWNHILL_SKIING: 'DOWNHILL_SKIING', - HealthWorkoutActivityType.ELLIPTICAL: 'ELLIPTICAL', - HealthWorkoutActivityType.FENCING: 'FENCING', - HealthWorkoutActivityType.GOLF: 'GOLF', - HealthWorkoutActivityType.GYMNASTICS: 'GYMNASTICS', - HealthWorkoutActivityType.HANDBALL: 'HANDBALL', - HealthWorkoutActivityType.HIGH_INTENSITY_INTERVAL_TRAINING: - 'HIGH_INTENSITY_INTERVAL_TRAINING', - HealthWorkoutActivityType.HIKING: 'HIKING', - HealthWorkoutActivityType.HOCKEY: 'HOCKEY', - HealthWorkoutActivityType.JUMP_ROPE: 'JUMP_ROPE', - HealthWorkoutActivityType.KICKBOXING: 'KICKBOXING', - HealthWorkoutActivityType.MARTIAL_ARTS: 'MARTIAL_ARTS', - HealthWorkoutActivityType.PILATES: 'PILATES', - HealthWorkoutActivityType.RACQUETBALL: 'RACQUETBALL', - HealthWorkoutActivityType.ROWING: 'ROWING', - HealthWorkoutActivityType.RUGBY: 'RUGBY', - HealthWorkoutActivityType.RUNNING: 'RUNNING', - HealthWorkoutActivityType.SAILING: 'SAILING', - HealthWorkoutActivityType.SKATING: 'SKATING', - HealthWorkoutActivityType.SNOWBOARDING: 'SNOWBOARDING', - HealthWorkoutActivityType.SOCCER: 'SOCCER', - HealthWorkoutActivityType.SOFTBALL: 'SOFTBALL', - HealthWorkoutActivityType.SQUASH: 'SQUASH', - HealthWorkoutActivityType.STAIR_CLIMBING: 'STAIR_CLIMBING', - HealthWorkoutActivityType.SWIMMING: 'SWIMMING', - HealthWorkoutActivityType.TABLE_TENNIS: 'TABLE_TENNIS', - HealthWorkoutActivityType.TENNIS: 'TENNIS', - HealthWorkoutActivityType.VOLLEYBALL: 'VOLLEYBALL', - HealthWorkoutActivityType.WALKING: 'WALKING', - HealthWorkoutActivityType.WATER_POLO: 'WATER_POLO', - HealthWorkoutActivityType.YOGA: 'YOGA', - HealthWorkoutActivityType.BARRE: 'BARRE', - HealthWorkoutActivityType.BOWLING: 'BOWLING', - HealthWorkoutActivityType.CLIMBING: 'CLIMBING', - HealthWorkoutActivityType.COOLDOWN: 'COOLDOWN', - HealthWorkoutActivityType.CORE_TRAINING: 'CORE_TRAINING', - HealthWorkoutActivityType.CROSS_TRAINING: 'CROSS_TRAINING', - HealthWorkoutActivityType.DISC_SPORTS: 'DISC_SPORTS', - HealthWorkoutActivityType.EQUESTRIAN_SPORTS: 'EQUESTRIAN_SPORTS', - HealthWorkoutActivityType.FISHING: 'FISHING', - HealthWorkoutActivityType.FITNESS_GAMING: 'FITNESS_GAMING', - HealthWorkoutActivityType.FLEXIBILITY: 'FLEXIBILITY', - HealthWorkoutActivityType.FUNCTIONAL_STRENGTH_TRAINING: - 'FUNCTIONAL_STRENGTH_TRAINING', - HealthWorkoutActivityType.HAND_CYCLING: 'HAND_CYCLING', - HealthWorkoutActivityType.HUNTING: 'HUNTING', - HealthWorkoutActivityType.LACROSSE: 'LACROSSE', - HealthWorkoutActivityType.MIND_AND_BODY: 'MIND_AND_BODY', - HealthWorkoutActivityType.MIXED_CARDIO: 'MIXED_CARDIO', - HealthWorkoutActivityType.PADDLE_SPORTS: 'PADDLE_SPORTS', - HealthWorkoutActivityType.PICKLEBALL: 'PICKLEBALL', - HealthWorkoutActivityType.PLAY: 'PLAY', - HealthWorkoutActivityType.PREPARATION_AND_RECOVERY: - 'PREPARATION_AND_RECOVERY', - HealthWorkoutActivityType.SNOW_SPORTS: 'SNOW_SPORTS', - HealthWorkoutActivityType.SOCIAL_DANCE: 'SOCIAL_DANCE', - HealthWorkoutActivityType.STAIRS: 'STAIRS', - HealthWorkoutActivityType.STEP_TRAINING: 'STEP_TRAINING', - HealthWorkoutActivityType.SURFING: 'SURFING', - HealthWorkoutActivityType.TAI_CHI: 'TAI_CHI', - HealthWorkoutActivityType.TRACK_AND_FIELD: 'TRACK_AND_FIELD', - HealthWorkoutActivityType.TRADITIONAL_STRENGTH_TRAINING: - 'TRADITIONAL_STRENGTH_TRAINING', - HealthWorkoutActivityType.WATER_FITNESS: 'WATER_FITNESS', - HealthWorkoutActivityType.WATER_SPORTS: 'WATER_SPORTS', - HealthWorkoutActivityType.WHEELCHAIR_RUN_PACE: 'WHEELCHAIR_RUN_PACE', - HealthWorkoutActivityType.WHEELCHAIR_WALK_PACE: 'WHEELCHAIR_WALK_PACE', - HealthWorkoutActivityType.WRESTLING: 'WRESTLING', - HealthWorkoutActivityType.UNDERWATER_DIVING: 'UNDERWATER_DIVING', - HealthWorkoutActivityType.BIKING_STATIONARY: 'BIKING_STATIONARY', - HealthWorkoutActivityType.CALISTHENICS: 'CALISTHENICS', - HealthWorkoutActivityType.DANCING: 'DANCING', - HealthWorkoutActivityType.FRISBEE_DISC: 'FRISBEE_DISC', - HealthWorkoutActivityType.GUIDED_BREATHING: 'GUIDED_BREATHING', - HealthWorkoutActivityType.ICE_SKATING: 'ICE_SKATING', - HealthWorkoutActivityType.PARAGLIDING: 'PARAGLIDING', - HealthWorkoutActivityType.ROCK_CLIMBING: 'ROCK_CLIMBING', - HealthWorkoutActivityType.ROWING_MACHINE: 'ROWING_MACHINE', - HealthWorkoutActivityType.RUNNING_TREADMILL: 'RUNNING_TREADMILL', - HealthWorkoutActivityType.SCUBA_DIVING: 'SCUBA_DIVING', - HealthWorkoutActivityType.SKIING: 'SKIING', - HealthWorkoutActivityType.SNOWSHOEING: 'SNOWSHOEING', - HealthWorkoutActivityType.STAIR_CLIMBING_MACHINE: 'STAIR_CLIMBING_MACHINE', - HealthWorkoutActivityType.STRENGTH_TRAINING: 'STRENGTH_TRAINING', - HealthWorkoutActivityType.SWIMMING_OPEN_WATER: 'SWIMMING_OPEN_WATER', - HealthWorkoutActivityType.SWIMMING_POOL: 'SWIMMING_POOL', - HealthWorkoutActivityType.WALKING_TREADMILL: 'WALKING_TREADMILL', - HealthWorkoutActivityType.WEIGHTLIFTING: 'WEIGHTLIFTING', - HealthWorkoutActivityType.WHEELCHAIR: 'WHEELCHAIR', - HealthWorkoutActivityType.OTHER: 'OTHER', -}; - -ElectrocardiogramHealthValue _$ElectrocardiogramHealthValueFromJson( - Map json, -) => ElectrocardiogramHealthValue( - voltageValues: (json['voltageValues'] as List) - .map( - (e) => - ElectrocardiogramVoltageValue.fromJson(e as Map), - ) - .toList(), - averageHeartRate: json['averageHeartRate'] as num?, - samplingFrequency: (json['samplingFrequency'] as num?)?.toDouble(), - classification: $enumDecodeNullable( - _$ElectrocardiogramClassificationEnumMap, - json['classification'], - ), -)..$type = json['__type'] as String?; - -Map _$ElectrocardiogramHealthValueToJson( - ElectrocardiogramHealthValue instance, -) => { - '__type': ?instance.$type, - 'voltageValues': instance.voltageValues.map((e) => e.toJson()).toList(), - 'averageHeartRate': ?instance.averageHeartRate, - 'samplingFrequency': ?instance.samplingFrequency, - 'classification': - ?_$ElectrocardiogramClassificationEnumMap[instance.classification], -}; - -const _$ElectrocardiogramClassificationEnumMap = { - ElectrocardiogramClassification.NOT_SET: 'NOT_SET', - ElectrocardiogramClassification.SINUS_RHYTHM: 'SINUS_RHYTHM', - ElectrocardiogramClassification.ATRIAL_FIBRILLATION: 'ATRIAL_FIBRILLATION', - ElectrocardiogramClassification.INCONCLUSIVE_LOW_HEART_RATE: - 'INCONCLUSIVE_LOW_HEART_RATE', - ElectrocardiogramClassification.INCONCLUSIVE_HIGH_HEART_RATE: - 'INCONCLUSIVE_HIGH_HEART_RATE', - ElectrocardiogramClassification.INCONCLUSIVE_POOR_READING: - 'INCONCLUSIVE_POOR_READING', - ElectrocardiogramClassification.INCONCLUSIVE_OTHER: 'INCONCLUSIVE_OTHER', - ElectrocardiogramClassification.UNRECOGNIZED: 'UNRECOGNIZED', -}; - -ElectrocardiogramVoltageValue _$ElectrocardiogramVoltageValueFromJson( - Map json, -) => ElectrocardiogramVoltageValue( - voltage: json['voltage'] as num, - timeSinceSampleStart: json['timeSinceSampleStart'] as num, -)..$type = json['__type'] as String?; - -Map _$ElectrocardiogramVoltageValueToJson( - ElectrocardiogramVoltageValue instance, -) => { - '__type': ?instance.$type, - 'voltage': instance.voltage, - 'timeSinceSampleStart': instance.timeSinceSampleStart, -}; - -InsulinDeliveryHealthValue _$InsulinDeliveryHealthValueFromJson( - Map json, -) => InsulinDeliveryHealthValue( - units: (json['units'] as num).toDouble(), - reason: $enumDecode(_$InsulinDeliveryReasonEnumMap, json['reason']), -)..$type = json['__type'] as String?; - -Map _$InsulinDeliveryHealthValueToJson( - InsulinDeliveryHealthValue instance, -) => { - '__type': ?instance.$type, - 'units': instance.units, - 'reason': _$InsulinDeliveryReasonEnumMap[instance.reason]!, -}; - -const _$InsulinDeliveryReasonEnumMap = { - InsulinDeliveryReason.NOT_SET: 'NOT_SET', - InsulinDeliveryReason.BASAL: 'BASAL', - InsulinDeliveryReason.BOLUS: 'BOLUS', -}; - -NutritionHealthValue _$NutritionHealthValueFromJson( - Map json, -) => NutritionHealthValue( - name: json['name'] as String?, - mealType: json['meal_type'] as String?, - calories: (json['calories'] as num?)?.toDouble(), - protein: (json['protein'] as num?)?.toDouble(), - fat: (json['fat'] as num?)?.toDouble(), - carbs: (json['carbs'] as num?)?.toDouble(), - caffeine: (json['caffeine'] as num?)?.toDouble(), - vitaminA: (json['vitamin_a'] as num?)?.toDouble(), - b1Thiamine: (json['b1_thiamine'] as num?)?.toDouble(), - b2Riboflavin: (json['b2_riboflavin'] as num?)?.toDouble(), - b3Niacin: (json['b3_niacin'] as num?)?.toDouble(), - b5PantothenicAcid: (json['b5_pantothenic_acid'] as num?)?.toDouble(), - b6Pyridoxine: (json['b6_pyridoxine'] as num?)?.toDouble(), - b7Biotin: (json['b7_biotin'] as num?)?.toDouble(), - b9Folate: (json['b9_folate'] as num?)?.toDouble(), - b12Cobalamin: (json['b12_cobalamin'] as num?)?.toDouble(), - vitaminC: (json['vitamin_c'] as num?)?.toDouble(), - vitaminD: (json['vitamin_d'] as num?)?.toDouble(), - vitaminE: (json['vitamin_e'] as num?)?.toDouble(), - vitaminK: (json['vitamin_k'] as num?)?.toDouble(), - calcium: (json['calcium'] as num?)?.toDouble(), - chloride: (json['chloride'] as num?)?.toDouble(), - cholesterol: (json['cholesterol'] as num?)?.toDouble(), - choline: (json['choline'] as num?)?.toDouble(), - chromium: (json['chromium'] as num?)?.toDouble(), - copper: (json['copper'] as num?)?.toDouble(), - fatUnsaturated: (json['fat_unsaturated'] as num?)?.toDouble(), - fatMonounsaturated: (json['fat_monounsaturated'] as num?)?.toDouble(), - fatPolyunsaturated: (json['fat_polyunsaturated'] as num?)?.toDouble(), - fatSaturated: (json['fat_saturated'] as num?)?.toDouble(), - fatTransMonoenoic: (json['fat_trans_monoenoic'] as num?)?.toDouble(), - fiber: (json['fiber'] as num?)?.toDouble(), - iodine: (json['iodine'] as num?)?.toDouble(), - iron: (json['iron'] as num?)?.toDouble(), - magnesium: (json['magnesium'] as num?)?.toDouble(), - manganese: (json['manganese'] as num?)?.toDouble(), - molybdenum: (json['molybdenum'] as num?)?.toDouble(), - phosphorus: (json['phosphorus'] as num?)?.toDouble(), - potassium: (json['potassium'] as num?)?.toDouble(), - selenium: (json['selenium'] as num?)?.toDouble(), - sodium: (json['sodium'] as num?)?.toDouble(), - sugar: (json['sugar'] as num?)?.toDouble(), - water: (json['water'] as num?)?.toDouble(), - zinc: (json['zinc'] as num?)?.toDouble(), -)..$type = json['__type'] as String?; - -Map _$NutritionHealthValueToJson( - NutritionHealthValue instance, -) => { - '__type': ?instance.$type, - 'name': ?instance.name, - 'meal_type': ?instance.mealType, - 'calories': ?instance.calories, - 'protein': ?instance.protein, - 'fat': ?instance.fat, - 'carbs': ?instance.carbs, - 'caffeine': ?instance.caffeine, - 'vitamin_a': ?instance.vitaminA, - 'b1_thiamine': ?instance.b1Thiamine, - 'b2_riboflavin': ?instance.b2Riboflavin, - 'b3_niacin': ?instance.b3Niacin, - 'b5_pantothenic_acid': ?instance.b5PantothenicAcid, - 'b6_pyridoxine': ?instance.b6Pyridoxine, - 'b7_biotin': ?instance.b7Biotin, - 'b9_folate': ?instance.b9Folate, - 'b12_cobalamin': ?instance.b12Cobalamin, - 'vitamin_c': ?instance.vitaminC, - 'vitamin_d': ?instance.vitaminD, - 'vitamin_e': ?instance.vitaminE, - 'vitamin_k': ?instance.vitaminK, - 'calcium': ?instance.calcium, - 'chloride': ?instance.chloride, - 'cholesterol': ?instance.cholesterol, - 'choline': ?instance.choline, - 'chromium': ?instance.chromium, - 'copper': ?instance.copper, - 'fat_unsaturated': ?instance.fatUnsaturated, - 'fat_monounsaturated': ?instance.fatMonounsaturated, - 'fat_polyunsaturated': ?instance.fatPolyunsaturated, - 'fat_saturated': ?instance.fatSaturated, - 'fat_trans_monoenoic': ?instance.fatTransMonoenoic, - 'fiber': ?instance.fiber, - 'iodine': ?instance.iodine, - 'iron': ?instance.iron, - 'magnesium': ?instance.magnesium, - 'manganese': ?instance.manganese, - 'molybdenum': ?instance.molybdenum, - 'phosphorus': ?instance.phosphorus, - 'potassium': ?instance.potassium, - 'selenium': ?instance.selenium, - 'sodium': ?instance.sodium, - 'sugar': ?instance.sugar, - 'water': ?instance.water, - 'zinc': ?instance.zinc, -}; - -MenstruationFlowHealthValue _$MenstruationFlowHealthValueFromJson( - Map json, -) => MenstruationFlowHealthValue( - flow: $enumDecodeNullable(_$MenstrualFlowEnumMap, json['flow']), - dateTime: DateTime.parse(json['dateTime'] as String), - isStartOfCycle: json['isStartOfCycle'] as bool?, - wasUserEntered: json['wasUserEntered'] as bool?, -)..$type = json['__type'] as String?; - -Map _$MenstruationFlowHealthValueToJson( - MenstruationFlowHealthValue instance, -) => { - '__type': ?instance.$type, - 'flow': ?_$MenstrualFlowEnumMap[instance.flow], - 'isStartOfCycle': ?instance.isStartOfCycle, - 'wasUserEntered': ?instance.wasUserEntered, - 'dateTime': instance.dateTime.toIso8601String(), -}; - -const _$MenstrualFlowEnumMap = { - MenstrualFlow.unspecified: 'unspecified', - MenstrualFlow.none: 'none', - MenstrualFlow.light: 'light', - MenstrualFlow.medium: 'medium', - MenstrualFlow.heavy: 'heavy', - MenstrualFlow.spotting: 'spotting', -}; - -WorkoutSummary _$WorkoutSummaryFromJson(Map json) => - WorkoutSummary( - workoutType: json['workoutType'] as String, - totalDistance: json['totalDistance'] as num, - totalEnergyBurned: json['totalEnergyBurned'] as num, - totalSteps: json['totalSteps'] as num, - ); - -Map _$WorkoutSummaryToJson(WorkoutSummary instance) => - { - 'workoutType': instance.workoutType, - 'totalDistance': instance.totalDistance, - 'totalEnergyBurned': instance.totalEnergyBurned, - 'totalSteps': instance.totalSteps, - }; diff --git a/packages/health/lib/health.json.dart b/packages/health/lib/health.json.dart deleted file mode 100644 index 4694720ea..000000000 --- a/packages/health/lib/health.json.dart +++ /dev/null @@ -1,30 +0,0 @@ -part of 'health.dart'; - -bool _fromJsonFunctionsRegistered = false; - -/// Register all the fromJson functions for the health domain classes. -void _registerFromJsonFunctions() { - if (_fromJsonFunctionsRegistered) return; - - // Protocol classes - FromJsonFactory().registerAll([ - HealthValue(), - NumericHealthValue(numericValue: 12), - WorkoutHealthValue(workoutActivityType: HealthWorkoutActivityType.RUNNING), - AudiogramHealthValue( - frequencies: [], - leftEarSensitivities: [], - rightEarSensitivities: [], - ), - ElectrocardiogramHealthValue(voltageValues: []), - ElectrocardiogramVoltageValue(voltage: 12, timeSinceSampleStart: 0), - NutritionHealthValue(), - MenstruationFlowHealthValue(flow: null, dateTime: DateTime.now()), - InsulinDeliveryHealthValue( - units: 0.0, - reason: InsulinDeliveryReason.NOT_SET, - ), - ]); - - _fromJsonFunctionsRegistered = true; -} diff --git a/packages/health/lib/src/functions.dart b/packages/health/lib/src/functions.dart deleted file mode 100644 index cc17b988c..000000000 --- a/packages/health/lib/src/functions.dart +++ /dev/null @@ -1,46 +0,0 @@ -part of '../health.dart'; - -/// Custom Exception for the plugin. Used when a Health Data Type is requested, -/// but not available on the current platform. -class HealthException implements Exception { - /// Data Type that was requested. - dynamic dataType; - - /// Cause of the exception. - String cause; - - HealthException(this.dataType, this.cause); - - @override - String toString() => - "Error requesting health data type '$dataType' - cause: $cause"; -} - -/// The status of Google Health Connect. -/// -/// **NOTE** - The enum order is arbitrary. If you need the native value, -/// use [nativeValue] and not the index. -/// -/// Reference: -/// https://developer.android.com/reference/kotlin/androidx/health/connect/client/HealthConnectClient#constants_1 -enum HealthConnectSdkStatus { - /// https://developer.android.com/reference/kotlin/androidx/health/connect/client/HealthConnectClient#SDK_UNAVAILABLE() - sdkUnavailable(1), - - /// https://developer.android.com/reference/kotlin/androidx/health/connect/client/HealthConnectClient#SDK_UNAVAILABLE_PROVIDER_UPDATE_REQUIRED() - sdkUnavailableProviderUpdateRequired(2), - - /// https://developer.android.com/reference/kotlin/androidx/health/connect/client/HealthConnectClient#SDK_AVAILABLE() - sdkAvailable(3); - - const HealthConnectSdkStatus(this.nativeValue); - - /// The native value that matches the value in the Android SDK. - final int nativeValue; - - factory HealthConnectSdkStatus.fromNativeValue(int value) { - return HealthConnectSdkStatus.values.firstWhere( - (e) => e.nativeValue == value, - orElse: () => HealthConnectSdkStatus.sdkUnavailable); - } -} diff --git a/packages/health/lib/src/health_data_point.dart b/packages/health/lib/src/health_data_point.dart deleted file mode 100644 index 1255e7048..000000000 --- a/packages/health/lib/src/health_data_point.dart +++ /dev/null @@ -1,225 +0,0 @@ -part of '../health.dart'; - -/// Types of health platforms. -enum HealthPlatformType { appleHealth, googleHealthConnect } - -/// A [HealthDataPoint] object corresponds to a data point capture from -/// Apple HealthKit or Google Health Connect with a [HealthValue] -/// as value. -@JsonSerializable(includeIfNull: false, explicitToJson: true) -class HealthDataPoint { - /// UUID of the data point. - String uuid; - - /// The quantity value of the data point - HealthValue value; - - /// The type of the data point. - HealthDataType type; - - /// The data point type as a string. - String get typeString => type.name; - - /// The unit of the data point. - HealthDataUnit unit; - - /// The data point unit as a string. - String get unitString => unit.name; - - /// The start of the time interval. - DateTime dateFrom; - - /// The end of the time interval. - DateTime dateTo; - - /// The health platform that this data point was fetched. - HealthPlatformType sourcePlatform; - - /// The id of the device from which the data point was fetched. - String sourceDeviceId; - - /// The id of the source from which the data point was fetched. - String sourceId; - - /// The name of the source from which the data point was fetched. - String sourceName; - - /// How the data point was recorded - /// (on Android: https://developer.android.com/reference/kotlin/androidx/health/connect/client/records/metadata/Metadata#summary) - /// on iOS: either user entered or manual https://developer.apple.com/documentation/healthkit/hkmetadatakeywasuserentered) - RecordingMethod recordingMethod; - - /// The summary of the workout data point, if available. - WorkoutSummary? workoutSummary; - - /// The metadata for this data point. - Map? metadata; - - /// The source of the data, whether from the iPhone or Watch or something else. - /// Only available fo iOS - /// On Android: always return null - String? deviceModel; - - HealthDataPoint({ - required this.uuid, - required this.value, - required this.type, - required this.unit, - required this.dateFrom, - required this.dateTo, - required this.sourcePlatform, - required this.sourceDeviceId, - required this.sourceId, - required this.sourceName, - this.recordingMethod = RecordingMethod.unknown, - this.workoutSummary, - this.metadata, - this.deviceModel, - }) { - // set the value to minutes rather than the category - // returned by the native API - if (type == HealthDataType.MINDFULNESS || - type == HealthDataType.HEADACHE_UNSPECIFIED || - type == HealthDataType.HEADACHE_NOT_PRESENT || - type == HealthDataType.HEADACHE_MILD || - type == HealthDataType.HEADACHE_MODERATE || - type == HealthDataType.HEADACHE_SEVERE || - type == HealthDataType.SLEEP_ASLEEP || - type == HealthDataType.SLEEP_AWAKE || - type == HealthDataType.SLEEP_AWAKE_IN_BED || - type == HealthDataType.SLEEP_DEEP || - type == HealthDataType.SLEEP_IN_BED || - type == HealthDataType.SLEEP_LIGHT || - type == HealthDataType.SLEEP_REM || - type == HealthDataType.SLEEP_UNKNOWN || - type == HealthDataType.SLEEP_OUT_OF_BED) { - value = _convertMinutes(); - } - } - - /// Converts dateTo - dateFrom to minutes. - NumericHealthValue _convertMinutes() => NumericHealthValue( - numericValue: - (dateTo.millisecondsSinceEpoch - dateFrom.millisecondsSinceEpoch) / - (1000 * 60)); - - /// Create a [HealthDataPoint] from json. - factory HealthDataPoint.fromJson(Map json) => - _$HealthDataPointFromJson(json); - - /// Convert this [HealthDataPoint] to json. - Map toJson() => _$HealthDataPointToJson(this); - - /// Create a [HealthDataPoint] based on a health data point from native data format. - factory HealthDataPoint.fromHealthDataPoint( - HealthDataType dataType, dynamic dataPoint, String? unitName) { - // Handling different [HealthValue] types - HealthValue value = switch (dataType) { - HealthDataType.AUDIOGRAM => - AudiogramHealthValue.fromHealthDataPoint(dataPoint), - HealthDataType.WORKOUT => - WorkoutHealthValue.fromHealthDataPoint(dataPoint), - HealthDataType.ELECTROCARDIOGRAM => - ElectrocardiogramHealthValue.fromHealthDataPoint(dataPoint), - HealthDataType.NUTRITION => - NutritionHealthValue.fromHealthDataPoint(dataPoint), - HealthDataType.INSULIN_DELIVERY => - InsulinDeliveryHealthValue.fromHealthDataPoint(dataPoint), - HealthDataType.MENSTRUATION_FLOW => - MenstruationFlowHealthValue.fromHealthDataPoint(dataPoint), - _ => NumericHealthValue.fromHealthDataPoint(dataPoint), - }; - - final DateTime from = - DateTime.fromMillisecondsSinceEpoch(dataPoint['date_from'] as int); - final DateTime to = - DateTime.fromMillisecondsSinceEpoch(dataPoint['date_to'] as int); - final String sourceId = dataPoint["source_id"] as String; - final String sourceName = dataPoint["source_name"] as String; - final Map? metadata = dataPoint["metadata"] == null - ? null - : Map.from(dataPoint['metadata'] as Map); - final HealthDataUnit unit = HealthDataUnit.values.firstWhere( - (value) => value.name == unitName, - orElse: () => dataTypeToUnit[dataType] ?? HealthDataUnit.UNKNOWN_UNIT); - final String? uuid = dataPoint["uuid"] as String?; - final String? deviceModel = dataPoint["device_model"] as String?; - - // Set WorkoutSummary, if available. - WorkoutSummary? workoutSummary; - if (dataPoint["workout_type"] != null || - dataPoint["total_distance"] != null || - dataPoint["total_energy_burned"] != null || - dataPoint["total_steps"] != null) { - workoutSummary = WorkoutSummary.fromHealthDataPoint(dataPoint); - } - - var recordingMethod = dataPoint["recording_method"] as int?; - - return HealthDataPoint( - uuid: uuid ?? "", - value: value, - type: dataType, - unit: unit, - dateFrom: from, - dateTo: to, - sourcePlatform: Health().platformType, - sourceDeviceId: Health().deviceId, - sourceId: sourceId, - sourceName: sourceName, - recordingMethod: RecordingMethod.fromInt(recordingMethod), - workoutSummary: workoutSummary, - metadata: metadata, - deviceModel: deviceModel, - ); - } - - @override - String toString() => """$runtimeType - - uuid: $uuid, - value: ${value.toString()}, - unit: ${unit.name}, - dateFrom: $dateFrom, - dateTo: $dateTo, - dataType: ${type.name}, - platform: $sourcePlatform, - deviceId: $sourceDeviceId, - sourceId: $sourceId, - sourceName: $sourceName - recordingMethod: $recordingMethod - workoutSummary: $workoutSummary - metadata: $metadata - deviceModel: $deviceModel"""; - - @override - bool operator ==(Object other) => - other is HealthDataPoint && - uuid == other.uuid && - value == other.value && - unit == other.unit && - dateFrom == other.dateFrom && - dateTo == other.dateTo && - type == other.type && - sourcePlatform == other.sourcePlatform && - sourceDeviceId == other.sourceDeviceId && - sourceId == other.sourceId && - sourceName == other.sourceName && - recordingMethod == other.recordingMethod && - metadata == other.metadata && - deviceModel == other.deviceModel; - - @override - int get hashCode => Object.hash( - uuid, - value, - unit, - dateFrom, - dateTo, - type, - sourcePlatform, - sourceDeviceId, - sourceId, - sourceName, - metadata, - deviceModel); -} diff --git a/packages/health/lib/src/health_plugin.dart b/packages/health/lib/src/health_plugin.dart deleted file mode 100644 index 622e3a937..000000000 --- a/packages/health/lib/src/health_plugin.dart +++ /dev/null @@ -1,1709 +0,0 @@ -part of '../health.dart'; - -/// Main class for the Plugin. -/// -/// Use this class to get an instance of the Health plugin, like this: -/// -/// final health = Health(); -/// -/// The plugin must be configured using the [configure] method before used. -/// -/// Overall, the plugin supports: -/// -/// * Handling permissions to access health data using the [hasPermissions], -/// [requestAuthorization], [revokePermissions] methods. -/// * Reading health data using the [getHealthDataFromTypes] method. -/// * Writing health data using the [writeHealthData] method. -/// * Cleaning up duplicate data points via the [removeDuplicates] method. -/// -/// In addition, the plugin has a set of specialized methods for reading and writing -/// different types of health data: -/// -/// * Reading aggregate health data using the [getHealthIntervalDataFromTypes] -/// and [getHealthAggregateDataFromTypes] methods. -/// * Reading total step counts using the [getTotalStepsInInterval] method. -/// * Writing different types of specialized health data like the [writeWorkoutData], -/// [writeBloodPressure], [writeBloodOxygen], [writeAudiogram], [writeMeal], -/// [writeMenstruationFlow], [writeInsulinDelivery] methods. -/// -/// On **Android**, this plugin relies on the Google Health Connect (GHC) SDK. -/// Since Health Connect is not installed on SDK level < 34, the plugin has a -/// set of specialized methods to handle GHC: -/// -/// * [getHealthConnectSdkStatus] to check the status of GHC -/// * [isHealthConnectAvailable] to check if GHC is installed on this phone -/// * [installHealthConnect] to direct the user to the app store to install GHC -/// -/// **Note** that you should check the availability of GHC before using any setter -/// or getter methods. Otherwise, the plugin will throw an exception. -class Health { - static const MethodChannel _channel = MethodChannel('flutter_health'); - - String? _deviceId; - final DeviceInfoPlugin _deviceInfo; - HealthConnectSdkStatus _healthConnectSdkStatus = - HealthConnectSdkStatus.sdkUnavailable; - - /// Get an instance of the health plugin. - Health({DeviceInfoPlugin? deviceInfo}) - : _deviceInfo = deviceInfo ?? DeviceInfoPlugin() { - _registerFromJsonFunctions(); - } - - /// The latest status on availability of Health Connect SDK on this phone. - HealthConnectSdkStatus get healthConnectSdkStatus => _healthConnectSdkStatus; - - /// The type of platform of this device. - HealthPlatformType get platformType => Platform.isIOS - ? HealthPlatformType.appleHealth - : HealthPlatformType.googleHealthConnect; - - /// The id of this device. - /// - /// On Android this is the [ID](https://developer.android.com/reference/android/os/Build#ID) of the BUILD. - /// On iOS this is the [identifierForVendor](https://developer.apple.com/documentation/uikit/uidevice/1620059-identifierforvendor) of the UIDevice. - String get deviceId => _deviceId ?? 'unknown'; - - /// Configure the health plugin. Must be called before using the plugin. - Future configure() async { - _deviceId = Platform.isAndroid - ? (await _deviceInfo.androidInfo).id - : (await _deviceInfo.iosInfo).identifierForVendor; - } - - /// Check if a given data type is available on the platform - bool isDataTypeAvailable(HealthDataType dataType) => Platform.isAndroid - ? dataTypeKeysAndroid.contains(dataType) - : dataTypeKeysIOS.contains(dataType); - - /// Determines if the health data [types] have been granted with the specified - /// access rights [permissions]. - /// - /// Returns: - /// - /// * true - if all of the data types have been granted with the specified access rights. - /// * false - if any of the data types has not been granted with the specified access right(s). - /// * null - if it can not be determined if the data types have been granted with the specified access right(s). - /// - /// Parameters: - /// - /// * [types] - List of [HealthDataType] whose permissions are to be checked. - /// * [permissions] - Optional. - /// + If unspecified, this method checks if each HealthDataType in [types] has been granted READ access. - /// + If specified, this method checks if each [HealthDataType] in [types] has been granted with the access specified in its - /// corresponding entry in this list. The length of this list must be equal to that of [types]. - /// - /// Caveat: - /// - /// * As Apple HealthKit will not disclose if READ access has been granted for a data type due to privacy concern, - /// this method can only return null to represent an undetermined status, if it is called on iOS - /// with a READ or READ_WRITE access. - /// - /// * On Android, this function returns true or false, depending on whether the specified access right has been granted. - Future hasPermissions( - List types, { - List? permissions, - }) async { - await _checkIfHealthConnectAvailableOnAndroid(); - if (permissions != null && permissions.length != types.length) { - throw ArgumentError( - "The lists of types and permissions must be of same length.", - ); - } - - final mTypes = List.from(types, growable: true); - final mPermissions = permissions == null - ? List.filled( - types.length, - HealthDataAccess.READ.index, - growable: true, - ) - : permissions.map((permission) => permission.index).toList(); - - /// On Android, if BMI is requested, then also ask for weight and height - if (Platform.isAndroid) _handleBMI(mTypes, mPermissions); - - return await _channel.invokeMethod('hasPermissions', { - "types": mTypes.map((type) => type.name).toList(), - "permissions": mPermissions, - }); - } - - /// Revokes Google Health Connect permissions on Android of all types. - /// - /// NOTE: The app must be completely killed and restarted for the changes to take effect. - /// Not implemented on iOS as there is no way to programmatically remove access. - /// - /// Android only. On iOS this does nothing. - Future revokePermissions() async { - if (Platform.isIOS) return; - - await _checkIfHealthConnectAvailableOnAndroid(); - try { - await _channel.invokeMethod('revokePermissions'); - } catch (e) { - debugPrint('$runtimeType - Exception in revokePermissions(): $e'); - } - } - - /// Checks the current status of Health Connect availability. - /// - /// See this for more info: - /// https://developer.android.com/reference/kotlin/androidx/health/connect/client/HealthConnectClient#getSdkStatus(android.content.Context,kotlin.String) - /// - /// Android only. Returns null on iOS or if an error occurs. - Future getHealthConnectSdkStatus() async { - if (Platform.isIOS) return null; - - try { - final status = await _channel.invokeMethod( - 'getHealthConnectSdkStatus', - ); - _healthConnectSdkStatus = status != null - ? HealthConnectSdkStatus.fromNativeValue(status) - : HealthConnectSdkStatus.sdkUnavailable; - - return _healthConnectSdkStatus; - } catch (e) { - debugPrint('$runtimeType - Exception in getHealthConnectSdkStatus(): $e'); - return null; - } - } - - /// Is Google Health Connect available on this phone? - /// - /// Android only. Returns always true on iOS. - Future isHealthConnectAvailable() async => !Platform.isAndroid - ? true - : (await getHealthConnectSdkStatus() == - HealthConnectSdkStatus.sdkAvailable); - - /// Prompt the user to install the Google Health Connect app via the - /// installed store (most likely Play Store). - /// - /// Android only. On iOS this does nothing. - Future installHealthConnect() async { - if (Platform.isIOS) return; - - try { - await _channel.invokeMethod('installHealthConnect'); - } catch (e) { - debugPrint('$runtimeType - Exception in installHealthConnect(): $e'); - } - } - - /// Checks if Google Health Connect is available and throws an [UnsupportedError] - /// if not. - /// Internal methods used to check availability before any getter or setter methods. - Future _checkIfHealthConnectAvailableOnAndroid() async { - if (!Platform.isAndroid) return; - - if (!(await isHealthConnectAvailable())) { - throw UnsupportedError( - "Google Health Connect is not available on this Android device. " - "You may prompt the user to install it using the 'installHealthConnect' method", - ); - } - } - - /// Checks if the Health Data History feature is available. - /// - /// See this for more info: https://developer.android.com/reference/androidx/health/connect/client/permission/HealthPermission#PERMISSION_READ_HEALTH_DATA_HISTORY() - /// - /// - /// Android only. Returns false on iOS or if an error occurs. - Future isHealthDataHistoryAvailable() async { - if (Platform.isIOS) return false; - - try { - final status = await _channel.invokeMethod( - 'isHealthDataHistoryAvailable', - ); - return status ?? false; - } catch (e) { - debugPrint( - '$runtimeType - Exception in isHealthDataHistoryAvailable(): $e', - ); - return false; - } - } - - /// Checks the current status of the Health Data History permission. - /// Make sure to check [isHealthConnectAvailable] before calling this method. - /// - /// See this for more info: https://developer.android.com/reference/androidx/health/connect/client/permission/HealthPermission#PERMISSION_READ_HEALTH_DATA_HISTORY() - /// - /// - /// Android only. Returns true on iOS or false if an error occurs. - Future isHealthDataHistoryAuthorized() async { - if (Platform.isIOS) return true; - - try { - final status = await _channel.invokeMethod( - 'isHealthDataHistoryAuthorized', - ); - return status ?? false; - } catch (e) { - debugPrint( - '$runtimeType - Exception in isHealthDataHistoryAuthorized(): $e', - ); - return false; - } - } - - /// Requests the Health Data History permission. - /// - /// Returns true if successful, false otherwise. - /// - /// See this for more info: https://developer.android.com/reference/androidx/health/connect/client/permission/HealthPermission#PERMISSION_READ_HEALTH_DATA_HISTORY() - /// - /// - /// Android only. Returns true on iOS or false if an error occurs. - Future requestHealthDataHistoryAuthorization() async { - if (Platform.isIOS) return true; - - await _checkIfHealthConnectAvailableOnAndroid(); - try { - final bool? isAuthorized = await _channel.invokeMethod( - 'requestHealthDataHistoryAuthorization', - ); - return isAuthorized ?? false; - } catch (e) { - debugPrint( - '$runtimeType - Exception in requestHealthDataHistoryAuthorization(): $e', - ); - return false; - } - } - - /// Checks if the Health Data in Background feature is available. - /// - /// See this for more info: https://developer.android.com/reference/androidx/health/connect/client/permission/HealthPermission#PERMISSION_READ_HEALTH_DATA_IN_BACKGROUND() - /// - /// - /// Android only. Returns false on iOS or if an error occurs. - Future isHealthDataInBackgroundAvailable() async { - if (Platform.isIOS) return false; - - try { - final status = await _channel.invokeMethod( - 'isHealthDataInBackgroundAvailable', - ); - return status ?? false; - } catch (e) { - debugPrint( - '$runtimeType - Exception in isHealthDataInBackgroundAvailable(): $e', - ); - return false; - } - } - - /// Checks the current status of the Health Data in Background permission. - /// Make sure to check [isHealthConnectAvailable] before calling this method. - /// - /// See this for more info: https://developer.android.com/reference/androidx/health/connect/client/permission/HealthPermission#PERMISSION_READ_HEALTH_DATA_IN_BACKGROUND() - /// - /// - /// Android only. Returns true on iOS or false if an error occurs. - Future isHealthDataInBackgroundAuthorized() async { - if (Platform.isIOS) return true; - - try { - final status = await _channel.invokeMethod( - 'isHealthDataInBackgroundAuthorized', - ); - return status ?? false; - } catch (e) { - debugPrint( - '$runtimeType - Exception in isHealthDataInBackgroundAuthorized(): $e', - ); - return false; - } - } - - /// Requests the Health Data in Background permission. - /// - /// Returns true if successful, false otherwise. - /// - /// See this for more info: https://developer.android.com/reference/androidx/health/connect/client/permission/HealthPermission#PERMISSION_READ_HEALTH_DATA_IN_BACKGROUND() - /// - /// - /// Android only. Returns true on iOS or false if an error occurs. - Future requestHealthDataInBackgroundAuthorization() async { - if (Platform.isIOS) return true; - - await _checkIfHealthConnectAvailableOnAndroid(); - try { - final bool? isAuthorized = await _channel.invokeMethod( - 'requestHealthDataInBackgroundAuthorization', - ); - return isAuthorized ?? false; - } catch (e) { - debugPrint( - '$runtimeType - Exception in requestHealthDataInBackgroundAuthorization(): $e', - ); - return false; - } - } - - /// Requests permissions to access health data [types]. - /// - /// Returns true if successful, false otherwise. - /// - /// Parameters: - /// - /// * [types] - a list of [HealthDataType] which the permissions are requested for. - /// * [permissions] - Optional. - /// + If unspecified, each [HealthDataType] in [types] is requested for READ [HealthDataAccess]. - /// + If specified, each [HealthDataAccess] in this list is requested for its corresponding indexed - /// entry in [types]. In addition, the length of this list must be equal to that of [types]. - /// - /// Caveats: - /// - /// * This method may block if permissions are already granted. Hence, check - /// [hasPermissions] before calling this method. - /// * As Apple HealthKit will not disclose if READ access has been granted for - /// a data type due to privacy concern, this method will return **true if - /// the window asking for permission was showed to the user without errors** - /// if it is called on iOS with a READ or READ_WRITE access. - Future requestAuthorization( - List types, { - List? permissions, - }) async { - await _checkIfHealthConnectAvailableOnAndroid(); - if (permissions != null && permissions.length != types.length) { - throw ArgumentError( - 'The length of [types] must be same as that of [permissions].', - ); - } - - if (permissions != null) { - for (int i = 0; i < types.length; i++) { - final type = types[i]; - final permission = permissions[i]; - if ((type == HealthDataType.ELECTROCARDIOGRAM || - type == HealthDataType.HIGH_HEART_RATE_EVENT || - type == HealthDataType.LOW_HEART_RATE_EVENT || - type == HealthDataType.IRREGULAR_HEART_RATE_EVENT || - type == HealthDataType.WALKING_HEART_RATE || - type == HealthDataType.ATRIAL_FIBRILLATION_BURDEN) && - permission != HealthDataAccess.READ) { - throw ArgumentError( - 'Requesting WRITE permission on ELECTROCARDIOGRAM / HIGH_HEART_RATE_EVENT / LOW_HEART_RATE_EVENT / IRREGULAR_HEART_RATE_EVENT / WALKING_HEART_RATE / ATRIAL_FIBRILLATION_BURDEN is not allowed.', - ); - } - } - } - - final mTypes = List.from(types, growable: true); - final mPermissions = permissions == null - ? List.filled( - types.length, - HealthDataAccess.READ.index, - growable: true, - ) - : permissions.map((permission) => permission.index).toList(); - - // on Android, if BMI is requested, then also ask for weight and height - if (Platform.isAndroid) _handleBMI(mTypes, mPermissions); - - List keys = mTypes.map((e) => e.name).toList(); - final bool? isAuthorized = await _channel.invokeMethod( - 'requestAuthorization', - {'types': keys, "permissions": mPermissions}, - ); - return isAuthorized ?? false; - } - - /// Obtains health and weight if BMI is requested on Android. - void _handleBMI(List mTypes, List mPermissions) { - final index = mTypes.indexOf(HealthDataType.BODY_MASS_INDEX); - - if (index != -1 && Platform.isAndroid) { - if (!mTypes.contains(HealthDataType.WEIGHT)) { - mTypes.add(HealthDataType.WEIGHT); - mPermissions.add(mPermissions[index]); - } - if (!mTypes.contains(HealthDataType.HEIGHT)) { - mTypes.add(HealthDataType.HEIGHT); - mPermissions.add(mPermissions[index]); - } - mTypes.remove(HealthDataType.BODY_MASS_INDEX); - mPermissions.removeAt(index); - } - } - - /// Calculate the BMI using the last observed height and weight values. - Future> _computeAndroidBMI( - DateTime startTime, - DateTime endTime, - List recordingMethodsToFilter, - ) async { - List heights = await _prepareQuery( - startTime, - endTime, - HealthDataType.HEIGHT, - recordingMethodsToFilter, - ); - - if (heights.isEmpty) { - return []; - } - - List weights = await _prepareQuery( - startTime, - endTime, - HealthDataType.WEIGHT, - recordingMethodsToFilter, - ); - - double h = (heights.last.value as NumericHealthValue).numericValue - .toDouble(); - - const dataType = HealthDataType.BODY_MASS_INDEX; - final unit = dataTypeToUnit[dataType]!; - - final bmiHealthPoints = []; - for (var i = 0; i < weights.length; i++) { - final bmiValue = - (weights[i].value as NumericHealthValue).numericValue.toDouble() / - (h * h); - final x = HealthDataPoint( - uuid: '', - value: NumericHealthValue(numericValue: bmiValue), - type: dataType, - unit: unit, - dateFrom: weights[i].dateFrom, - dateTo: weights[i].dateTo, - sourcePlatform: platformType, - sourceDeviceId: _deviceId!, - sourceId: '', - sourceName: '', - recordingMethod: RecordingMethod.unknown, - ); - - bmiHealthPoints.add(x); - } - return bmiHealthPoints; - } - - /// Write health data. - /// - /// Returns true if successful, false otherwise. - /// - /// Parameters: - /// * [value] - the health data's value in double - /// * [unit] - **iOS ONLY** the unit the health data is measured in. - /// * [type] - the value's HealthDataType - /// * [startTime] - the start time when this [value] is measured. - /// It must be equal to or earlier than [endTime]. - /// * [endTime] - the end time when this [value] is measured. - /// It must be equal to or later than [startTime]. - /// Simply set [endTime] equal to [startTime] if the [value] is measured - /// only at a specific point in time (default). - /// * [recordingMethod] - the recording method of the data point, automatic by default. - /// (on iOS this must be manual or automatic) - /// - /// Values for Sleep and Headache are ignored and will be automatically assigned - /// the default value. - Future writeHealthData({ - required double value, - HealthDataUnit? unit, - required HealthDataType type, - required DateTime startTime, - String? clientRecordId, - double? clientRecordVersion, - DateTime? endTime, - RecordingMethod recordingMethod = RecordingMethod.automatic, - }) async { - await _checkIfHealthConnectAvailableOnAndroid(); - if (Platform.isIOS && - [ - RecordingMethod.active, - RecordingMethod.unknown, - ].contains(recordingMethod)) { - throw ArgumentError("recordingMethod must be manual or automatic on iOS"); - } - - if (type == HealthDataType.WORKOUT) { - throw ArgumentError( - "Adding workouts should be done using the writeWorkoutData method.", - ); - } - // If not implemented on platform, throw an exception - if (!isDataTypeAvailable(type)) { - throw HealthException(type, 'Not available on platform $platformType'); - } - endTime ??= startTime; - if (startTime.isAfter(endTime)) { - throw ArgumentError("startTime must be equal or earlier than endTime"); - } - if ({ - HealthDataType.HIGH_HEART_RATE_EVENT, - HealthDataType.LOW_HEART_RATE_EVENT, - HealthDataType.IRREGULAR_HEART_RATE_EVENT, - HealthDataType.ELECTROCARDIOGRAM, - }.contains(type) && - Platform.isIOS) { - throw ArgumentError( - "$type - iOS does not support writing this data type in HealthKit", - ); - } - - // Assign default unit if not specified - unit ??= dataTypeToUnit[type]!; - - // Align values to type in cases where the type defines the value. - // E.g. SLEEP_IN_BED should have value 0 - if (type == HealthDataType.SLEEP_ASLEEP || - type == HealthDataType.SLEEP_AWAKE || - type == HealthDataType.SLEEP_IN_BED || - type == HealthDataType.SLEEP_DEEP || - type == HealthDataType.SLEEP_REM || - type == HealthDataType.SLEEP_LIGHT || - type == HealthDataType.HEADACHE_NOT_PRESENT || - type == HealthDataType.HEADACHE_MILD || - type == HealthDataType.HEADACHE_MODERATE || - type == HealthDataType.HEADACHE_SEVERE || - type == HealthDataType.HEADACHE_UNSPECIFIED) { - value = _alignValue(type).toDouble(); - } - - Map args = { - 'value': value, - 'dataTypeKey': type.name, - 'dataUnitKey': unit.name, - 'startTime': startTime.millisecondsSinceEpoch, - 'endTime': endTime.millisecondsSinceEpoch, - 'recordingMethod': recordingMethod.toInt(), - 'clientRecordId': clientRecordId, - 'clientRecordVersion': clientRecordVersion, - }; - bool? success = await _channel.invokeMethod('writeData', args); - return success ?? false; - } - - /// Deletes all records of the given [type] for a given period of time. - /// - /// Returns true if successful, false otherwise. - /// - /// Parameters: - /// * [type] - the value's HealthDataType. - /// * [startTime] - the start time when this [value] is measured. - /// Must be equal to or earlier than [endTime]. - /// * [endTime] - the end time when this [value] is measured. - /// Must be equal to or later than [startTime]. - Future delete({ - required HealthDataType type, - required DateTime startTime, - DateTime? endTime, - }) async { - await _checkIfHealthConnectAvailableOnAndroid(); - endTime ??= startTime; - if (startTime.isAfter(endTime)) { - throw ArgumentError("startTime must be equal or earlier than endTime"); - } - - Map args = { - 'dataTypeKey': type.name, - 'startTime': startTime.millisecondsSinceEpoch, - 'endTime': endTime.millisecondsSinceEpoch, - }; - bool? success = await _channel.invokeMethod('delete', args); - return success ?? false; - } - - /// Deletes a specific health record by its UUID. - /// - /// Returns true if successful, false otherwise. - /// - /// Parameters: - /// * [uuid] - The UUID of the health record to delete. - /// * [type] - The health data type of the record. Required on iOS. - /// - /// On Android, only the UUID is required. On iOS, both UUID and type are required. - Future deleteByUUID({ - required String uuid, - HealthDataType? type, - }) async { - await _checkIfHealthConnectAvailableOnAndroid(); - - if (uuid.isEmpty || uuid == "") { - throw ArgumentError("UUID must not be empty."); - } - - if (Platform.isIOS && type == null) { - throw ArgumentError( - "On iOS, both UUID and type are required to delete a record.", - ); - } - - Map args = {'uuid': uuid, 'dataTypeKey': type?.name}; - - bool? success = await _channel.invokeMethod('deleteByUUID', args); - return success ?? false; - } - - Future deleteByClientRecordId({ - required HealthDataType dataTypeKey, - required String clientRecordId, - String? recordId, - }) async { - await _checkIfHealthConnectAvailableOnAndroid(); - - Map args = { - 'dataTypeKey': dataTypeKey.name, - 'recordId': recordId, - 'clientRecordId': clientRecordId, - }; - bool? success = await _channel.invokeMethod('deleteByClientRecordId', args); - return success ?? false; - } - - /// Saves a blood pressure record. - /// - /// Returns true if successful, false otherwise. - /// - /// Parameters: - /// * [systolic] - the systolic part of the blood pressure. - /// * [diastolic] - the diastolic part of the blood pressure. - /// * [startTime] - the start time when this [value] is measured. - /// Must be equal to or earlier than [endTime]. - /// * [endTime] - the end time when this [value] is measured. - /// Must be equal to or later than [startTime]. - /// Simply set [endTime] equal to [startTime] if the blood pressure is measured - /// only at a specific point in time. If omitted, [endTime] is set to [startTime]. - /// * [recordingMethod] - the recording method of the data point. - Future writeBloodPressure({ - required int systolic, - required int diastolic, - required DateTime startTime, - String? clientRecordId, - double? clientRecordVersion, - DateTime? endTime, - RecordingMethod recordingMethod = RecordingMethod.automatic, - }) async { - await _checkIfHealthConnectAvailableOnAndroid(); - if (Platform.isIOS && - [ - RecordingMethod.active, - RecordingMethod.unknown, - ].contains(recordingMethod)) { - throw ArgumentError("recordingMethod must be manual or automatic on iOS"); - } - - endTime ??= startTime; - if (startTime.isAfter(endTime)) { - throw ArgumentError("startTime must be equal or earlier than endTime"); - } - - Map args = { - 'systolic': systolic, - 'diastolic': diastolic, - 'startTime': startTime.millisecondsSinceEpoch, - 'endTime': endTime.millisecondsSinceEpoch, - 'recordingMethod': recordingMethod.toInt(), - 'clientRecordId': clientRecordId, - 'clientRecordVersion': clientRecordVersion, - }; - return await _channel.invokeMethod('writeBloodPressure', args) == true; - } - - /// Saves blood oxygen saturation record. - /// - /// Returns true if successful, false otherwise. - /// - /// Parameters: - /// * [saturation] - the saturation of the blood oxygen in percentage - /// * [startTime] - the start time when this [saturation] is measured. - /// Must be equal to or earlier than [endTime]. - /// * [endTime] - the end time when this [saturation] is measured. - /// Must be equal to or later than [startTime]. - /// Simply set [endTime] equal to [startTime] if the blood oxygen saturation - /// is measured only at a specific point in time (default). - /// * [recordingMethod] - the recording method of the data point. - Future writeBloodOxygen({ - required double saturation, - required DateTime startTime, - DateTime? endTime, - RecordingMethod recordingMethod = RecordingMethod.automatic, - }) async { - await _checkIfHealthConnectAvailableOnAndroid(); - if (Platform.isIOS && - [ - RecordingMethod.active, - RecordingMethod.unknown, - ].contains(recordingMethod)) { - throw ArgumentError("recordingMethod must be manual or automatic on iOS"); - } - - endTime ??= startTime; - if (startTime.isAfter(endTime)) { - throw ArgumentError("startTime must be equal or earlier than endTime"); - } - bool? success; - - if (Platform.isIOS) { - success = await writeHealthData( - value: saturation, - type: HealthDataType.BLOOD_OXYGEN, - startTime: startTime, - endTime: endTime, - recordingMethod: recordingMethod, - ); - } else if (Platform.isAndroid) { - Map args = { - 'value': saturation, - 'startTime': startTime.millisecondsSinceEpoch, - 'endTime': endTime.millisecondsSinceEpoch, - 'dataTypeKey': HealthDataType.BLOOD_OXYGEN.name, - 'recordingMethod': recordingMethod.toInt(), - }; - success = await _channel.invokeMethod('writeBloodOxygen', args); - } - return success ?? false; - } - - /// Saves meal record into Apple Health or Health Connect. - /// - /// Returns true if successful, false otherwise. - /// - /// Parameters: - /// * [mealType] - the type of meal. - /// * [startTime] - the start time when the meal was consumed. - /// It must be equal to or earlier than [endTime]. - /// * [endTime] - the end time when the meal was consumed. - /// It must be equal to or later than [startTime]. - /// * [name] - optional name information about this meal. - /// * [caloriesConsumed] - total calories consumed with this meal. - /// * [carbohydrates] - optional carbohydrates information. - /// * [protein] - optional protein information. - /// * [fatTotal] - optional total fat information. - /// * [caffeine] - optional caffeine information. - /// * [vitaminA] - optional vitamin A information. - /// * [b1Thiamin] - optional vitamin B1 (thiamin) information. - /// * [b2Riboflavin] - optional vitamin B2 (riboflavin) information. - /// * [b3Niacin] - optional vitamin B3 (niacin) information. - /// * [b5PantothenicAcid] - optional vitamin B5 (pantothenic acid) information. - /// * [b6Pyridoxine] - optional vitamin B6 (pyridoxine) information. - /// * [b7Biotin] - optional vitamin B7 (biotin) information. - /// * [b9Folate] - optional vitamin B9 (folate) information. - /// * [b12Cobalamin] - optional vitamin B12 (cobalamin) information. - /// * [vitaminC] - optional vitamin C information. - /// * [vitaminD] - optional vitamin D information. - /// * [vitaminE] - optional vitamin E information. - /// * [vitaminK] - optional vitamin K information. - /// * [calcium] - optional calcium information. - /// * [cholesterol] - optional cholesterol information. - /// * [chloride] - optional chloride information. - /// * [chromium] - optional chromium information. - /// * [copper] - optional copper information. - /// * [fatUnsaturated] - optional unsaturated fat information. - /// * [fatMonounsaturated] - optional monounsaturated fat information. - /// * [fatPolyunsaturated] - optional polyunsaturated fat information. - /// * [fatSaturated] - optional saturated fat information. - /// * [fatTransMonoenoic] - optional trans-monoenoic fat information. - /// * [fiber] - optional fiber information. - /// * [iodine] - optional iodine information. - /// * [iron] - optional iron information. - /// * [magnesium] - optional magnesium information. - /// * [manganese] - optional manganese information. - /// * [molybdenum] - optional molybdenum information. - /// * [phosphorus] - optional phosphorus information. - /// * [potassium] - optional potassium information. - /// * [selenium] - optional selenium information. - /// * [sodium] - optional sodium information. - /// * [sugar] - optional sugar information. - /// * [water] - optional water information. - /// * [zinc] - optional zinc information. - /// * [recordingMethod] - the recording method of the data point. - Future writeMeal({ - required MealType mealType, - required DateTime startTime, - required DateTime endTime, - String? clientRecordId, - double? clientRecordVersion, - double? caloriesConsumed, - double? carbohydrates, - double? protein, - double? fatTotal, - String? name, - double? caffeine, - double? vitaminA, - double? b1Thiamin, - double? b2Riboflavin, - double? b3Niacin, - double? b5PantothenicAcid, - double? b6Pyridoxine, - double? b7Biotin, - double? b9Folate, - double? b12Cobalamin, - double? vitaminC, - double? vitaminD, - double? vitaminE, - double? vitaminK, - double? calcium, - double? cholesterol, - double? chloride, - double? chromium, - double? copper, - double? fatUnsaturated, - double? fatMonounsaturated, - double? fatPolyunsaturated, - double? fatSaturated, - double? fatTransMonoenoic, - double? fiber, - double? iodine, - double? iron, - double? magnesium, - double? manganese, - double? molybdenum, - double? phosphorus, - double? potassium, - double? selenium, - double? sodium, - double? sugar, - double? water, - double? zinc, - RecordingMethod recordingMethod = RecordingMethod.automatic, - }) async { - await _checkIfHealthConnectAvailableOnAndroid(); - if (Platform.isIOS && - [ - RecordingMethod.active, - RecordingMethod.unknown, - ].contains(recordingMethod)) { - throw ArgumentError("recordingMethod must be manual or automatic on iOS"); - } - - if (startTime.isAfter(endTime)) { - throw ArgumentError("startTime must be equal or earlier than endTime"); - } - - Map args = { - 'name': name, - 'meal_type': mealType.name, - 'start_time': startTime.millisecondsSinceEpoch, - 'end_time': endTime.millisecondsSinceEpoch, - 'clientRecordId': clientRecordId, - 'clientRecordVersion': clientRecordVersion, - 'calories': caloriesConsumed, - 'carbs': carbohydrates, - 'protein': protein, - 'fat': fatTotal, - 'caffeine': caffeine, - 'vitamin_a': vitaminA, - 'b1_thiamin': b1Thiamin, - 'b2_riboflavin': b2Riboflavin, - 'b3_niacin': b3Niacin, - 'b5_pantothenic_acid': b5PantothenicAcid, - 'b6_pyridoxine': b6Pyridoxine, - 'b7_biotin': b7Biotin, - 'b9_folate': b9Folate, - 'b12_cobalamin': b12Cobalamin, - 'vitamin_c': vitaminC, - 'vitamin_d': vitaminD, - 'vitamin_e': vitaminE, - 'vitamin_k': vitaminK, - 'calcium': calcium, - 'cholesterol': cholesterol, - 'chloride': chloride, - 'chromium': chromium, - 'copper': copper, - 'fat_unsaturated': fatUnsaturated, - 'fat_monounsaturated': fatMonounsaturated, - 'fat_polyunsaturated': fatPolyunsaturated, - 'fat_saturated': fatSaturated, - 'fat_trans_monoenoic': fatTransMonoenoic, - 'fiber': fiber, - 'iodine': iodine, - 'iron': iron, - 'magnesium': magnesium, - 'manganese': manganese, - 'molybdenum': molybdenum, - 'phosphorus': phosphorus, - 'potassium': potassium, - 'selenium': selenium, - 'sodium': sodium, - 'sugar': sugar, - 'water': water, - 'zinc': zinc, - 'recordingMethod': recordingMethod.toInt(), - }; - bool? success = await _channel.invokeMethod('writeMeal', args); - return success ?? false; - } - - /// Save menstruation flow into Apple Health and Google Health Connect. - /// - /// Returns true if successful, false otherwise. - /// - /// Parameters: - /// * [flow] - the menstrual flow - /// * [startTime] - the start time when the menstrual flow is measured. - /// * [endTime] - the start time when the menstrual flow is measured. - /// * [isStartOfCycle] - A bool that indicates whether the sample represents - /// the start of a menstrual cycle. - /// * [recordingMethod] - the recording method of the data point. - Future writeMenstruationFlow({ - required MenstrualFlow flow, - required DateTime startTime, - required DateTime endTime, - required bool isStartOfCycle, - RecordingMethod recordingMethod = RecordingMethod.automatic, - }) async { - await _checkIfHealthConnectAvailableOnAndroid(); - if (Platform.isIOS && - [ - RecordingMethod.active, - RecordingMethod.unknown, - ].contains(recordingMethod)) { - throw ArgumentError("recordingMethod must be manual or automatic on iOS"); - } - - var value = Platform.isAndroid - ? MenstrualFlow.toHealthConnect(flow) - : flow.index; - - if (value == -1) { - throw ArgumentError( - "$flow is not a valid menstrual flow value for $platformType", - ); - } - - Map args = { - 'value': value, - 'startTime': startTime.millisecondsSinceEpoch, - 'endTime': endTime.millisecondsSinceEpoch, - 'isStartOfCycle': isStartOfCycle, - 'dataTypeKey': HealthDataType.MENSTRUATION_FLOW.name, - 'recordingMethod': recordingMethod.toInt(), - }; - return await _channel.invokeMethod('writeMenstruationFlow', args) == true; - } - - /// Saves audiogram into Apple Health. Not supported on Android. - /// - /// Returns true if successful, false otherwise. - /// - /// Parameters: - /// * [frequencies] - array of frequencies of the test - /// * [leftEarSensitivities] threshold in decibel for the left ear - /// * [rightEarSensitivities] threshold in decibel for the left ear - /// * [startTime] - the start time when the audiogram is measured. - /// It must be equal to or earlier than [endTime]. - /// * [endTime] - the end time when the audiogram is measured. - /// It must be equal to or later than [startTime]. - /// Simply set [endTime] equal to [startTime] if the audiogram is measured - /// only at a specific point in time (default). - /// * [metadata] - optional map of keys, both HKMetadataKeyExternalUUID - /// and HKMetadataKeyDeviceName are required - Future writeAudiogram({ - required List frequencies, - required List leftEarSensitivities, - required List rightEarSensitivities, - required DateTime startTime, - DateTime? endTime, - Map? metadata, - }) async { - if (frequencies.isEmpty || - leftEarSensitivities.isEmpty || - rightEarSensitivities.isEmpty) { - throw ArgumentError( - "frequencies, leftEarSensitivities and rightEarSensitivities can't be empty", - ); - } - if (frequencies.length != leftEarSensitivities.length || - rightEarSensitivities.length != leftEarSensitivities.length) { - throw ArgumentError( - "frequencies, leftEarSensitivities and rightEarSensitivities need to be of the same length", - ); - } - endTime ??= startTime; - if (startTime.isAfter(endTime)) { - throw ArgumentError("startTime must be equal or earlier than endTime"); - } - if (Platform.isAndroid) { - throw UnsupportedError("writeAudiogram is not supported on Android"); - } - - Map args = { - 'frequencies': frequencies, - 'leftEarSensitivities': leftEarSensitivities, - 'rightEarSensitivities': rightEarSensitivities, - 'dataTypeKey': HealthDataType.AUDIOGRAM.name, - 'startTime': startTime.millisecondsSinceEpoch, - 'endTime': endTime.millisecondsSinceEpoch, - 'metadata': metadata, - }; - return await _channel.invokeMethod('writeAudiogram', args) == true; - } - - /// Saves insulin delivery record into Apple Health. - /// - /// Returns true if successful, false otherwise. - /// - /// Parameters: - /// * [units] - the number of units of insulin taken. - /// * [reason] - the insulin reason, basal or bolus. - /// * [startTime] - the start time when the meal was consumed. - /// It must be equal to or earlier than [endTime]. - /// * [endTime] - the end time when the meal was consumed. - /// It must be equal to or later than [startTime]. - Future writeInsulinDelivery( - double units, - InsulinDeliveryReason reason, - DateTime startTime, - DateTime endTime, - ) async { - if (startTime.isAfter(endTime)) { - throw ArgumentError("startTime must be equal or earlier than endTime"); - } - - if (reason == InsulinDeliveryReason.NOT_SET) { - throw ArgumentError("set a valid insulin delivery reason"); - } - - if (Platform.isAndroid) { - throw UnsupportedError( - "writeInsulinDelivery is not supported on Android", - ); - } - - Map args = { - 'units': units, - 'reason': reason.index, - 'startTime': startTime.millisecondsSinceEpoch, - 'endTime': endTime.millisecondsSinceEpoch, - }; - - bool? success = await _channel.invokeMethod('writeInsulinDelivery', args); - return success ?? false; - } - - /// [iOS only] Fetch a `HealthDataPoint` by `uuid` and `type`. Returns `null` if no matching record. - /// - /// Parameters: - /// * [uuid] - UUID of your saved health data point (e.g. A91A2F10-3D7B-486A-B140-5ADCD3C9C6D0) - /// * [type] - Data type of your saved health data point (e.g. HealthDataType.WORKOUT) - /// - /// Assuming above data are coming from your database. - /// - /// Note: this feature is only for iOS at this moment due to - /// requires refactoring for Android. - Future getHealthDataByUUID({ - required String uuid, - required HealthDataType type, - }) async { - if (uuid.isEmpty) { - throw HealthException(type, 'UUID is empty!'); - } - - await _checkIfHealthConnectAvailableOnAndroid(); - - // Ask for device ID only once - _deviceId ??= Platform.isAndroid - ? (await _deviceInfo.androidInfo).id - : (await _deviceInfo.iosInfo).identifierForVendor; - - // If not implemented on platform, throw an exception - if (!isDataTypeAvailable(type)) { - throw HealthException(type, 'Not available on platform $platformType'); - } - - final result = await _dataQueryByUUID(uuid, type); - - debugPrint('data by UUID: ${result?.toString()}'); - - return result; - } - - /// Fetch a list of health data points based on [types]. - /// You can also specify the [recordingMethodsToFilter] to filter the data points. - /// If not specified, all data points will be included. - Future> getHealthDataFromTypes({ - required List types, - Map? preferredUnits, - required DateTime startTime, - required DateTime endTime, - List recordingMethodsToFilter = const [], - }) async { - await _checkIfHealthConnectAvailableOnAndroid(); - List dataPoints = []; - - for (var type in types) { - final result = await _prepareQuery( - startTime, - endTime, - type, - recordingMethodsToFilter, - dataUnit: preferredUnits?[type], - ); - dataPoints.addAll(result); - } - - const int threshold = 100; - if (dataPoints.length > threshold) { - return compute(removeDuplicates, dataPoints); - } - - return removeDuplicates(dataPoints); - } - - /// Fetch a list of health data points based on [types]. - /// You can also specify the [recordingMethodsToFilter] to filter the data points. - /// If not specified, all data points will be included.Vkk - Future> getHealthIntervalDataFromTypes({ - required DateTime startDate, - required DateTime endDate, - required List types, - required int interval, - List recordingMethodsToFilter = const [], - }) async { - await _checkIfHealthConnectAvailableOnAndroid(); - List dataPoints = []; - - for (var type in types) { - final result = await _prepareIntervalQuery( - startDate, - endDate, - type, - interval, - recordingMethodsToFilter, - ); - dataPoints.addAll(result); - } - - return removeDuplicates(dataPoints); - } - - /// Fetch a list of health data points based on [types]. - Future> getHealthAggregateDataFromTypes({ - required List types, - required DateTime startDate, - required DateTime endDate, - int activitySegmentDuration = 1, - bool includeManualEntry = true, - }) async { - await _checkIfHealthConnectAvailableOnAndroid(); - List dataPoints = []; - - final result = await _prepareAggregateQuery( - startDate, - endDate, - types, - activitySegmentDuration, - includeManualEntry, - ); - dataPoints.addAll(result); - - return removeDuplicates(dataPoints); - } - - /// Prepares an interval query, i.e. checks if the types are available, etc. - Future> _prepareQuery( - DateTime startTime, - DateTime endTime, - HealthDataType dataType, - List recordingMethodsToFilter, { - HealthDataUnit? dataUnit, - }) async { - // Ask for device ID only once - _deviceId ??= Platform.isAndroid - ? (await _deviceInfo.androidInfo).id - : (await _deviceInfo.iosInfo).identifierForVendor; - - // If not implemented on platform, throw an exception - if (!isDataTypeAvailable(dataType)) { - throw HealthException( - dataType, - 'Not available on platform $platformType', - ); - } - - // If BodyMassIndex is requested on Android, calculate this manually - if (dataType == HealthDataType.BODY_MASS_INDEX && Platform.isAndroid) { - return _computeAndroidBMI(startTime, endTime, recordingMethodsToFilter); - } - return await _dataQuery( - startTime, - endTime, - dataType, - recordingMethodsToFilter, - dataUnit: dataUnit, - ); - } - - /// Prepares an interval query, i.e. checks if the types are available, etc. - Future> _prepareIntervalQuery( - DateTime startDate, - DateTime endDate, - HealthDataType dataType, - int interval, - List recordingMethodsToFilter, - ) async { - // Ask for device ID only once - _deviceId ??= Platform.isAndroid - ? (await _deviceInfo.androidInfo).id - : (await _deviceInfo.iosInfo).identifierForVendor; - - // If not implemented on platform, throw an exception - if (!isDataTypeAvailable(dataType)) { - throw HealthException( - dataType, - 'Not available on platform $platformType', - ); - } - - return await _dataIntervalQuery( - startDate, - endDate, - dataType, - interval, - recordingMethodsToFilter, - ); - } - - /// Prepares an aggregate query, i.e. checks if the types are available, etc. - Future> _prepareAggregateQuery( - DateTime startDate, - DateTime endDate, - List dataTypes, - int activitySegmentDuration, - bool includeManualEntry, - ) async { - // Ask for device ID only once - _deviceId ??= Platform.isAndroid - ? (await _deviceInfo.androidInfo).id - : (await _deviceInfo.iosInfo).identifierForVendor; - - for (var type in dataTypes) { - // If not implemented on platform, throw an exception - if (!isDataTypeAvailable(type)) { - throw HealthException(type, 'Not available on platform $platformType'); - } - } - - return await _dataAggregateQuery( - startDate, - endDate, - dataTypes, - activitySegmentDuration, - includeManualEntry, - ); - } - - /// Fetches data points from Android/iOS native code. - Future> _dataQuery( - DateTime startTime, - DateTime endTime, - HealthDataType dataType, - List recordingMethodsToFilter, { - HealthDataUnit? dataUnit, - }) async { - String? unit = dataUnit?.name ?? dataTypeToUnit[dataType]?.name; - final args = { - 'dataTypeKey': dataType.name, - 'dataUnitKey': unit, - 'startTime': startTime.millisecondsSinceEpoch, - 'endTime': endTime.millisecondsSinceEpoch, - 'recordingMethodsToFilter': recordingMethodsToFilter - .map((e) => e.toInt()) - .toList(), - }; - final fetchedDataPoints = await _channel.invokeMethod('getData', args); - - if (fetchedDataPoints != null && fetchedDataPoints is List) { - final msg = { - "dataType": dataType, - "dataPoints": fetchedDataPoints, - "unit": unit, - }; - const thresHold = 100; - // If the no. of data points are larger than the threshold, - // call the compute method to spawn an Isolate to do the parsing in a separate thread. - if (fetchedDataPoints.length > thresHold) { - return compute(_parse, msg); - } - return _parse(msg); - } else { - return []; - } - } - - /// Fetches single data point by `uuid` and `type` from Android/iOS native code. - Future _dataQueryByUUID( - String uuid, - HealthDataType dataType, - ) async { - final args = { - 'dataTypeKey': dataType.name, - 'dataUnitKey': dataTypeToUnit[dataType]!.name, - 'uuid': uuid, - }; - - final fetchedDataPoint = await _channel.invokeMethod('getDataByUUID', args); - - // fetchedDataPoint is Map. // Must be converted to List first - // so no need to recreate _parse() to handle single HealthDataPoint. - - if (fetchedDataPoint != null) { - final msg = { - "dataType": dataType, - "dataPoints": [fetchedDataPoint], - }; - - // get single record of parsed fetchedDataPoints - return _parse(msg).first; - } else { - return null; - } - } - - /// function for fetching statistic health data - Future> _dataIntervalQuery( - DateTime startDate, - DateTime endDate, - HealthDataType dataType, - int interval, - List recordingMethodsToFilter, - ) async { - final args = { - 'dataTypeKey': dataType.name, - 'dataUnitKey': dataTypeToUnit[dataType]!.name, - 'startTime': startDate.millisecondsSinceEpoch, - 'endTime': endDate.millisecondsSinceEpoch, - 'interval': interval, - 'recordingMethodsToFilter': recordingMethodsToFilter - .map((e) => e.toInt()) - .toList(), - }; - - final fetchedDataPoints = await _channel.invokeMethod( - 'getIntervalData', - args, - ); - if (fetchedDataPoints != null) { - final msg = { - "dataType": dataType, - "dataPoints": fetchedDataPoints, - }; - return _parse(msg); - } - return []; - } - - /// function for fetching statistic health data - Future> _dataAggregateQuery( - DateTime startDate, - DateTime endDate, - List dataTypes, - int activitySegmentDuration, - bool includeManualEntry, - ) async { - final args = { - 'dataTypeKeys': dataTypes.map((dataType) => dataType.name).toList(), - 'startTime': startDate.millisecondsSinceEpoch, - 'endTime': endDate.millisecondsSinceEpoch, - 'activitySegmentDuration': activitySegmentDuration, - 'includeManualEntry': includeManualEntry, - }; - - final fetchedDataPoints = await _channel.invokeMethod( - 'getAggregateData', - args, - ); - - if (fetchedDataPoints != null) { - final msg = { - "dataType": HealthDataType.WORKOUT, - "dataPoints": fetchedDataPoints, - }; - return _parse(msg); - } - return []; - } - - List _parse(Map message) { - final dataType = message["dataType"] as HealthDataType; - final dataPoints = message["dataPoints"] as List; - String? unit = message["unit"] as String?; - - return dataPoints - .map( - (dataPoint) => - HealthDataPoint.fromHealthDataPoint(dataType, dataPoint, unit), - ) - .toList(); - } - - /// Return a list of [HealthDataPoint] based on [points] with no duplicates. - List removeDuplicates(List points) => - LinkedHashSet.of(points).toList(); - - /// Get the total number of steps within a specific time period. - /// Returns null if not successful. - Future getTotalStepsInInterval( - DateTime startTime, - DateTime endTime, { - bool includeManualEntry = true, - }) async { - final args = { - 'startTime': startTime.millisecondsSinceEpoch, - 'endTime': endTime.millisecondsSinceEpoch, - 'recordingMethodsToFilter': includeManualEntry - ? [] - : [RecordingMethod.manual.toInt()], - }; - final stepsCount = await _channel.invokeMethod( - 'getTotalStepsInInterval', - args, - ); - return stepsCount; - } - - /// Assigns numbers to specific [HealthDataType]s. - int _alignValue(HealthDataType type) => switch (type) { - HealthDataType.SLEEP_IN_BED => 0, - HealthDataType.SLEEP_ASLEEP => 1, - HealthDataType.SLEEP_AWAKE => 2, - HealthDataType.SLEEP_LIGHT => 3, - HealthDataType.SLEEP_DEEP => 4, - HealthDataType.SLEEP_REM => 5, - HealthDataType.HEADACHE_UNSPECIFIED => 0, - HealthDataType.HEADACHE_NOT_PRESENT => 1, - HealthDataType.HEADACHE_MILD => 2, - HealthDataType.HEADACHE_MODERATE => 3, - HealthDataType.HEADACHE_SEVERE => 4, - _ => throw HealthException( - type, - "HealthDataType was not aligned correctly - please report bug at https://github.com/cph-cachet/flutter-plugins/issues", - ), - }; - - /// Write workout data to Apple Health or Google Health Connect. - /// - /// Returns true if the workout data was successfully added. - /// - /// Parameters: - /// - [activityType] The type of activity performed. - /// - [start] The start time of the workout. - /// - [end] The end time of the workout. - /// - [totalEnergyBurned] The total energy burned during the workout. - /// - [totalEnergyBurnedUnit] The UNIT used to measure [totalEnergyBurned] - /// *ONLY FOR IOS* Default value is KILOCALORIE. - /// - [totalDistance] The total distance traveled during the workout. - /// - [totalDistanceUnit] The UNIT used to measure [totalDistance] - /// *ONLY FOR IOS* Default value is METER. - /// - [title] The title of the workout. - /// *ONLY FOR HEALTH CONNECT* Default value is the [activityType], e.g. "STRENGTH_TRAINING". - /// - [recordingMethod] The recording method of the data point, automatic by default (on iOS this can only be automatic or manual). - Future writeWorkoutData({ - required HealthWorkoutActivityType activityType, - required DateTime start, - required DateTime end, - int? totalEnergyBurned, - HealthDataUnit totalEnergyBurnedUnit = HealthDataUnit.KILOCALORIE, - int? totalDistance, - HealthDataUnit totalDistanceUnit = HealthDataUnit.METER, - String? title, - RecordingMethod recordingMethod = RecordingMethod.automatic, - }) async { - await _checkIfHealthConnectAvailableOnAndroid(); - if (Platform.isIOS && - [ - RecordingMethod.active, - RecordingMethod.unknown, - ].contains(recordingMethod)) { - throw ArgumentError("recordingMethod must be manual or automatic on iOS"); - } - - // Check that value is on the current Platform - if (Platform.isIOS && !_isOnIOS(activityType)) { - throw HealthException( - activityType, - "Workout activity type $activityType is not supported on iOS", - ); - } else if (Platform.isAndroid && !_isOnAndroid(activityType)) { - throw HealthException( - activityType, - "Workout activity type $activityType is not supported on Android", - ); - } - final args = { - 'activityType': activityType.name, - 'startTime': start.millisecondsSinceEpoch, - 'endTime': end.millisecondsSinceEpoch, - 'totalEnergyBurned': totalEnergyBurned, - 'totalEnergyBurnedUnit': totalEnergyBurnedUnit.name, - 'totalDistance': totalDistance, - 'totalDistanceUnit': totalDistanceUnit.name, - 'title': title, - 'recordingMethod': recordingMethod.toInt(), - }; - return await _channel.invokeMethod('writeWorkoutData', args) == true; - } - - /// Check if the given [HealthWorkoutActivityType] is supported on the iOS platform - bool _isOnIOS(HealthWorkoutActivityType type) { - // Returns true if the type is part of the iOS set - return { - HealthWorkoutActivityType.AMERICAN_FOOTBALL, - HealthWorkoutActivityType.ARCHERY, - HealthWorkoutActivityType.AUSTRALIAN_FOOTBALL, - HealthWorkoutActivityType.BADMINTON, - HealthWorkoutActivityType.BARRE, - HealthWorkoutActivityType.BASEBALL, - HealthWorkoutActivityType.BASKETBALL, - HealthWorkoutActivityType.BIKING, - HealthWorkoutActivityType.BOWLING, - HealthWorkoutActivityType.BOXING, - HealthWorkoutActivityType.CARDIO_DANCE, - HealthWorkoutActivityType.CLIMBING, - HealthWorkoutActivityType.COOLDOWN, - HealthWorkoutActivityType.CORE_TRAINING, - HealthWorkoutActivityType.CRICKET, - HealthWorkoutActivityType.CROSS_COUNTRY_SKIING, - HealthWorkoutActivityType.CROSS_TRAINING, - HealthWorkoutActivityType.CURLING, - HealthWorkoutActivityType.DISC_SPORTS, - HealthWorkoutActivityType.DOWNHILL_SKIING, - HealthWorkoutActivityType.ELLIPTICAL, - HealthWorkoutActivityType.EQUESTRIAN_SPORTS, - HealthWorkoutActivityType.FENCING, - HealthWorkoutActivityType.FISHING, - HealthWorkoutActivityType.FITNESS_GAMING, - HealthWorkoutActivityType.FLEXIBILITY, - HealthWorkoutActivityType.FUNCTIONAL_STRENGTH_TRAINING, - HealthWorkoutActivityType.GOLF, - HealthWorkoutActivityType.GYMNASTICS, - HealthWorkoutActivityType.HAND_CYCLING, - HealthWorkoutActivityType.HANDBALL, - HealthWorkoutActivityType.HIGH_INTENSITY_INTERVAL_TRAINING, - HealthWorkoutActivityType.HIKING, - HealthWorkoutActivityType.HOCKEY, - HealthWorkoutActivityType.HUNTING, - HealthWorkoutActivityType.JUMP_ROPE, - HealthWorkoutActivityType.KICKBOXING, - HealthWorkoutActivityType.LACROSSE, - HealthWorkoutActivityType.MARTIAL_ARTS, - HealthWorkoutActivityType.MIND_AND_BODY, - HealthWorkoutActivityType.MIXED_CARDIO, - HealthWorkoutActivityType.OTHER, - HealthWorkoutActivityType.PADDLE_SPORTS, - HealthWorkoutActivityType.PICKLEBALL, - HealthWorkoutActivityType.PILATES, - HealthWorkoutActivityType.PLAY, - HealthWorkoutActivityType.PREPARATION_AND_RECOVERY, - HealthWorkoutActivityType.RACQUETBALL, - HealthWorkoutActivityType.ROWING, - HealthWorkoutActivityType.RUGBY, - HealthWorkoutActivityType.RUNNING, - HealthWorkoutActivityType.SAILING, - HealthWorkoutActivityType.SKATING, - HealthWorkoutActivityType.SNOW_SPORTS, - HealthWorkoutActivityType.SNOWBOARDING, - HealthWorkoutActivityType.SOCCER, - HealthWorkoutActivityType.SOCIAL_DANCE, - HealthWorkoutActivityType.SOFTBALL, - HealthWorkoutActivityType.SQUASH, - HealthWorkoutActivityType.STAIR_CLIMBING, - HealthWorkoutActivityType.STAIRS, - HealthWorkoutActivityType.STEP_TRAINING, - HealthWorkoutActivityType.SURFING, - HealthWorkoutActivityType.SWIMMING, - HealthWorkoutActivityType.TABLE_TENNIS, - HealthWorkoutActivityType.TAI_CHI, - HealthWorkoutActivityType.TENNIS, - HealthWorkoutActivityType.TRACK_AND_FIELD, - HealthWorkoutActivityType.TRADITIONAL_STRENGTH_TRAINING, - HealthWorkoutActivityType.VOLLEYBALL, - HealthWorkoutActivityType.WALKING, - HealthWorkoutActivityType.WATER_FITNESS, - HealthWorkoutActivityType.WATER_POLO, - HealthWorkoutActivityType.WATER_SPORTS, - HealthWorkoutActivityType.WHEELCHAIR_RUN_PACE, - HealthWorkoutActivityType.WHEELCHAIR_WALK_PACE, - HealthWorkoutActivityType.WRESTLING, - HealthWorkoutActivityType.YOGA, - HealthWorkoutActivityType.SWIMMING_OPEN_WATER, - HealthWorkoutActivityType.SWIMMING_POOL, - HealthWorkoutActivityType.UNDERWATER_DIVING, - }.contains(type); - } - - /// Check if the given [HealthWorkoutActivityType] is supported on the Android platform - bool _isOnAndroid(HealthWorkoutActivityType type) { - // Returns true if the type is part of the Android set - return { - // Both - HealthWorkoutActivityType.AMERICAN_FOOTBALL, - HealthWorkoutActivityType.ARCHERY, - HealthWorkoutActivityType.AUSTRALIAN_FOOTBALL, - HealthWorkoutActivityType.BADMINTON, - HealthWorkoutActivityType.BASEBALL, - HealthWorkoutActivityType.BASKETBALL, - HealthWorkoutActivityType.BIKING, - HealthWorkoutActivityType.BOXING, - HealthWorkoutActivityType.CARDIO_DANCE, - HealthWorkoutActivityType.CRICKET, - HealthWorkoutActivityType.CROSS_COUNTRY_SKIING, - HealthWorkoutActivityType.CURLING, - HealthWorkoutActivityType.DOWNHILL_SKIING, - HealthWorkoutActivityType.ELLIPTICAL, - HealthWorkoutActivityType.FENCING, - HealthWorkoutActivityType.GOLF, - HealthWorkoutActivityType.GYMNASTICS, - HealthWorkoutActivityType.HANDBALL, - HealthWorkoutActivityType.HIGH_INTENSITY_INTERVAL_TRAINING, - HealthWorkoutActivityType.HIKING, - HealthWorkoutActivityType.HOCKEY, - HealthWorkoutActivityType.MARTIAL_ARTS, - HealthWorkoutActivityType.PILATES, - HealthWorkoutActivityType.RACQUETBALL, - HealthWorkoutActivityType.ROWING, - HealthWorkoutActivityType.RUGBY, - HealthWorkoutActivityType.RUNNING, - HealthWorkoutActivityType.SAILING, - HealthWorkoutActivityType.SKATING, - HealthWorkoutActivityType.SNOWBOARDING, - HealthWorkoutActivityType.SOCCER, - HealthWorkoutActivityType.SOCIAL_DANCE, - HealthWorkoutActivityType.SOFTBALL, - HealthWorkoutActivityType.SQUASH, - HealthWorkoutActivityType.STAIR_CLIMBING, - HealthWorkoutActivityType.TABLE_TENNIS, - HealthWorkoutActivityType.TENNIS, - HealthWorkoutActivityType.VOLLEYBALL, - HealthWorkoutActivityType.WALKING, - HealthWorkoutActivityType.WATER_POLO, - HealthWorkoutActivityType.WHEELCHAIR_RUN_PACE, - HealthWorkoutActivityType.WHEELCHAIR_WALK_PACE, - HealthWorkoutActivityType.YOGA, - - // Android only - HealthWorkoutActivityType.BIKING_STATIONARY, - HealthWorkoutActivityType.CALISTHENICS, - HealthWorkoutActivityType.DANCING, - HealthWorkoutActivityType.FRISBEE_DISC, - HealthWorkoutActivityType.GUIDED_BREATHING, - HealthWorkoutActivityType.ICE_SKATING, - HealthWorkoutActivityType.PARAGLIDING, - HealthWorkoutActivityType.ROCK_CLIMBING, - HealthWorkoutActivityType.ROWING_MACHINE, - HealthWorkoutActivityType.RUNNING_TREADMILL, - HealthWorkoutActivityType.SCUBA_DIVING, - HealthWorkoutActivityType.SKIING, - HealthWorkoutActivityType.SNOWSHOEING, - HealthWorkoutActivityType.STAIR_CLIMBING_MACHINE, - HealthWorkoutActivityType.STRENGTH_TRAINING, - HealthWorkoutActivityType.SURFING, - HealthWorkoutActivityType.SWIMMING_OPEN_WATER, - HealthWorkoutActivityType.SWIMMING_POOL, - HealthWorkoutActivityType.WALKING_TREADMILL, - HealthWorkoutActivityType.WEIGHTLIFTING, - HealthWorkoutActivityType.WHEELCHAIR, - HealthWorkoutActivityType.OTHER, - }.contains(type); - } -} diff --git a/packages/health/lib/src/health_value_types.dart b/packages/health/lib/src/health_value_types.dart deleted file mode 100644 index fd3b8c0aa..000000000 --- a/packages/health/lib/src/health_value_types.dart +++ /dev/null @@ -1,976 +0,0 @@ -part of '../health.dart'; - -/// An abstract class for health values. -@JsonSerializable(includeIfNull: false, explicitToJson: true) -class HealthValue extends Serializable { - HealthValue(); - - @override - Function get fromJsonFunction => _$HealthValueFromJson; - factory HealthValue.fromJson(Map json) => - FromJsonFactory().fromJson(json); - @override - Map toJson() => _$HealthValueToJson(this); -} - -/// A numerical value from Apple HealthKit or Google Health Connect -/// such as integer or double. E.g. 1, 2.9, -3 -/// -/// Parameters: -/// * [numericValue] - a [num] value for the [HealthDataPoint] -@JsonSerializable(includeIfNull: false, explicitToJson: true) -class NumericHealthValue extends HealthValue { - /// A [num] value for the [HealthDataPoint]. - num numericValue; - - NumericHealthValue({required this.numericValue}); - - /// Create a [NumericHealthValue] based on a health data point from native data format. - factory NumericHealthValue.fromHealthDataPoint(dynamic dataPoint) => - NumericHealthValue(numericValue: dataPoint['value'] as num? ?? 0); - - @override - String toString() => '$runtimeType - numericValue: $numericValue'; - - @override - Function get fromJsonFunction => _$NumericHealthValueFromJson; - factory NumericHealthValue.fromJson(Map json) => - FromJsonFactory().fromJson(json); - @override - Map toJson() => _$NumericHealthValueToJson(this); - - @override - bool operator ==(Object other) => - other is NumericHealthValue && numericValue == other.numericValue; - - @override - int get hashCode => numericValue.hashCode; -} - -/// A [HealthValue] object for audiograms -/// -/// Parameters: -/// * [frequencies] - array of frequencies of the test -/// * [leftEarSensitivities] threshold in decibel for the left ear -/// * [rightEarSensitivities] threshold in decibel for the left ear -@JsonSerializable(includeIfNull: false, explicitToJson: true) -class AudiogramHealthValue extends HealthValue { - /// Array of frequencies of the test. - List frequencies; - - /// Threshold in decibel for the left ear. - List leftEarSensitivities; - - /// Threshold in decibel for the right ear. - List rightEarSensitivities; - - AudiogramHealthValue({ - required this.frequencies, - required this.leftEarSensitivities, - required this.rightEarSensitivities, - }); - - /// Create a [AudiogramHealthValue] based on a health data point from native data format. - factory AudiogramHealthValue.fromHealthDataPoint(dynamic dataPoint) => - AudiogramHealthValue( - frequencies: List.from(dataPoint['frequencies'] as List), - leftEarSensitivities: List.from( - dataPoint['leftEarSensitivities'] as List, - ), - rightEarSensitivities: List.from( - dataPoint['rightEarSensitivities'] as List, - ), - ); - - @override - String toString() => - """$runtimeType - frequencies: ${frequencies.toString()}, - left ear sensitivities: ${leftEarSensitivities.toString()}, - right ear sensitivities: ${rightEarSensitivities.toString()}"""; - - @override - Function get fromJsonFunction => _$AudiogramHealthValueFromJson; - factory AudiogramHealthValue.fromJson(Map json) => - FromJsonFactory().fromJson(json); - @override - Map toJson() => _$AudiogramHealthValueToJson(this); - - @override - bool operator ==(Object other) => - other is AudiogramHealthValue && - listEquals(frequencies, other.frequencies) && - listEquals(leftEarSensitivities, other.leftEarSensitivities) && - listEquals(rightEarSensitivities, other.rightEarSensitivities); - - @override - int get hashCode => - Object.hash(frequencies, leftEarSensitivities, rightEarSensitivities); -} - -/// A [HealthValue] object for workouts -/// -/// Parameters: -/// * [workoutActivityType] - the type of workout -/// * [totalEnergyBurned] - the total energy burned during the workout -/// * [totalEnergyBurnedUnit] - the unit of the total energy burned -/// * [totalDistance] - the total distance of the workout -/// * [totalDistanceUnit] - the unit of the total distance -@JsonSerializable(includeIfNull: false, explicitToJson: true) -class WorkoutHealthValue extends HealthValue { - /// The type of the workout. - HealthWorkoutActivityType workoutActivityType; - - /// The total energy burned during the workout. - /// Might not be available for all workouts. - int? totalEnergyBurned; - - /// The unit of the total energy burned during the workout. - /// Might not be available for all workouts. - HealthDataUnit? totalEnergyBurnedUnit; - - /// The total distance covered during the workout. - /// Might not be available for all workouts. - int? totalDistance; - - /// The unit of the total distance covered during the workout. - /// Might not be available for all workouts. - HealthDataUnit? totalDistanceUnit; - - /// The total steps covered during the workout. - /// Might not be available for all workouts. - int? totalSteps; - - /// The unit of the total steps covered during the workout. - /// Might not be available for all workouts. - HealthDataUnit? totalStepsUnit; - - WorkoutHealthValue({ - required this.workoutActivityType, - this.totalEnergyBurned, - this.totalEnergyBurnedUnit, - this.totalDistance, - this.totalDistanceUnit, - this.totalSteps, - this.totalStepsUnit, - }); - - /// Create a [WorkoutHealthValue] based on a health data point from native data format. - factory WorkoutHealthValue.fromHealthDataPoint(dynamic dataPoint) => - WorkoutHealthValue( - workoutActivityType: HealthWorkoutActivityType.values.firstWhere( - (element) => element.name == dataPoint['workoutActivityType'], - orElse: () => HealthWorkoutActivityType.OTHER, - ), - totalEnergyBurned: dataPoint['totalEnergyBurned'] != null - ? (dataPoint['totalEnergyBurned'] as num).toInt() - : null, - totalEnergyBurnedUnit: dataPoint['totalEnergyBurnedUnit'] != null - ? HealthDataUnit.values.firstWhere( - (element) => element.name == dataPoint['totalEnergyBurnedUnit'], - ) - : null, - totalDistance: dataPoint['totalDistance'] != null - ? (dataPoint['totalDistance'] as num).toInt() - : null, - totalDistanceUnit: dataPoint['totalDistanceUnit'] != null - ? HealthDataUnit.values.firstWhere( - (element) => element.name == dataPoint['totalDistanceUnit'], - ) - : null, - totalSteps: dataPoint['totalSteps'] != null - ? (dataPoint['totalSteps'] as num).toInt() - : null, - totalStepsUnit: dataPoint['totalStepsUnit'] != null - ? HealthDataUnit.values.firstWhere( - (element) => element.name == dataPoint['totalStepsUnit'], - ) - : null, - ); - - @override - Function get fromJsonFunction => _$WorkoutHealthValueFromJson; - factory WorkoutHealthValue.fromJson(Map json) => - FromJsonFactory().fromJson(json); - @override - Map toJson() => _$WorkoutHealthValueToJson(this); - - @override - String toString() => - """$runtimeType - workoutActivityType: ${workoutActivityType.name}, - totalEnergyBurned: $totalEnergyBurned, - totalEnergyBurnedUnit: ${totalEnergyBurnedUnit?.name}, - totalDistance: $totalDistance, - totalDistanceUnit: ${totalDistanceUnit?.name} - totalSteps: $totalSteps, - totalStepsUnit: ${totalStepsUnit?.name}"""; - - @override - bool operator ==(Object other) => - other is WorkoutHealthValue && - workoutActivityType == other.workoutActivityType && - totalEnergyBurned == other.totalEnergyBurned && - totalEnergyBurnedUnit == other.totalEnergyBurnedUnit && - totalDistance == other.totalDistance && - totalDistanceUnit == other.totalDistanceUnit && - totalSteps == other.totalSteps && - totalStepsUnit == other.totalStepsUnit; - - @override - int get hashCode => Object.hash( - workoutActivityType, - totalEnergyBurned, - totalEnergyBurnedUnit, - totalDistance, - totalDistanceUnit, - totalSteps, - totalStepsUnit, - ); -} - -/// A [HealthValue] object for ECGs -/// -/// Parameters: -/// * [voltageValues] - an array of [ElectrocardiogramVoltageValue]s -/// * [averageHeartRate] - the average heart rate during the ECG (in BPM) -/// * [samplingFrequency] - the frequency at which the Apple Watch sampled the voltage. -/// * [classification] - an [ElectrocardiogramClassification] -@JsonSerializable(includeIfNull: false, explicitToJson: true) -class ElectrocardiogramHealthValue extends HealthValue { - /// An array of [ElectrocardiogramVoltageValue]s. - List voltageValues; - - /// The average heart rate during the ECG (in BPM). - num? averageHeartRate; - - /// The frequency at which the Apple Watch sampled the voltage. - double? samplingFrequency; - - /// An [ElectrocardiogramClassification]. - ElectrocardiogramClassification? classification; - - ElectrocardiogramHealthValue({ - required this.voltageValues, - this.averageHeartRate, - this.samplingFrequency, - this.classification, - }); - - @override - Function get fromJsonFunction => _$ElectrocardiogramHealthValueFromJson; - factory ElectrocardiogramHealthValue.fromJson(Map json) => - FromJsonFactory().fromJson(json); - @override - Map toJson() => _$ElectrocardiogramHealthValueToJson(this); - - /// Create a [ElectrocardiogramHealthValue] based on a health data point from native data format. - factory ElectrocardiogramHealthValue.fromHealthDataPoint(dynamic dataPoint) => - ElectrocardiogramHealthValue( - voltageValues: (dataPoint['voltageValues'] as List) - .map( - (voltageValue) => - ElectrocardiogramVoltageValue.fromHealthDataPoint( - voltageValue, - ), - ) - .toList(), - averageHeartRate: dataPoint['averageHeartRate'] as num?, - samplingFrequency: dataPoint['samplingFrequency'] as double?, - classification: ElectrocardiogramClassification.values.firstWhere( - (c) => c.value == dataPoint['classification'], - ), - ); - - @override - bool operator ==(Object other) => - other is ElectrocardiogramHealthValue && - voltageValues == other.voltageValues && - averageHeartRate == other.averageHeartRate && - samplingFrequency == other.samplingFrequency && - classification == other.classification; - - @override - int get hashCode => Object.hash( - voltageValues, - averageHeartRate, - samplingFrequency, - classification, - ); - - @override - String toString() => - '$runtimeType - ${voltageValues.length} values, $averageHeartRate BPM, $samplingFrequency HZ, $classification'; -} - -/// Single voltage value belonging to a [ElectrocardiogramHealthValue] -@JsonSerializable(includeIfNull: false, explicitToJson: true) -class ElectrocardiogramVoltageValue extends HealthValue { - /// Voltage of the ECG. - num voltage; - - /// Time since the start of the ECG. - num timeSinceSampleStart; - - ElectrocardiogramVoltageValue({ - required this.voltage, - required this.timeSinceSampleStart, - }); - - /// Create a [ElectrocardiogramVoltageValue] based on a health data point from native data format. - factory ElectrocardiogramVoltageValue.fromHealthDataPoint( - dynamic dataPoint, - ) => ElectrocardiogramVoltageValue( - voltage: dataPoint['voltage'] as num, - timeSinceSampleStart: dataPoint['timeSinceSampleStart'] as num, - ); - - @override - Function get fromJsonFunction => _$ElectrocardiogramVoltageValueFromJson; - factory ElectrocardiogramVoltageValue.fromJson(Map json) => - FromJsonFactory().fromJson(json); - @override - Map toJson() => _$ElectrocardiogramVoltageValueToJson(this); - - @override - bool operator ==(Object other) => - other is ElectrocardiogramVoltageValue && - voltage == other.voltage && - timeSinceSampleStart == other.timeSinceSampleStart; - - @override - int get hashCode => Object.hash(voltage, timeSinceSampleStart); - - @override - String toString() => '$runtimeType - voltage: $voltage'; -} - -/// A [HealthValue] object from insulin delivery (iOS only) -@JsonSerializable(includeIfNull: false, explicitToJson: true) -class InsulinDeliveryHealthValue extends HealthValue { - /// The amount of units of insulin taken - double units; - - /// If it's basal, bolus or unknown reason for insulin dosage - InsulinDeliveryReason reason; - - InsulinDeliveryHealthValue({required this.units, required this.reason}); - - factory InsulinDeliveryHealthValue.fromHealthDataPoint(dynamic dataPoint) { - final units = dataPoint['value'] as num; - - final metadata = dataPoint['metadata'] == null - ? null - : Map.from(dataPoint['metadata'] as Map); - final reasonIndex = - metadata == null || !metadata.containsKey('HKInsulinDeliveryReason') - ? 0 - : metadata['HKInsulinDeliveryReason'] as double; - final reason = InsulinDeliveryReason.values[reasonIndex.toInt()]; - - return InsulinDeliveryHealthValue(units: units.toDouble(), reason: reason); - } - - @override - Function get fromJsonFunction => _$InsulinDeliveryHealthValueFromJson; - factory InsulinDeliveryHealthValue.fromJson(Map json) => - FromJsonFactory().fromJson(json); - @override - Map toJson() => _$InsulinDeliveryHealthValueToJson(this); - - @override - bool operator ==(Object other) => - other is InsulinDeliveryHealthValue && - units == other.units && - reason == other.reason; - - @override - int get hashCode => Object.hash(units, reason); - - @override - String toString() => '$runtimeType - units: $units, reason: $reason'; -} - -/// A [HealthValue] object for nutrition. -/// -/// Parameters: -/// * [mealType] - the type of meal -/// * [name] - the name of the food -/// * [b1Thiamine] - the amount of thiamine (B1) in grams -/// * [b2Riboflavin] - the amount of riboflavin (B2) in grams -/// * [b3Niacin] - the amount of niacin (B3) in grams -/// * [b5PantothenicAcid] - the amount of pantothenic acid (B5) in grams -/// * [b6Pyridoxine] - the amount of pyridoxine (B6) in grams -/// * [b7Biotin] - the amount of biotin (B7) in grams -/// * [b9Folate] - the amount of folate (B9) in grams -/// * [b12Cobalamin] - the amount of cobalamin (B12) in grams -/// * [caffeine] - the amount of caffeine in grams -/// * [calcium] - the amount of calcium in grams -/// * [calories] - the amount of calories in kcal -/// * [carbs] - the amount of carbs in grams -/// * [chloride] - the amount of chloride in grams -/// * [cholesterol] - the amount of cholesterol in grams -/// * [choline] - the amount of choline in grams -/// * [chromium] - the amount of chromium in grams -/// * [copper] - the amount of copper in grams -/// * [fat] - the amount of fat in grams -/// * [fatMonounsaturated] - the amount of monounsaturated fat in grams -/// * [fatPolyunsaturated] - the amount of polyunsaturated fat in grams -/// * [fatSaturated] - the amount of saturated fat in grams -/// * [fatTransMonoenoic] - the amount of trans-monoenoic fat in grams -/// * [fatUnsaturated] - the amount of unsaturated fat in grams -/// * [fiber] - the amount of fiber in grams -/// * [iodine] - the amount of iodine in grams -/// * [iron] - the amount of iron in grams -/// * [magnesium] - the amount of magnesium in grams -/// * [manganese] - the amount of manganese in grams -/// * [molybdenum] - the amount of molybdenum in grams -/// * [phosphorus] - the amount of phosphorus in grams -/// * [potassium] - the amount of potassium in grams -/// * [protein] - the amount of protein in grams -/// * [selenium] - the amount of selenium in grams -/// * [sodium] - the amount of sodium in grams -/// * [sugar] - the amount of sugar in grams -/// * [vitaminA] - the amount of vitamin A in grams -/// * [vitaminC] - the amount of vitamin C in grams -/// * [vitaminD] - the amount of vitamin D in grams -/// * [vitaminE] - the amount of vitamin E in grams -/// * [vitaminK] - the amount of vitamin K in grams -/// * [water] - the amount of water in grams -/// * [zinc] - the amount of zinc in grams - -@JsonSerializable(includeIfNull: false, explicitToJson: true) -class NutritionHealthValue extends HealthValue { - /// The name of the food. - String? name; - - /// The type of meal. - @JsonKey(name: 'meal_type') - String? mealType; - - /// The amount of calories in kcal. - double? calories; - - /// The amount of protein in grams. - double? protein; - - /// The amount of fat in grams. - double? fat; - - /// The amount of carbs in grams. - double? carbs; - - /// The amount of caffeine in grams. - double? caffeine; - - /// The amount of vitamin A in grams. - @JsonKey(name: 'vitamin_a') - double? vitaminA; - - /// The amount of thiamine (B1) in grams. - @JsonKey(name: 'b1_thiamine') - double? b1Thiamine; - - /// The amount of riboflavin (B2) in grams. - @JsonKey(name: 'b2_riboflavin') - double? b2Riboflavin; - - /// The amount of niacin (B3) in grams. - @JsonKey(name: 'b3_niacin') - double? b3Niacin; - - /// The amount of pantothenic acid (B5) in grams. - @JsonKey(name: 'b5_pantothenic_acid') - double? b5PantothenicAcid; - - /// The amount of pyridoxine (B6) in grams. - @JsonKey(name: 'b6_pyridoxine') - double? b6Pyridoxine; - - /// The amount of biotin (B7) in grams. - @JsonKey(name: 'b7_biotin') - double? b7Biotin; - - /// The amount of folate (B9) in grams. - @JsonKey(name: 'b9_folate') - double? b9Folate; - - /// The amount of cobalamin (B12) in grams. - @JsonKey(name: 'b12_cobalamin') - double? b12Cobalamin; - - /// The amount of vitamin C in grams. - @JsonKey(name: 'vitamin_c') - double? vitaminC; - - /// The amount of vitamin D in grams. - @JsonKey(name: 'vitamin_d') - double? vitaminD; - - /// The amount of vitamin E in grams. - @JsonKey(name: 'vitamin_e') - double? vitaminE; - - /// The amount of vitamin K in grams. - @JsonKey(name: 'vitamin_k') - double? vitaminK; - - /// The amount of calcium in grams. - double? calcium; - - /// The amount of chloride in grams. - double? chloride; - - /// The amount of cholesterol in grams. - double? cholesterol; - - /// The amount of choline in grams. - double? choline; - - /// The amount of chromium in grams. - double? chromium; - - /// The amount of copper in grams. - double? copper; - - /// The amount of unsaturated fat in grams. - @JsonKey(name: 'fat_unsaturated') - double? fatUnsaturated; - - /// The amount of monounsaturated fat in grams. - @JsonKey(name: 'fat_monounsaturated') - double? fatMonounsaturated; - - /// The amount of polyunsaturated fat in grams. - @JsonKey(name: 'fat_polyunsaturated') - double? fatPolyunsaturated; - - /// The amount of saturated fat in grams. - @JsonKey(name: 'fat_saturated') - double? fatSaturated; - - /// The amount of trans-monoenoic fat in grams. - @JsonKey(name: 'fat_trans_monoenoic') - double? fatTransMonoenoic; - - /// The amount of fiber in grams. - double? fiber; - - /// The amount of iodine in grams. - double? iodine; - - /// The amount of iron in grams. - double? iron; - - /// The amount of magnesium in grams. - double? magnesium; - - /// The amount of manganese in grams. - double? manganese; - - /// The amount of molybdenum in grams. - double? molybdenum; - - /// The amount of phosphorus in grams. - double? phosphorus; - - /// The amount of potassium in grams. - double? potassium; - - /// The amount of selenium in grams. - double? selenium; - - /// The amount of sodium in grams. - double? sodium; - - /// The amount of sugar in grams. - double? sugar; - - /// The amount of water in grams. - double? water; - - /// The amount of zinc in grams. - double? zinc; - - NutritionHealthValue({ - this.name, - this.mealType, - this.calories, - this.protein, - this.fat, - this.carbs, - this.caffeine, - this.vitaminA, - this.b1Thiamine, - this.b2Riboflavin, - this.b3Niacin, - this.b5PantothenicAcid, - this.b6Pyridoxine, - this.b7Biotin, - this.b9Folate, - this.b12Cobalamin, - this.vitaminC, - this.vitaminD, - this.vitaminE, - this.vitaminK, - this.calcium, - this.chloride, - this.cholesterol, - this.choline, - this.chromium, - this.copper, - this.fatUnsaturated, - this.fatMonounsaturated, - this.fatPolyunsaturated, - this.fatSaturated, - this.fatTransMonoenoic, - this.fiber, - this.iodine, - this.iron, - this.magnesium, - this.manganese, - this.molybdenum, - this.phosphorus, - this.potassium, - this.selenium, - this.sodium, - this.sugar, - this.water, - this.zinc, - }); - - @override - Function get fromJsonFunction => _$NutritionHealthValueFromJson; - factory NutritionHealthValue.fromJson(Map json) => - _$NutritionHealthValueFromJson(json); - @override - Map toJson() => _$NutritionHealthValueToJson(this); - - /// Create a [NutritionHealthValue] based on a health data point from native data format. - factory NutritionHealthValue.fromHealthDataPoint(dynamic dataPoint) { - dataPoint = dataPoint as Map; - // Convert to Map and ensure all expected fields are present - final Map dataPointMap = {}; - - // Add all entries from the native data - dataPoint.forEach((key, value) { - if (key != null) { - dataPointMap[key as String] = value; - } - }); - - return _$NutritionHealthValueFromJson(dataPointMap); - } - - @override - String toString() => - """$runtimeType - protein: ${protein.toString()}, - calories: ${calories.toString()}, - fat: ${fat.toString()}, - name: ${name.toString()}, - carbs: ${carbs.toString()}, - caffeine: ${caffeine.toString()}, - mealType: $mealType, - vitaminA: ${vitaminA.toString()}, - b1Thiamine: ${b1Thiamine.toString()}, - b2Riboflavin: ${b2Riboflavin.toString()}, - b3Niacin: ${b3Niacin.toString()}, - b5PantothenicAcid: ${b5PantothenicAcid.toString()}, - b6Pyridoxine: ${b6Pyridoxine.toString()}, - b7Biotin: ${b7Biotin.toString()}, - b9Folate: ${b9Folate.toString()}, - b12Cobalamin: ${b12Cobalamin.toString()}, - vitaminC: ${vitaminC.toString()}, - vitaminD: ${vitaminD.toString()}, - vitaminE: ${vitaminE.toString()}, - vitaminK: ${vitaminK.toString()}, - calcium: ${calcium.toString()}, - chloride: ${chloride.toString()}, - cholesterol: ${cholesterol.toString()}, - choline: ${choline.toString()}, - chromium: ${chromium.toString()}, - copper: ${copper.toString()}, - unsaturatedFat: ${fatUnsaturated.toString()}, - fatMonounsaturated: ${fatMonounsaturated.toString()}, - fatPolyunsaturated: ${fatPolyunsaturated.toString()}, - fatSaturated: ${fatSaturated.toString()}, - fatTransMonoenoic: ${fatTransMonoenoic.toString()}, - fiber: ${fiber.toString()}, - iodine: ${iodine.toString()}, - iron: ${iron.toString()}, - magnesium: ${magnesium.toString()}, - manganese: ${manganese.toString()}, - molybdenum: ${molybdenum.toString()}, - phosphorus: ${phosphorus.toString()}, - potassium: ${potassium.toString()}, - selenium: ${selenium.toString()}, - sodium: ${sodium.toString()}, - sugar: ${sugar.toString()}, - water: ${water.toString()}, - zinc: ${zinc.toString()}"""; - - @override - bool operator ==(Object other) => - other is NutritionHealthValue && - other.name == name && - other.mealType == mealType && - other.calories == calories && - other.protein == protein && - other.fat == fat && - other.carbs == carbs && - other.caffeine == caffeine && - other.vitaminA == vitaminA && - other.b1Thiamine == b1Thiamine && - other.b2Riboflavin == b2Riboflavin && - other.b3Niacin == b3Niacin && - other.b5PantothenicAcid == b5PantothenicAcid && - other.b6Pyridoxine == b6Pyridoxine && - other.b7Biotin == b7Biotin && - other.b9Folate == b9Folate && - other.b12Cobalamin == b12Cobalamin && - other.vitaminC == vitaminC && - other.vitaminD == vitaminD && - other.vitaminE == vitaminE && - other.vitaminK == vitaminK && - other.calcium == calcium && - other.chloride == chloride && - other.cholesterol == cholesterol && - other.choline == choline && - other.chromium == chromium && - other.copper == copper && - other.fatUnsaturated == fatUnsaturated && - other.fatMonounsaturated == fatMonounsaturated && - other.fatPolyunsaturated == fatPolyunsaturated && - other.fatSaturated == fatSaturated && - other.fatTransMonoenoic == fatTransMonoenoic && - other.fiber == fiber && - other.iodine == iodine && - other.iron == iron && - other.magnesium == magnesium && - other.manganese == manganese && - other.molybdenum == molybdenum && - other.phosphorus == phosphorus && - other.potassium == potassium && - other.selenium == selenium && - other.sodium == sodium && - other.sugar == sugar && - other.water == water && - other.zinc == zinc; - - @override - int get hashCode => Object.hashAll([ - protein, - calories, - fat, - name, - carbs, - caffeine, - vitaminA, - b1Thiamine, - b2Riboflavin, - b3Niacin, - b5PantothenicAcid, - b6Pyridoxine, - b7Biotin, - b9Folate, - b12Cobalamin, - vitaminC, - vitaminD, - vitaminE, - vitaminK, - calcium, - chloride, - cholesterol, - choline, - chromium, - copper, - fatUnsaturated, - fatMonounsaturated, - fatPolyunsaturated, - fatSaturated, - fatTransMonoenoic, - fiber, - iodine, - iron, - magnesium, - manganese, - molybdenum, - phosphorus, - potassium, - selenium, - sodium, - sugar, - water, - zinc, - ]); -} - -enum MenstrualFlow { - unspecified, - none, - light, - medium, - heavy, - spotting; - - static MenstrualFlow fromHealthConnect(int value) { - switch (value) { - case 0: - return MenstrualFlow.unspecified; - case 1: - return MenstrualFlow.light; - case 2: - return MenstrualFlow.medium; - case 3: - return MenstrualFlow.heavy; - default: - return MenstrualFlow.unspecified; - } - } - - static MenstrualFlow fromHealthKit(int value) { - switch (value) { - case 1: - return MenstrualFlow.unspecified; - case 2: - return MenstrualFlow.light; - case 3: - return MenstrualFlow.medium; - case 4: - return MenstrualFlow.heavy; - case 5: - return MenstrualFlow.none; - default: - return MenstrualFlow.unspecified; - } - } - - static int toHealthConnect(MenstrualFlow value) { - switch (value) { - case MenstrualFlow.unspecified: - return 0; - case MenstrualFlow.light: - return 1; - case MenstrualFlow.medium: - return 2; - case MenstrualFlow.heavy: - return 3; - default: - return -1; - } - } -} - -enum RecordingMethod { - unknown, - active, - automatic, - manual; - - /// Create a [RecordingMethod] from an integer. - /// 0: unknown, 1: active, 2: automatic, 3: manual - /// If the integer is not in the range of 0-3, [RecordingMethod.unknown] is returned. - /// This is used to align the recording method with the platform. - static RecordingMethod fromInt(int? recordingMethod) { - switch (recordingMethod) { - case 0: - return RecordingMethod.unknown; - case 1: - return RecordingMethod.active; - case 2: - return RecordingMethod.automatic; - case 3: - return RecordingMethod.manual; - default: - return RecordingMethod.unknown; - } - } - - /// Convert this [RecordingMethod] to an integer. - int toInt() { - switch (this) { - case RecordingMethod.unknown: - return 0; - case RecordingMethod.active: - return 1; - case RecordingMethod.automatic: - return 2; - case RecordingMethod.manual: - return 3; - } - } -} - -/// A [HealthValue] object for menstrual flow. -/// -/// Parameters: -/// * [flowValue] - the flow value -/// * [isStartOfCycle] - indicator whether or not this occurrence is the first day of the menstrual cycle (iOS only) -/// * [wasUserEntered] - indicator whether or not the data was entered by the user (iOS only) -/// * [dateTime] - the date and time of the menstrual flow -@JsonSerializable(includeIfNull: false, explicitToJson: true) -class MenstruationFlowHealthValue extends HealthValue { - final MenstrualFlow? flow; - final bool? isStartOfCycle; - final bool? wasUserEntered; - final DateTime dateTime; - - MenstruationFlowHealthValue({ - required this.flow, - required this.dateTime, - this.isStartOfCycle, - this.wasUserEntered, - }); - - @override - String toString() => - "flow: ${flow?.name}, startOfCycle: $isStartOfCycle, wasUserEntered: $wasUserEntered, dateTime: $dateTime"; - - factory MenstruationFlowHealthValue.fromHealthDataPoint(dynamic dataPoint) { - // Parse flow value safely - final flowValueIndex = dataPoint['value'] as int? ?? 0; - MenstrualFlow? menstrualFlow; - if (Platform.isAndroid) { - menstrualFlow = MenstrualFlow.fromHealthConnect(flowValueIndex); - } else if (Platform.isIOS) { - menstrualFlow = MenstrualFlow.fromHealthKit(flowValueIndex); - } - - return MenstruationFlowHealthValue( - flow: menstrualFlow, - isStartOfCycle: - dataPoint['metadata']?.containsKey('HKMenstrualCycleStart') == true - ? dataPoint['metadata']['HKMenstrualCycleStart'] == 1.0 - : null, - wasUserEntered: - dataPoint['metadata']?.containsKey('HKWasUserEntered') == true - ? dataPoint['metadata']['HKWasUserEntered'] == 1.0 - : null, - dateTime: DateTime.fromMillisecondsSinceEpoch( - dataPoint['date_from'] as int, - ), - ); - } - - @override - Function get fromJsonFunction => _$MenstruationFlowHealthValueFromJson; - - factory MenstruationFlowHealthValue.fromJson(Map json) => - FromJsonFactory().fromJson(json); - - @override - Map toJson() => _$MenstruationFlowHealthValueToJson(this); - - @override - bool operator ==(Object other) { - return identical(this, other) || - other is MenstruationFlowHealthValue && - runtimeType == other.runtimeType && - flow == other.flow && - isStartOfCycle == other.isStartOfCycle && - wasUserEntered == other.wasUserEntered && - dateTime == other.dateTime; - } - - @override - int get hashCode => - Object.hash(flow, isStartOfCycle, wasUserEntered, dateTime); -} diff --git a/packages/health/lib/src/heath_data_types.dart b/packages/health/lib/src/heath_data_types.dart deleted file mode 100644 index 3d23592e3..000000000 --- a/packages/health/lib/src/heath_data_types.dart +++ /dev/null @@ -1,630 +0,0 @@ -part of '../health.dart'; - -/// List of all available health data types. -enum HealthDataType { - ACTIVE_ENERGY_BURNED, - ATRIAL_FIBRILLATION_BURDEN, - APPLE_STAND_HOUR, - APPLE_MOVE_TIME, - APPLE_STAND_TIME, - AUDIOGRAM, - BASAL_ENERGY_BURNED, - BLOOD_GLUCOSE, - BLOOD_OXYGEN, - BLOOD_PRESSURE_DIASTOLIC, - BLOOD_PRESSURE_SYSTOLIC, - BODY_FAT_PERCENTAGE, - LEAN_BODY_MASS, - BODY_MASS_INDEX, - BODY_TEMPERATURE, - BODY_WATER_MASS, - DIETARY_CARBS_CONSUMED, - DIETARY_CAFFEINE, - DIETARY_ENERGY_CONSUMED, - DIETARY_FATS_CONSUMED, - DIETARY_PROTEIN_CONSUMED, - DIETARY_FIBER, - DIETARY_SUGAR, - DIETARY_FAT_MONOUNSATURATED, - DIETARY_FAT_POLYUNSATURATED, - DIETARY_FAT_SATURATED, - DIETARY_CHOLESTEROL, - DIETARY_VITAMIN_A, - DIETARY_THIAMIN, - DIETARY_RIBOFLAVIN, - DIETARY_NIACIN, - DIETARY_PANTOTHENIC_ACID, - DIETARY_VITAMIN_B6, - DIETARY_BIOTIN, - DIETARY_VITAMIN_B12, - DIETARY_VITAMIN_C, - DIETARY_VITAMIN_D, - DIETARY_VITAMIN_E, - DIETARY_VITAMIN_K, - DIETARY_FOLATE, - DIETARY_CALCIUM, - DIETARY_CHLORIDE, - DIETARY_IRON, - DIETARY_MAGNESIUM, - DIETARY_PHOSPHORUS, - DIETARY_POTASSIUM, - DIETARY_SODIUM, - DIETARY_ZINC, - DIETARY_CHROMIUM, - DIETARY_COPPER, - DIETARY_IODINE, - DIETARY_MANGANESE, - DIETARY_MOLYBDENUM, - DIETARY_SELENIUM, - FORCED_EXPIRATORY_VOLUME, - HEART_RATE, - HEART_RATE_VARIABILITY_SDNN, - HEART_RATE_VARIABILITY_RMSSD, - HEIGHT, - INSULIN_DELIVERY, - RESTING_HEART_RATE, - RESPIRATORY_RATE, - PERIPHERAL_PERFUSION_INDEX, - STEPS, - WAIST_CIRCUMFERENCE, - WALKING_HEART_RATE, - WEIGHT, - DISTANCE_WALKING_RUNNING, - DISTANCE_SWIMMING, - DISTANCE_CYCLING, - FLIGHTS_CLIMBED, - DISTANCE_DELTA, - WALKING_SPEED, - SPEED, - MINDFULNESS, - WATER, - SLEEP_ASLEEP, - SLEEP_AWAKE_IN_BED, - SLEEP_AWAKE, - SLEEP_DEEP, - SLEEP_IN_BED, - SLEEP_LIGHT, - SLEEP_OUT_OF_BED, - SLEEP_REM, - SLEEP_SESSION, - SLEEP_UNKNOWN, - EXERCISE_TIME, - WORKOUT, - HEADACHE_NOT_PRESENT, - HEADACHE_MILD, - HEADACHE_MODERATE, - HEADACHE_SEVERE, - HEADACHE_UNSPECIFIED, - NUTRITION, - UV_INDEX, - // HealthKit Characteristics - GENDER, - BIRTH_DATE, - BLOOD_TYPE, - MENSTRUATION_FLOW, - WATER_TEMPERATURE, - UNDERWATER_DEPTH, - - // Heart Rate events (specific to Apple Watch) - HIGH_HEART_RATE_EVENT, - LOW_HEART_RATE_EVENT, - IRREGULAR_HEART_RATE_EVENT, - ELECTRODERMAL_ACTIVITY, - ELECTROCARDIOGRAM, - - // Health Connect - TOTAL_CALORIES_BURNED, -} - -/// Access types for Health Data. -enum HealthDataAccess { - READ, - WRITE, - READ_WRITE, -} - -/// List of data types available on iOS. -const List dataTypeKeysIOS = [ - HealthDataType.ACTIVE_ENERGY_BURNED, - HealthDataType.ATRIAL_FIBRILLATION_BURDEN, - HealthDataType.APPLE_STAND_HOUR, - HealthDataType.APPLE_MOVE_TIME, - HealthDataType.APPLE_STAND_TIME, - HealthDataType.AUDIOGRAM, - HealthDataType.BASAL_ENERGY_BURNED, - HealthDataType.BLOOD_GLUCOSE, - HealthDataType.BLOOD_OXYGEN, - HealthDataType.BLOOD_PRESSURE_DIASTOLIC, - HealthDataType.BLOOD_PRESSURE_SYSTOLIC, - HealthDataType.BODY_FAT_PERCENTAGE, - HealthDataType.LEAN_BODY_MASS, - HealthDataType.BODY_MASS_INDEX, - HealthDataType.BODY_TEMPERATURE, - HealthDataType.DIETARY_CARBS_CONSUMED, - HealthDataType.DIETARY_CAFFEINE, - HealthDataType.DIETARY_ENERGY_CONSUMED, - HealthDataType.DIETARY_FATS_CONSUMED, - HealthDataType.DIETARY_PROTEIN_CONSUMED, - HealthDataType.DIETARY_FIBER, - HealthDataType.DIETARY_SUGAR, - HealthDataType.DIETARY_FAT_MONOUNSATURATED, - HealthDataType.DIETARY_FAT_POLYUNSATURATED, - HealthDataType.DIETARY_FAT_SATURATED, - HealthDataType.DIETARY_CHOLESTEROL, - HealthDataType.DIETARY_VITAMIN_A, - HealthDataType.DIETARY_THIAMIN, - HealthDataType.DIETARY_RIBOFLAVIN, - HealthDataType.DIETARY_NIACIN, - HealthDataType.DIETARY_PANTOTHENIC_ACID, - HealthDataType.DIETARY_VITAMIN_B6, - HealthDataType.DIETARY_BIOTIN, - HealthDataType.DIETARY_VITAMIN_B12, - HealthDataType.DIETARY_VITAMIN_C, - HealthDataType.DIETARY_VITAMIN_D, - HealthDataType.DIETARY_VITAMIN_E, - HealthDataType.DIETARY_VITAMIN_K, - HealthDataType.DIETARY_FOLATE, - HealthDataType.DIETARY_CALCIUM, - HealthDataType.DIETARY_CHLORIDE, - HealthDataType.DIETARY_IRON, - HealthDataType.DIETARY_MAGNESIUM, - HealthDataType.DIETARY_PHOSPHORUS, - HealthDataType.DIETARY_POTASSIUM, - HealthDataType.DIETARY_SODIUM, - HealthDataType.DIETARY_ZINC, - HealthDataType.DIETARY_CHROMIUM, - HealthDataType.DIETARY_COPPER, - HealthDataType.DIETARY_IODINE, - HealthDataType.DIETARY_MANGANESE, - HealthDataType.DIETARY_MOLYBDENUM, - HealthDataType.DIETARY_SELENIUM, - HealthDataType.ELECTRODERMAL_ACTIVITY, - HealthDataType.FORCED_EXPIRATORY_VOLUME, - HealthDataType.HEART_RATE, - HealthDataType.HEART_RATE_VARIABILITY_SDNN, - HealthDataType.HEIGHT, - HealthDataType.INSULIN_DELIVERY, - HealthDataType.HIGH_HEART_RATE_EVENT, - HealthDataType.IRREGULAR_HEART_RATE_EVENT, - HealthDataType.LOW_HEART_RATE_EVENT, - HealthDataType.RESTING_HEART_RATE, - HealthDataType.RESPIRATORY_RATE, - HealthDataType.PERIPHERAL_PERFUSION_INDEX, - HealthDataType.STEPS, - HealthDataType.WAIST_CIRCUMFERENCE, - HealthDataType.WALKING_HEART_RATE, - HealthDataType.WEIGHT, - HealthDataType.FLIGHTS_CLIMBED, - HealthDataType.DISTANCE_WALKING_RUNNING, - HealthDataType.DISTANCE_SWIMMING, - HealthDataType.DISTANCE_CYCLING, - HealthDataType.WALKING_SPEED, - HealthDataType.MINDFULNESS, - HealthDataType.SLEEP_ASLEEP, - HealthDataType.SLEEP_AWAKE, - HealthDataType.SLEEP_DEEP, - HealthDataType.SLEEP_IN_BED, - HealthDataType.SLEEP_LIGHT, - HealthDataType.SLEEP_REM, - HealthDataType.WATER, - HealthDataType.EXERCISE_TIME, - HealthDataType.WORKOUT, - HealthDataType.HEADACHE_NOT_PRESENT, - HealthDataType.HEADACHE_MILD, - HealthDataType.HEADACHE_MODERATE, - HealthDataType.HEADACHE_SEVERE, - HealthDataType.HEADACHE_UNSPECIFIED, - HealthDataType.ELECTROCARDIOGRAM, - HealthDataType.NUTRITION, - HealthDataType.GENDER, - HealthDataType.BIRTH_DATE, - HealthDataType.BLOOD_TYPE, - HealthDataType.MENSTRUATION_FLOW, - HealthDataType.WATER_TEMPERATURE, - HealthDataType.UNDERWATER_DEPTH, - HealthDataType.UV_INDEX, - HealthDataType.TOTAL_CALORIES_BURNED, -]; - -/// List of data types available on Android -const List dataTypeKeysAndroid = [ - HealthDataType.ACTIVE_ENERGY_BURNED, - HealthDataType.BLOOD_GLUCOSE, - HealthDataType.BLOOD_OXYGEN, - HealthDataType.BLOOD_PRESSURE_DIASTOLIC, - HealthDataType.BLOOD_PRESSURE_SYSTOLIC, - HealthDataType.BODY_FAT_PERCENTAGE, - HealthDataType.LEAN_BODY_MASS, - HealthDataType.BODY_MASS_INDEX, - HealthDataType.BODY_TEMPERATURE, - HealthDataType.BODY_WATER_MASS, - HealthDataType.HEART_RATE, - HealthDataType.HEART_RATE_VARIABILITY_RMSSD, - HealthDataType.HEIGHT, - HealthDataType.STEPS, - HealthDataType.WEIGHT, - HealthDataType.DISTANCE_DELTA, - HealthDataType.SPEED, - HealthDataType.SLEEP_ASLEEP, - HealthDataType.SLEEP_AWAKE_IN_BED, - HealthDataType.SLEEP_AWAKE, - HealthDataType.SLEEP_DEEP, - HealthDataType.SLEEP_LIGHT, - HealthDataType.SLEEP_OUT_OF_BED, - HealthDataType.SLEEP_REM, - HealthDataType.SLEEP_SESSION, - HealthDataType.SLEEP_UNKNOWN, - HealthDataType.WATER, - HealthDataType.WORKOUT, - HealthDataType.RESTING_HEART_RATE, - HealthDataType.FLIGHTS_CLIMBED, - HealthDataType.BASAL_ENERGY_BURNED, - HealthDataType.RESPIRATORY_RATE, - HealthDataType.NUTRITION, - HealthDataType.TOTAL_CALORIES_BURNED, - HealthDataType.MENSTRUATION_FLOW, -]; - -/// Maps a [HealthDataType] to a [HealthDataUnit]. -const Map dataTypeToUnit = { - HealthDataType.ACTIVE_ENERGY_BURNED: HealthDataUnit.KILOCALORIE, - HealthDataType.ATRIAL_FIBRILLATION_BURDEN: HealthDataUnit.PERCENT, - HealthDataType.APPLE_STAND_HOUR: HealthDataUnit.HOUR, - HealthDataType.APPLE_MOVE_TIME: HealthDataUnit.SECOND, - HealthDataType.APPLE_STAND_TIME: HealthDataUnit.SECOND, - HealthDataType.AUDIOGRAM: HealthDataUnit.DECIBEL_HEARING_LEVEL, - HealthDataType.BASAL_ENERGY_BURNED: HealthDataUnit.KILOCALORIE, - HealthDataType.BLOOD_GLUCOSE: HealthDataUnit.MILLIGRAM_PER_DECILITER, - HealthDataType.BLOOD_OXYGEN: HealthDataUnit.PERCENT, - HealthDataType.BLOOD_PRESSURE_DIASTOLIC: HealthDataUnit.MILLIMETER_OF_MERCURY, - HealthDataType.BLOOD_PRESSURE_SYSTOLIC: HealthDataUnit.MILLIMETER_OF_MERCURY, - HealthDataType.BODY_FAT_PERCENTAGE: HealthDataUnit.PERCENT, - HealthDataType.LEAN_BODY_MASS: HealthDataUnit.KILOGRAM, - HealthDataType.BODY_MASS_INDEX: HealthDataUnit.NO_UNIT, - HealthDataType.BODY_TEMPERATURE: HealthDataUnit.DEGREE_CELSIUS, - HealthDataType.BODY_WATER_MASS: HealthDataUnit.KILOGRAM, - HealthDataType.DIETARY_CARBS_CONSUMED: HealthDataUnit.GRAM, - HealthDataType.DIETARY_CAFFEINE: HealthDataUnit.GRAM, - HealthDataType.DIETARY_ENERGY_CONSUMED: HealthDataUnit.KILOCALORIE, - HealthDataType.DIETARY_FATS_CONSUMED: HealthDataUnit.GRAM, - HealthDataType.DIETARY_PROTEIN_CONSUMED: HealthDataUnit.GRAM, - HealthDataType.DIETARY_FIBER: HealthDataUnit.GRAM, - HealthDataType.DIETARY_SUGAR: HealthDataUnit.GRAM, - HealthDataType.DIETARY_FAT_MONOUNSATURATED: HealthDataUnit.GRAM, - HealthDataType.DIETARY_FAT_POLYUNSATURATED: HealthDataUnit.GRAM, - HealthDataType.DIETARY_FAT_SATURATED: HealthDataUnit.GRAM, - HealthDataType.DIETARY_CHOLESTEROL: HealthDataUnit.GRAM, - HealthDataType.DIETARY_VITAMIN_A: HealthDataUnit.GRAM, - HealthDataType.DIETARY_THIAMIN: HealthDataUnit.GRAM, - HealthDataType.DIETARY_RIBOFLAVIN: HealthDataUnit.GRAM, - HealthDataType.DIETARY_NIACIN: HealthDataUnit.GRAM, - HealthDataType.DIETARY_PANTOTHENIC_ACID: HealthDataUnit.GRAM, - HealthDataType.DIETARY_VITAMIN_B6: HealthDataUnit.GRAM, - HealthDataType.DIETARY_BIOTIN: HealthDataUnit.GRAM, - HealthDataType.DIETARY_VITAMIN_B12: HealthDataUnit.GRAM, - HealthDataType.DIETARY_VITAMIN_C: HealthDataUnit.GRAM, - HealthDataType.DIETARY_VITAMIN_D: HealthDataUnit.GRAM, - HealthDataType.DIETARY_VITAMIN_E: HealthDataUnit.GRAM, - HealthDataType.DIETARY_VITAMIN_K: HealthDataUnit.GRAM, - HealthDataType.DIETARY_FOLATE: HealthDataUnit.GRAM, - HealthDataType.DIETARY_CALCIUM: HealthDataUnit.GRAM, - HealthDataType.DIETARY_CHLORIDE: HealthDataUnit.GRAM, - HealthDataType.DIETARY_IRON: HealthDataUnit.GRAM, - HealthDataType.DIETARY_MAGNESIUM: HealthDataUnit.GRAM, - HealthDataType.DIETARY_PHOSPHORUS: HealthDataUnit.GRAM, - HealthDataType.DIETARY_POTASSIUM: HealthDataUnit.GRAM, - HealthDataType.DIETARY_SODIUM: HealthDataUnit.GRAM, - HealthDataType.DIETARY_ZINC: HealthDataUnit.GRAM, - HealthDataType.DIETARY_CHROMIUM: HealthDataUnit.GRAM, - HealthDataType.DIETARY_COPPER: HealthDataUnit.GRAM, - HealthDataType.DIETARY_IODINE: HealthDataUnit.GRAM, - HealthDataType.DIETARY_MANGANESE: HealthDataUnit.GRAM, - HealthDataType.DIETARY_MOLYBDENUM: HealthDataUnit.GRAM, - HealthDataType.DIETARY_SELENIUM: HealthDataUnit.GRAM, - - HealthDataType.ELECTRODERMAL_ACTIVITY: HealthDataUnit.SIEMEN, - HealthDataType.FORCED_EXPIRATORY_VOLUME: HealthDataUnit.LITER, - HealthDataType.HEART_RATE: HealthDataUnit.BEATS_PER_MINUTE, - HealthDataType.RESPIRATORY_RATE: HealthDataUnit.RESPIRATIONS_PER_MINUTE, - HealthDataType.PERIPHERAL_PERFUSION_INDEX: HealthDataUnit.PERCENT, - HealthDataType.HEIGHT: HealthDataUnit.METER, - HealthDataType.INSULIN_DELIVERY: HealthDataUnit.INTERNATIONAL_UNIT, - HealthDataType.RESTING_HEART_RATE: HealthDataUnit.BEATS_PER_MINUTE, - HealthDataType.STEPS: HealthDataUnit.COUNT, - HealthDataType.WAIST_CIRCUMFERENCE: HealthDataUnit.METER, - HealthDataType.WALKING_HEART_RATE: HealthDataUnit.BEATS_PER_MINUTE, - HealthDataType.WEIGHT: HealthDataUnit.KILOGRAM, - HealthDataType.DISTANCE_WALKING_RUNNING: HealthDataUnit.METER, - HealthDataType.DISTANCE_SWIMMING: HealthDataUnit.METER, - HealthDataType.DISTANCE_CYCLING: HealthDataUnit.METER, - HealthDataType.FLIGHTS_CLIMBED: HealthDataUnit.COUNT, - HealthDataType.DISTANCE_DELTA: HealthDataUnit.METER, - HealthDataType.WALKING_SPEED: HealthDataUnit.METER_PER_SECOND, - HealthDataType.SPEED: HealthDataUnit.METER_PER_SECOND, - - HealthDataType.WATER: HealthDataUnit.LITER, - HealthDataType.SLEEP_ASLEEP: HealthDataUnit.MINUTE, - HealthDataType.SLEEP_AWAKE: HealthDataUnit.MINUTE, - HealthDataType.SLEEP_AWAKE_IN_BED: HealthDataUnit.MINUTE, - HealthDataType.SLEEP_DEEP: HealthDataUnit.MINUTE, - HealthDataType.SLEEP_IN_BED: HealthDataUnit.MINUTE, - HealthDataType.SLEEP_LIGHT: HealthDataUnit.MINUTE, - HealthDataType.SLEEP_OUT_OF_BED: HealthDataUnit.MINUTE, - HealthDataType.SLEEP_REM: HealthDataUnit.MINUTE, - HealthDataType.SLEEP_SESSION: HealthDataUnit.MINUTE, - HealthDataType.SLEEP_UNKNOWN: HealthDataUnit.MINUTE, - - HealthDataType.MINDFULNESS: HealthDataUnit.MINUTE, - HealthDataType.EXERCISE_TIME: HealthDataUnit.MINUTE, - HealthDataType.WORKOUT: HealthDataUnit.NO_UNIT, - - HealthDataType.HEADACHE_NOT_PRESENT: HealthDataUnit.MINUTE, - HealthDataType.HEADACHE_MILD: HealthDataUnit.MINUTE, - HealthDataType.HEADACHE_MODERATE: HealthDataUnit.MINUTE, - HealthDataType.HEADACHE_SEVERE: HealthDataUnit.MINUTE, - HealthDataType.HEADACHE_UNSPECIFIED: HealthDataUnit.MINUTE, - - HealthDataType.GENDER: HealthDataUnit.NO_UNIT, - HealthDataType.BIRTH_DATE: HealthDataUnit.NO_UNIT, - HealthDataType.BLOOD_TYPE: HealthDataUnit.NO_UNIT, - - // Heart Rate events (specific to Apple Watch) - HealthDataType.HIGH_HEART_RATE_EVENT: HealthDataUnit.NO_UNIT, - HealthDataType.LOW_HEART_RATE_EVENT: HealthDataUnit.NO_UNIT, - HealthDataType.IRREGULAR_HEART_RATE_EVENT: HealthDataUnit.NO_UNIT, - HealthDataType.HEART_RATE_VARIABILITY_SDNN: HealthDataUnit.MILLISECOND, - HealthDataType.HEART_RATE_VARIABILITY_RMSSD: HealthDataUnit.MILLISECOND, - HealthDataType.ELECTROCARDIOGRAM: HealthDataUnit.VOLT, - - HealthDataType.NUTRITION: HealthDataUnit.NO_UNIT, - HealthDataType.MENSTRUATION_FLOW: HealthDataUnit.NO_UNIT, - HealthDataType.WATER_TEMPERATURE: HealthDataUnit.DEGREE_CELSIUS, - HealthDataType.UNDERWATER_DEPTH: HealthDataUnit.METER, - HealthDataType.UV_INDEX: HealthDataUnit.COUNT, - - // Health Connect - HealthDataType.TOTAL_CALORIES_BURNED: HealthDataUnit.KILOCALORIE, -}; - -// const PlatformTypeJsonValue = { -// PlatformType.IOS: 'ios', -// PlatformType.ANDROID: 'android', -// }; - -/// List of all [HealthDataUnit]s. -enum HealthDataUnit { - // Mass units - GRAM, - KILOGRAM, - OUNCE, - POUND, - STONE, - // MOLE_UNIT_WITH_MOLAR_MASS, // requires molar mass input - not supported yet - // MOLE_UNIT_WITH_PREFIX_MOLAR_MASS, // requires molar mass & prefix input - not supported yet - - // Length units - METER, - CENTIMETER, - INCH, - FOOT, - YARD, - MILE, - - // Volume units - LITER, - MILLILITER, - FLUID_OUNCE_US, - FLUID_OUNCE_IMPERIAL, - CUP_US, - CUP_IMPERIAL, - PINT_US, - PINT_IMPERIAL, - - // Pressure units - PASCAL, - MILLIMETER_OF_MERCURY, - INCHES_OF_MERCURY, - CENTIMETER_OF_WATER, - ATMOSPHERE, - DECIBEL_A_WEIGHTED_SOUND_PRESSURE_LEVEL, - - // Time units - SECOND, - MILLISECOND, - MINUTE, - HOUR, - DAY, - - // Energy units - JOULE, - KILOCALORIE, - LARGE_CALORIE, - SMALL_CALORIE, - - // Temperature units - DEGREE_CELSIUS, - DEGREE_FAHRENHEIT, - KELVIN, - - // Hearing units - DECIBEL_HEARING_LEVEL, - - // Frequency units - HERTZ, - - // Electrical conductance units - SIEMEN, - - // Potential units - VOLT, - - // Pharmacology units - INTERNATIONAL_UNIT, - - // Scalar units - COUNT, - PERCENT, - - // Other units - BEATS_PER_MINUTE, - RESPIRATIONS_PER_MINUTE, - MILLIGRAM_PER_DECILITER, - MILLIMOLES_PER_LITER, - METER_PER_SECOND, - UNKNOWN_UNIT, - NO_UNIT, -} - -/// List of [HealthWorkoutActivityType]s. -enum HealthWorkoutActivityType { - // Commented for which platform the type are supported - - // Both - AMERICAN_FOOTBALL, - ARCHERY, - AUSTRALIAN_FOOTBALL, - BADMINTON, - BASEBALL, - BASKETBALL, - BIKING, // This also entails the iOS version where it is called CYCLING - BOXING, - CARDIO_DANCE, - CRICKET, - CROSS_COUNTRY_SKIING, - CURLING, - DOWNHILL_SKIING, - ELLIPTICAL, - FENCING, - GOLF, - GYMNASTICS, - HANDBALL, - HIGH_INTENSITY_INTERVAL_TRAINING, - HIKING, - HOCKEY, - JUMP_ROPE, - KICKBOXING, - MARTIAL_ARTS, - PILATES, - RACQUETBALL, - ROWING, - RUGBY, - RUNNING, - SAILING, - SKATING, - SNOWBOARDING, - SOCCER, - SOFTBALL, - SQUASH, - STAIR_CLIMBING, - SWIMMING, - TABLE_TENNIS, - TENNIS, - VOLLEYBALL, - WALKING, - WATER_POLO, - YOGA, - - // iOS only - BARRE, - BOWLING, - CLIMBING, - COOLDOWN, - CORE_TRAINING, - CROSS_TRAINING, - DISC_SPORTS, - EQUESTRIAN_SPORTS, - FISHING, - FITNESS_GAMING, - FLEXIBILITY, - FUNCTIONAL_STRENGTH_TRAINING, - HAND_CYCLING, - HUNTING, - LACROSSE, - MIND_AND_BODY, - MIXED_CARDIO, - PADDLE_SPORTS, - PICKLEBALL, - PLAY, - PREPARATION_AND_RECOVERY, - SNOW_SPORTS, - SOCIAL_DANCE, - STAIRS, - STEP_TRAINING, - SURFING, - TAI_CHI, - TRACK_AND_FIELD, - TRADITIONAL_STRENGTH_TRAINING, - WATER_FITNESS, - WATER_SPORTS, - WHEELCHAIR_RUN_PACE, - WHEELCHAIR_WALK_PACE, - WRESTLING, - UNDERWATER_DIVING, - - // Android only - BIKING_STATIONARY, - CALISTHENICS, - DANCING, - FRISBEE_DISC, - GUIDED_BREATHING, - ICE_SKATING, - PARAGLIDING, - ROCK_CLIMBING, // on iOS this is the same as CLIMBING - ROWING_MACHINE, - RUNNING_TREADMILL, // on iOS this is the same as RUNNING - SCUBA_DIVING, - SKIING, - SNOWSHOEING, - STAIR_CLIMBING_MACHINE, - STRENGTH_TRAINING, - SWIMMING_OPEN_WATER, - SWIMMING_POOL, - WALKING_TREADMILL, - WEIGHTLIFTING, - WHEELCHAIR, - - // - OTHER, -} - -enum MealType { - BREAKFAST, - LUNCH, - DINNER, - SNACK, - UNKNOWN, -} - -/// Classifications for ECG readings. -enum ElectrocardiogramClassification { - NOT_SET, - SINUS_RHYTHM, - ATRIAL_FIBRILLATION, - INCONCLUSIVE_LOW_HEART_RATE, - INCONCLUSIVE_HIGH_HEART_RATE, - INCONCLUSIVE_POOR_READING, - INCONCLUSIVE_OTHER, - UNRECOGNIZED, -} - -/// Types of insulin delivery reason -enum InsulinDeliveryReason { - NOT_SET, - BASAL, - BOLUS, -} - -/// Extension to assign numbers to [ElectrocardiogramClassification]s -extension ElectrocardiogramClassificationValue - on ElectrocardiogramClassification { - int get value => switch (this) { - ElectrocardiogramClassification.NOT_SET => 0, - ElectrocardiogramClassification.SINUS_RHYTHM => 1, - ElectrocardiogramClassification.ATRIAL_FIBRILLATION => 2, - ElectrocardiogramClassification.INCONCLUSIVE_LOW_HEART_RATE => 3, - ElectrocardiogramClassification.INCONCLUSIVE_HIGH_HEART_RATE => 4, - ElectrocardiogramClassification.INCONCLUSIVE_POOR_READING => 5, - ElectrocardiogramClassification.INCONCLUSIVE_OTHER => 6, - ElectrocardiogramClassification.UNRECOGNIZED => 100, - }; -} diff --git a/packages/health/lib/src/workout_summary.dart b/packages/health/lib/src/workout_summary.dart deleted file mode 100644 index 14682a402..000000000 --- a/packages/health/lib/src/workout_summary.dart +++ /dev/null @@ -1,52 +0,0 @@ -part of '../health.dart'; - -/// A [WorkoutSummary] object store vary metrics of a workout. -/// -/// * [workoutType] - The type of workout. See [HealthWorkoutActivityType] for available types. -/// * [totalDistance] - The total distance that was traveled during a workout. -/// * [totalEnergyBurned] - The amount of energy that was burned during a workout. -/// * [totalSteps] - The number of steps during a workout. -@JsonSerializable(includeIfNull: false, explicitToJson: true) -class WorkoutSummary { - /// Workout type. - String workoutType; - - /// The total distance value of the workout. - num totalDistance; - - /// The total energy burned value of the workout. - num totalEnergyBurned; - - /// The total steps value of the workout. - num totalSteps; - - WorkoutSummary({ - required this.workoutType, - required this.totalDistance, - required this.totalEnergyBurned, - required this.totalSteps, - }); - - /// Create a [WorkoutSummary] based on a health data point from native data format. - factory WorkoutSummary.fromHealthDataPoint(dynamic dataPoint) => - WorkoutSummary( - workoutType: dataPoint['workout_type'] as String? ?? '', - totalDistance: dataPoint['total_distance'] as num? ?? 0, - totalEnergyBurned: dataPoint['total_energy_burned'] as num? ?? 0, - totalSteps: dataPoint['total_steps'] as num? ?? 0, - ); - - /// Create a [HealthDataPoint] from json. - factory WorkoutSummary.fromJson(Map json) => - _$WorkoutSummaryFromJson(json); - - /// Convert this [HealthDataPoint] to json. - Map toJson() => _$WorkoutSummaryToJson(this); - - @override - String toString() => '$runtimeType - ' - 'workoutType: $workoutType' - 'totalDistance: $totalDistance, ' - 'totalEnergyBurned: $totalEnergyBurned, ' - 'totalSteps: $totalSteps'; -} diff --git a/packages/health/pubspec.yaml b/packages/health/pubspec.yaml deleted file mode 100644 index b4f8185d7..000000000 --- a/packages/health/pubspec.yaml +++ /dev/null @@ -1,39 +0,0 @@ -name: health -description: Wrapper for Apple's HealthKit on iOS and Google's Health Connect on Android. -version: 13.2.0 -homepage: https://github.com/cph-cachet/flutter-plugins/tree/master/packages/health - -environment: - sdk: ">=3.8.0 <4.0.0" - flutter: ">=3.6.0" - -dependencies: - flutter: - sdk: flutter - intl: '>=0.18.0 <0.21.0' - device_info_plus: '^12.1.0' - json_annotation: ^4.9.0 - carp_serializable: ^2.0.1 # polymorphic json serialization - -dev_dependencies: - flutter_test: - sdk: flutter - integration_test: - sdk: flutter - flutter_lints: any - - # Using carp_serializable & json_serializable to auto generate json code (.g files) with this command: - # dart run build_runner build --delete-conflicting-outputs - build_runner: any - json_serializable: any - mocktail: ^1.0.4 - mockito: ^5.4.5 - -flutter: - plugin: - platforms: - android: - package: cachet.plugins.health - pluginClass: HealthPlugin - ios: - pluginClass: HealthPlugin diff --git a/packages/health/test/health_test.dart b/packages/health/test/health_test.dart deleted file mode 100644 index dc7c88cbc..000000000 --- a/packages/health/test/health_test.dart +++ /dev/null @@ -1,337 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:health/health.dart'; -import 'package:carp_serializable/carp_serializable.dart'; - -import 'mocks/device_info_mock.dart'; - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - group('HealthDataPoint fromJson Tests', () { - - //Instantiate Health class with the Mock - final health = Health(deviceInfo: MockDeviceInfoPlugin()); - setUpAll(() async { - await health.configure(); - }); - test('Test WorkoutHealthValue', () async { - var entry = { - "uuid": "A91A2F10-3D7B-486A-B140-5ADCD3C9C6D0", - "value": { - "__type": "WorkoutHealthValue", - "workoutActivityType": "AMERICAN_FOOTBALL", - "totalEnergyBurned": 100, - "totalEnergyBurnedUnit": "KILOCALORIE", - "totalDistance": 2000, - "totalDistanceUnit": "METER" - }, - "type": "WORKOUT", - "unit": "NO_UNIT", - "dateFrom": "2024-09-24T17:34:00.000", - "dateTo": "2024-09-24T17:57:00.000", - "sourcePlatform": "appleHealth", - "sourceDeviceId": "756B1A7A-C972-4BDB-9748-0D4749CF299C", - "sourceId": "com.apple.Health", - "sourceName": "Salud", - "recordingMethod": "manual", - "workoutSummary": { - "workoutType": "AMERICAN_FOOTBALL", - "totalDistance": 2000, - "totalEnergyBurned": 100, - "totalSteps": 0 - } - }; - - var hdp = HealthDataPoint.fromJson(entry); - - expect(hdp.uuid, "A91A2F10-3D7B-486A-B140-5ADCD3C9C6D0"); - expect(hdp.type, HealthDataType.WORKOUT); - expect(hdp.unit, HealthDataUnit.NO_UNIT); - expect(hdp.sourcePlatform, HealthPlatformType.appleHealth); - expect(hdp.sourceDeviceId, "756B1A7A-C972-4BDB-9748-0D4749CF299C"); - expect(hdp.sourceId, "com.apple.Health"); - expect(hdp.sourceName, "Salud"); - expect(hdp.recordingMethod, RecordingMethod.manual); - - expect(hdp.value, isA()); - expect((hdp.value as WorkoutHealthValue).workoutActivityType, - HealthWorkoutActivityType.AMERICAN_FOOTBALL); - expect((hdp.value as WorkoutHealthValue).totalEnergyBurned, 100); - expect((hdp.value as WorkoutHealthValue).totalEnergyBurnedUnit, - HealthDataUnit.KILOCALORIE); - expect((hdp.value as WorkoutHealthValue).totalDistance, 2000); - expect((hdp.value as WorkoutHealthValue).totalDistanceUnit, - HealthDataUnit.METER); - - // debugPrint(toJsonString(hdp)); - expect(toJsonString(hdp), isA()); - - - }); - test('Test NumericHealthValue', () { - final json = { - "uuid": "some-uuid-1", - "value": {"__type": "NumericHealthValue", "numericValue": 123.45}, - "type": "HEART_RATE", - "unit": "COUNT", - "dateFrom": "2024-09-24T17:34:00.000", - "dateTo": "2024-09-24T17:57:00.000", - "sourcePlatform": "googleHealthConnect", - "sourceDeviceId": "some-device-id", - "sourceId": "some-source-id", - "sourceName": "some-source-name", - "recordingMethod": "automatic" - }; - - final hdp = HealthDataPoint.fromJson(json); - - expect(hdp.uuid, "some-uuid-1"); - expect(hdp.type, HealthDataType.HEART_RATE); - expect(hdp.unit, HealthDataUnit.COUNT); - expect(hdp.sourcePlatform, HealthPlatformType.googleHealthConnect); - expect(hdp.sourceDeviceId, "some-device-id"); - expect(hdp.sourceId, "some-source-id"); - expect(hdp.sourceName, "some-source-name"); - expect(hdp.recordingMethod, RecordingMethod.automatic); - - expect(hdp.value, isA()); - expect((hdp.value as NumericHealthValue).numericValue, 123.45); - - // debugPrint(toJsonString(hdp)); - expect(toJsonString(hdp), isA()); - }); - test('Test AudiogramHealthValue', () { - final json = { - "uuid": "some-uuid-2", - "value": { - "__type": "AudiogramHealthValue", - "frequencies": [1000.0, 2000.0, 3000.0], - "leftEarSensitivities": [20.0, 25.0, 30.0], - "rightEarSensitivities": [15.0, 20.0, 25.0] - }, - "type": "AUDIOGRAM", - "unit": "DECIBEL_HEARING_LEVEL", - "dateFrom": "2024-09-24T17:34:00.000", - "dateTo": "2024-09-24T17:57:00.000", - "sourcePlatform": "appleHealth", - "sourceDeviceId": "some-device-id", - "sourceId": "some-source-id", - "sourceName": "some-source-name", - "recordingMethod": "manual" - }; - final hdp = HealthDataPoint.fromJson(json); - - expect(hdp.uuid, "some-uuid-2"); - expect(hdp.type, HealthDataType.AUDIOGRAM); - expect(hdp.unit, HealthDataUnit.DECIBEL_HEARING_LEVEL); - expect(hdp.sourcePlatform, HealthPlatformType.appleHealth); - expect(hdp.sourceDeviceId, "some-device-id"); - expect(hdp.sourceId, "some-source-id"); - expect(hdp.sourceName, "some-source-name"); - expect(hdp.recordingMethod, RecordingMethod.manual); - expect(hdp.value, isA()); - - final audiogramValue = hdp.value as AudiogramHealthValue; - expect(audiogramValue.frequencies, [1000.0, 2000.0, 3000.0]); - expect(audiogramValue.leftEarSensitivities, [20.0, 25.0, 30.0]); - expect(audiogramValue.rightEarSensitivities, [15.0, 20.0, 25.0]); - - // debugPrint(toJsonString(hdp)); - expect(toJsonString(hdp), isA()); - }); - test('Test ElectrocardiogramHealthValue', () { - final json = { - "uuid": "some-uuid-3", - "value": { - "__type": "ElectrocardiogramHealthValue", - "voltageValues": [ - { - "__type": "ElectrocardiogramVoltageValue", - "voltage": 0.1, - "timeSinceSampleStart": 0.01 - }, - { - "__type": "ElectrocardiogramVoltageValue", - "voltage": 0.2, - "timeSinceSampleStart": 0.02 - }, - { - "__type": "ElectrocardiogramVoltageValue", - "voltage": 0.3, - "timeSinceSampleStart": 0.03 - } - ], - }, - "type": "ELECTROCARDIOGRAM", - "unit": "VOLT", - "dateFrom": "2024-09-24T17:34:00.000", - "dateTo": "2024-09-24T17:57:00.000", - "sourcePlatform": "appleHealth", - "sourceDeviceId": "some-device-id", - "sourceId": "some-source-id", - "sourceName": "some-source-name", - "recordingMethod": "active" - }; - - final hdp = HealthDataPoint.fromJson(json); - - expect(hdp.uuid, "some-uuid-3"); - expect(hdp.type, HealthDataType.ELECTROCARDIOGRAM); - expect(hdp.unit, HealthDataUnit.VOLT); - expect(hdp.sourcePlatform, HealthPlatformType.appleHealth); - expect(hdp.sourceDeviceId, "some-device-id"); - expect(hdp.sourceId, "some-source-id"); - expect(hdp.sourceName, "some-source-name"); - expect(hdp.recordingMethod, RecordingMethod.active); - expect(hdp.value, isA()); - - final ecgValue = hdp.value as ElectrocardiogramHealthValue; - expect(ecgValue.voltageValues.length, 3); - expect(ecgValue.voltageValues[0], isA()); - expect(ecgValue.voltageValues[0].voltage, 0.1); - expect(ecgValue.voltageValues[0].timeSinceSampleStart, 0.01); - expect(ecgValue.voltageValues[1].voltage, 0.2); - expect(ecgValue.voltageValues[1].timeSinceSampleStart, 0.02); - expect(ecgValue.voltageValues[2].voltage, 0.3); - expect(ecgValue.voltageValues[2].timeSinceSampleStart, 0.03); - // debugPrint(toJsonString(hdp)); - expect(toJsonString(hdp), isA()); - }); - test('Test NutritionHealthValue', () { - final json = { - "uuid": "some-uuid-4", - "value": { - "__type": "NutritionHealthValue", - "calories": 500.0, - "carbs": 60.0, - "protein": 20.0, - "fat": 30.0, - "caffeine": 100.0, - "vitaminA": 20.0, - "b1Thiamine": 20.0, - "b2Riboflavin": 20.0, - "b3Niacin": 20.0, - "b5PantothenicAcid": 20.0, - "b6Pyridoxine": 20.0, - "b7Biotin": 20.0, - "b9Folate": 20.0, - "b12Cobalamin": 20.0, - "vitaminC": 20.0, - "vitaminD": 20.0, - "vitaminE": 20.0, - "vitaminK": 20.0, - "calcium": 20.0, - "cholesterol": 20.0, - "chloride": 20.0, - "chromium": 20.0, - "copper": 20.0, - "fatUnsaturated": 20.0, - "fatMonounsaturated": 20.0, - "fatPolyunsaturated": 20.0, - "fatSaturated": 20.0, - "fatTransMonoenoic": 20.0, - "fiber": 20.0, - "iodine": 20.0, - "iron": 20.0, - "magnesium": 20.0, - "manganese": 20.0, - "molybdenum": 20.0, - "phosphorus": 20.0, - "potassium": 20.0, - "selenium": 20.0, - "sodium": 20.0, - "sugar": 20.0, - "water": 20.0, - "zinc": 20.0 - }, - "type": "NUTRITION", - "unit": "NO_UNIT", - "dateFrom": "2024-09-24T17:34:00.000", - "dateTo": "2024-09-24T17:57:00.000", - "sourcePlatform": "googleHealthConnect", - "sourceDeviceId": "some-device-id", - "sourceId": "some-source-id", - "sourceName": "some-source-name", - "recordingMethod": "manual" - }; - - final hdp = HealthDataPoint.fromJson(json); - expect(hdp.uuid, "some-uuid-4"); - expect(hdp.type, HealthDataType.NUTRITION); - expect(hdp.unit, HealthDataUnit.NO_UNIT); - expect(hdp.sourcePlatform, HealthPlatformType.googleHealthConnect); - expect(hdp.sourceDeviceId, "some-device-id"); - expect(hdp.sourceId, "some-source-id"); - expect(hdp.sourceName, "some-source-name"); - expect(hdp.recordingMethod, RecordingMethod.manual); - expect(hdp.value, isA()); - - final nutritionValue = hdp.value as NutritionHealthValue; - expect(nutritionValue.calories, 500.0); - expect(nutritionValue.carbs, 60.0); - expect(nutritionValue.protein, 20.0); - expect(nutritionValue.fat, 30.0); - expect(nutritionValue.caffeine, 100.0); - expect(nutritionValue.vitaminA, 20.0); - expect(nutritionValue.b1Thiamine, 20.0); - expect(nutritionValue.b2Riboflavin, 20.0); - expect(nutritionValue.b3Niacin, 20.0); - expect(nutritionValue.b5PantothenicAcid, 20.0); - expect(nutritionValue.b6Pyridoxine, 20.0); - expect(nutritionValue.b7Biotin, 20.0); - expect(nutritionValue.b9Folate, 20.0); - expect(nutritionValue.b12Cobalamin, 20.0); - expect(nutritionValue.vitaminC, 20.0); - expect(nutritionValue.vitaminD, 20.0); - expect(nutritionValue.vitaminE, 20.0); - expect(nutritionValue.vitaminK, 20.0); - expect(nutritionValue.calcium, 20.0); - expect(nutritionValue.cholesterol, 20.0); - expect(nutritionValue.chloride, 20.0); - expect(nutritionValue.chromium, 20.0); - expect(nutritionValue.copper, 20.0); - expect(nutritionValue.fatUnsaturated, 20.0); - expect(nutritionValue.fatMonounsaturated, 20.0); - expect(nutritionValue.fatPolyunsaturated, 20.0); - expect(nutritionValue.fatSaturated, 20.0); - expect(nutritionValue.fatTransMonoenoic, 20.0); - expect(nutritionValue.fiber, 20.0); - expect(nutritionValue.iodine, 20.0); - expect(nutritionValue.iron, 20.0); - expect(nutritionValue.magnesium, 20.0); - expect(nutritionValue.manganese, 20.0); - expect(nutritionValue.molybdenum, 20.0); - expect(nutritionValue.phosphorus, 20.0); - expect(nutritionValue.potassium, 20.0); - expect(nutritionValue.selenium, 20.0); - expect(nutritionValue.sodium, 20.0); - expect(nutritionValue.sugar, 20.0); - expect(nutritionValue.water, 20.0); - expect(nutritionValue.zinc, 20.0); - // debugPrint(toJsonString(hdp)); - expect(toJsonString(hdp), isA()); - }); - test('Test HealthValue error handling', () { - final json = { - "uuid": "some-uuid-error", - "value": { - "__type": "UnknownHealthValue", // This should throw an error - "numericValue": 123.45 - }, - "type": "HEART_RATE", - "unit": "COUNT_PER_MINUTE", - "dateFrom": "2024-09-24T17:34:00.000", - "dateTo": "2024-09-24T17:57:00.000", - "sourcePlatform": "googleHealthConnect", - "sourceDeviceId": "some-device-id", - "sourceId": "some-source-id", - "sourceName": "some-source-name", - "recordingMethod": "automatic" - }; - expect( - () => HealthDataPoint.fromJson(json), - throwsA( - isA())); //Expect SerializationException - }); - }); -} diff --git a/packages/health/test/mocks/device_info_mock.dart b/packages/health/test/mocks/device_info_mock.dart deleted file mode 100644 index 019a36012..000000000 --- a/packages/health/test/mocks/device_info_mock.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:mocktail/mocktail.dart'; -import 'package:device_info_plus/device_info_plus.dart'; - -class MockDeviceInfoPlugin extends Mock implements DeviceInfoPlugin { - @override - Future get androidInfo => - Future.value(AndroidDeviceInfo.fromMap({ - 'id': 'mock-android-id', - 'version': { - 'baseOS': 'mock-baseOS', - 'codename': 'mock-codename', - 'incremental': 'mock-incremental', - 'previewSdkInt': 23, - 'release': 'mock-release', - 'sdkInt': 30, - 'securityPatch': 'mock-securityPatch', - }, - 'board': 'mock-board', - 'bootloader': 'mock-bootloader', - 'brand': 'mock-brand', - 'device': 'mock-device', - 'display': 'mock-display', - 'fingerprint': 'mock-fingerprint', - 'hardware': 'mock-hardware', - 'host': 'mock-host', - 'manufacturer': 'mock-manufacturer', - 'model': 'mock-model', - 'product': 'mock-product', - 'supported32BitAbis': [], - 'supported64BitAbis': [], - 'supportedAbis': [], - 'tags': 'mock-tags', - 'type': 'mock-type', - 'isPhysicalDevice': true, - 'systemFeatures': [], - 'serialNumber': 'mock-serial', - 'isLowRamDevice': false, - })); - - - @override - Future get iosInfo => Future.value(IosDeviceInfo.fromMap({ - 'name': 'mock-ios-name', - 'systemName': 'mock-ios-systemName', - 'systemVersion': '16.0', - 'model': 'mock-ios-model', - 'modelName': 'mock-ios-modelName', - 'localizedModel': 'mock-ios-localizedModel', - 'identifierForVendor': 'mock-ios-id', - 'isPhysicalDevice': true, - 'isiOSAppOnMac': false, - 'utsname': { - 'sysname': 'mock-ios-sysname', - 'nodename': 'mock-ios-nodename', - 'release': 'mock-ios-release', - 'version': 'mock-ios-version', - 'machine': 'mock-ios-machine', - }, - })); -} \ No newline at end of file diff --git a/packages/health/test/swift_test.dart b/packages/health/test/swift_test.dart deleted file mode 100644 index 1ab431831..000000000 --- a/packages/health/test/swift_test.dart +++ /dev/null @@ -1,174 +0,0 @@ -import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:health/health.dart'; -import 'package:mockito/mockito.dart'; - -import 'mocks/device_info_mock.dart'; - -// Mock MethodChannel to simulate native responses -class MockMethodChannel extends Mock implements MethodChannel { - @override - Future invokeMethod(String method, [dynamic arguments]) async { - if (method == 'getData') { - final dataTypeKey = (arguments as Map)['dataTypeKey']; - switch (dataTypeKey) { - case 'HEART_RATE': - return Future.value(>[ - { - 'uuid': 'test-uuid-1', - 'value': 75.5, - 'date_from': DateTime(2024, 9, 24, 12, 0).millisecondsSinceEpoch, - 'date_to': DateTime(2024, 9, 24, 12, 0).millisecondsSinceEpoch, - 'source_id': 'com.apple.Health', - 'source_name': 'Health', - 'recording_method': 2, // automatic - 'metadata': { - 'HKDeviceName': 'Apple Watch', - 'HKExternalUUID': '123e4567-e89b-12d3-a456-426614174000', - 'recording_method': 2, - } - } - ] as T); - case 'WORKOUT': - return Future.value(>[ - { - 'uuid': 'test-uuid-2', - 'workoutActivityType': 'RUNNING', - 'totalEnergyBurned': 200.0, - 'totalEnergyBurnedUnit': 'KILOCALORIE', - 'totalDistance': 5000.0, - 'totalDistanceUnit': 'METER', - 'date_from': DateTime(2024, 9, 24, 12, 0).millisecondsSinceEpoch, - 'date_to': DateTime(2024, 9, 24, 13, 0).millisecondsSinceEpoch, - 'source_id': 'com.apple.Health', - 'source_name': 'Health', - 'recording_method': 2, - 'metadata': { - 'HKDeviceName': 'Apple Watch', - 'complex': { - 'key': 'value', - 'number': 42, - }, - } - } - ] as T); - case 'NUTRITION': - return Future.value(>[ - { - 'uuid': 'test-uuid-3', - 'name': 'Lunch', - 'meal_type': 'LUNCH', - 'calories': 500.0, - 'carbs': 60.0, - 'protein': 20.0, - 'date_from': DateTime(2024, 9, 24, 13, 0).millisecondsSinceEpoch, - 'date_to': DateTime(2024, 9, 24, 13, 30).millisecondsSinceEpoch, - 'source_id': 'com.apple.Health', - 'source_name': 'Health', - 'recording_method': 2, - 'metadata': { - 'HKFoodMeal': 'LUNCH', - 'array': [1, 'test', false, 'DateTime.now()'], - } - } - ] as T); - default: - return Future.value(>[] as T); - } - } - return Future.value(null); - } -} - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - // Define the channel - const channel = MethodChannel('flutter_health'); - final mockChannel = MockMethodChannel(); - - setUp(() { - // Use the updated method to set the mock handler - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler(channel, (call) => mockChannel.invokeMethod(call.method, call.arguments)); - }); - - tearDown(() { - // Clear the mock handler - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler(channel, null); - }); - - group('Sanitization via getHealthDataFromTypes', () { - final health = Health(deviceInfo: MockDeviceInfoPlugin()); - - setUpAll(() async { - await health.configure(); - }); - - test('Test sanitization with simple metadata - HEART_RATE', () async { - final dataPoints = await health.getHealthDataFromTypes( - types: [HealthDataType.HEART_RATE], - startTime: DateTime(2024, 9, 24, 0, 0), - endTime: DateTime(2024, 9, 24, 23, 59), - ); - - expect(dataPoints.length, 1); - final hdp = dataPoints.first; - expect(hdp.type, HealthDataType.HEART_RATE); - expect(hdp.metadata, { - 'HKDeviceName': 'Apple Watch', - 'HKExternalUUID': '123e4567-e89b-12d3-a456-426614174000', - 'recording_method': 2, - }); - expect(hdp.value, isA()); - expect((hdp.value as NumericHealthValue).numericValue, 75.5); - }); - - test('Test sanitization with nested metadata - WORKOUT', () async { - final dataPoints = await health.getHealthDataFromTypes( - types: [HealthDataType.WORKOUT], - startTime: DateTime(2024, 9, 24, 0, 0), - endTime: DateTime(2024, 9, 24, 23, 59), - ); - - expect(dataPoints.length, 1); - final hdp = dataPoints.first; - expect(hdp.type, HealthDataType.WORKOUT); - expect(hdp.metadata, { - 'HKDeviceName': 'Apple Watch', - 'complex': { - 'key': 'value', - 'number': 42, - }, - // 'unsupported' should be filtered out - }); - expect(hdp.value, isA()); - final workoutValue = hdp.value as WorkoutHealthValue; - expect(workoutValue.workoutActivityType, HealthWorkoutActivityType.RUNNING); - expect(workoutValue.totalEnergyBurned, 200); - expect(workoutValue.totalDistance, 5000); - }); - - test('Test sanitization with array in metadata - NUTRITION', () async { - final dataPoints = await health.getHealthDataFromTypes( - types: [HealthDataType.NUTRITION], - startTime: DateTime(2024, 9, 24, 0, 0), - endTime: DateTime(2024, 9, 24, 23, 59), - ); - - expect(dataPoints.length, 1); - final hdp = dataPoints.first; - expect(hdp.type, HealthDataType.NUTRITION); - expect(hdp.metadata, { - 'HKFoodMeal': 'LUNCH', - 'array': [1, 'test', false, 'DateTime.now()'], // 'DateTime.now()' should be filtered out - }); - expect(hdp.value, isA()); - final nutritionValue = hdp.value as NutritionHealthValue; - expect(nutritionValue.calories, 500.0); - expect(nutritionValue.carbs, 60.0); - expect(nutritionValue.protein, 20.0); - }); - }); -} \ No newline at end of file