From 4f7bfb44637b5d7b0a100b51c40bc64eeb42507e Mon Sep 17 00:00:00 2001 From: Navan Chauhan Date: Sat, 10 Feb 2024 06:07:53 -0700 Subject: add trend page --- iCUrHealth.xcodeproj/project.pbxproj | 59 ++---- .../xcshareddata/swiftpm/Package.resolved | 14 -- iCUrHealth/ContentView.swift | 5 + iCUrHealth/DataAtAGlance.swift | 185 ++++++++++++++++ iCUrHealth/DetailedAnalysisView.swift | 58 ++++++ iCUrHealth/HealthData.swift | 232 +++++++++++++++++++++ 6 files changed, 500 insertions(+), 53 deletions(-) delete mode 100644 iCUrHealth.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved create mode 100644 iCUrHealth/DataAtAGlance.swift create mode 100644 iCUrHealth/DetailedAnalysisView.swift create mode 100644 iCUrHealth/HealthData.swift diff --git a/iCUrHealth.xcodeproj/project.pbxproj b/iCUrHealth.xcodeproj/project.pbxproj index eab4ec3..a9a3455 100644 --- a/iCUrHealth.xcodeproj/project.pbxproj +++ b/iCUrHealth.xcodeproj/project.pbxproj @@ -11,9 +11,11 @@ 3E54FF0E2B772F1C00260FCB /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E54FF0D2B772F1C00260FCB /* ContentView.swift */; }; 3E54FF102B772F1E00260FCB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3E54FF0F2B772F1E00260FCB /* Assets.xcassets */; }; 3E54FF142B772F1E00260FCB /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3E54FF132B772F1E00260FCB /* Preview Assets.xcassets */; }; - 3E54FF1C2B7765C200260FCB /* CareKit in Frameworks */ = {isa = PBXBuildFile; productRef = 3E54FF1B2B7765C200260FCB /* CareKit */; }; - 3E54FF1E2B7765C200260FCB /* CareKitStore in Frameworks */ = {isa = PBXBuildFile; productRef = 3E54FF1D2B7765C200260FCB /* CareKitStore */; }; - 3E54FF202B7765C200260FCB /* CareKitUI in Frameworks */ = {isa = PBXBuildFile; productRef = 3E54FF1F2B7765C200260FCB /* CareKitUI */; }; + 3E54FF242B778ABC00260FCB /* UserCharts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E54FF232B778ABC00260FCB /* UserCharts.swift */; }; + 3E54FF262B778AC200260FCB /* HealthChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E54FF252B778AC200260FCB /* HealthChart.swift */; }; + 3E54FF282B778BBB00260FCB /* HealthData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E54FF272B778BBB00260FCB /* HealthData.swift */; }; + 3E54FF2A2B778D9600260FCB /* DataAtAGlance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E54FF292B778D9600260FCB /* DataAtAGlance.swift */; }; + 3EAA76192B77A10100DEE6D2 /* DetailedAnalysisView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EAA76182B77A10100DEE6D2 /* DetailedAnalysisView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -23,6 +25,11 @@ 3E54FF0F2B772F1E00260FCB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 3E54FF112B772F1E00260FCB /* iCUrHealth.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = iCUrHealth.entitlements; sourceTree = ""; }; 3E54FF132B772F1E00260FCB /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 3E54FF232B778ABC00260FCB /* UserCharts.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserCharts.swift; sourceTree = ""; }; + 3E54FF252B778AC200260FCB /* HealthChart.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HealthChart.swift; sourceTree = ""; }; + 3E54FF272B778BBB00260FCB /* HealthData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthData.swift; sourceTree = ""; }; + 3E54FF292B778D9600260FCB /* DataAtAGlance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataAtAGlance.swift; sourceTree = ""; }; + 3EAA76182B77A10100DEE6D2 /* DetailedAnalysisView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailedAnalysisView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -30,9 +37,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 3E54FF1E2B7765C200260FCB /* CareKitStore in Frameworks */, - 3E54FF1C2B7765C200260FCB /* CareKit in Frameworks */, - 3E54FF202B7765C200260FCB /* CareKitUI in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -58,11 +62,16 @@ 3E54FF0A2B772F1C00260FCB /* iCUrHealth */ = { isa = PBXGroup; children = ( + 3EAA76182B77A10100DEE6D2 /* DetailedAnalysisView.swift */, + 3E54FF252B778AC200260FCB /* HealthChart.swift */, + 3E54FF232B778ABC00260FCB /* UserCharts.swift */, 3E54FF0B2B772F1C00260FCB /* iCUrHealthApp.swift */, 3E54FF0D2B772F1C00260FCB /* ContentView.swift */, 3E54FF0F2B772F1E00260FCB /* Assets.xcassets */, 3E54FF112B772F1E00260FCB /* iCUrHealth.entitlements */, 3E54FF122B772F1E00260FCB /* Preview Content */, + 3E54FF272B778BBB00260FCB /* HealthData.swift */, + 3E54FF292B778D9600260FCB /* DataAtAGlance.swift */, ); path = iCUrHealth; sourceTree = ""; @@ -92,9 +101,6 @@ ); name = iCUrHealth; packageProductDependencies = ( - 3E54FF1B2B7765C200260FCB /* CareKit */, - 3E54FF1D2B7765C200260FCB /* CareKitStore */, - 3E54FF1F2B7765C200260FCB /* CareKitUI */, ); productName = iCUrHealth; productReference = 3E54FF082B772F1C00260FCB /* iCUrHealth.app */; @@ -125,7 +131,6 @@ ); mainGroup = 3E54FEFF2B772F1C00260FCB; packageReferences = ( - 3E54FF1A2B7765C200260FCB /* XCRemoteSwiftPackageReference "CareKit" */, ); productRefGroup = 3E54FF092B772F1C00260FCB /* Products */; projectDirPath = ""; @@ -153,8 +158,13 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 3E54FF262B778AC200260FCB /* HealthChart.swift in Sources */, + 3E54FF282B778BBB00260FCB /* HealthData.swift in Sources */, + 3E54FF2A2B778D9600260FCB /* DataAtAGlance.swift in Sources */, 3E54FF0E2B772F1C00260FCB /* ContentView.swift in Sources */, 3E54FF0C2B772F1C00260FCB /* iCUrHealthApp.swift in Sources */, + 3E54FF242B778ABC00260FCB /* UserCharts.swift in Sources */, + 3EAA76192B77A10100DEE6D2 /* DetailedAnalysisView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -379,35 +389,6 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ - -/* Begin XCRemoteSwiftPackageReference section */ - 3E54FF1A2B7765C200260FCB /* XCRemoteSwiftPackageReference "CareKit" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/carekit-apple/CareKit"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 2.0.2; - }; - }; -/* End XCRemoteSwiftPackageReference section */ - -/* Begin XCSwiftPackageProductDependency section */ - 3E54FF1B2B7765C200260FCB /* CareKit */ = { - isa = XCSwiftPackageProductDependency; - package = 3E54FF1A2B7765C200260FCB /* XCRemoteSwiftPackageReference "CareKit" */; - productName = CareKit; - }; - 3E54FF1D2B7765C200260FCB /* CareKitStore */ = { - isa = XCSwiftPackageProductDependency; - package = 3E54FF1A2B7765C200260FCB /* XCRemoteSwiftPackageReference "CareKit" */; - productName = CareKitStore; - }; - 3E54FF1F2B7765C200260FCB /* CareKitUI */ = { - isa = XCSwiftPackageProductDependency; - package = 3E54FF1A2B7765C200260FCB /* XCRemoteSwiftPackageReference "CareKit" */; - productName = CareKitUI; - }; -/* End XCSwiftPackageProductDependency section */ }; rootObject = 3E54FF002B772F1C00260FCB /* Project object */; } diff --git a/iCUrHealth.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/iCUrHealth.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index 973296d..0000000 --- a/iCUrHealth.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -1,14 +0,0 @@ -{ - "pins" : [ - { - "identity" : "carekit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/carekit-apple/CareKit", - "state" : { - "revision" : "9d4233253faf39e5dcd52e6cb4a0498ac4ca4a1f", - "version" : "2.0.2" - } - } - ], - "version" : 2 -} diff --git a/iCUrHealth/ContentView.swift b/iCUrHealth/ContentView.swift index 2b532ce..2f6933a 100644 --- a/iCUrHealth/ContentView.swift +++ b/iCUrHealth/ContentView.swift @@ -93,6 +93,11 @@ struct ContentView: View { Image(systemName: "chart.xyaxis.line") Text("Charts") } + DataAtAGlance() + .tabItem { + Image(systemName: "chart.xyaxis.line") + Text("Trends") + } } } } diff --git a/iCUrHealth/DataAtAGlance.swift b/iCUrHealth/DataAtAGlance.swift new file mode 100644 index 0000000..f09ae3c --- /dev/null +++ b/iCUrHealth/DataAtAGlance.swift @@ -0,0 +1,185 @@ +// +// DataAtAGlance.swift +// iCUrHealth +// +// Created by Navan Chauhan on 2/10/24. +// + +import SwiftUI +import Charts + +struct Analysis: Hashable { + var image: String? + var prediction: String + var category: String + var rank: Int = 0 // Neutral = 0, Good = 1, Bad = -1 +} + +struct DataAtAGlance: View { + @State var healthData: [HealthData] = [] + @State var predictions: [Analysis] = [] + + var body: some View { + NavigationView { + VStack { + + List(predictions, id: \.self) { pred in + NavigationLink { + DetailedAnalysisView(healthData: self.healthData, prediction: pred) + } + label: { + VStack { + HStack { + if let img = pred.image { + Image(systemName: img) + } + Text(pred.category).font(.callout) + Spacer() + }.padding(.bottom, 1) + Text(pred.prediction) + } + } + }.listRowSpacing(10) + + Text("Data for last \(healthData.count) days") + .onAppear { + let healthDataFetcher = HealthDataFetcher() + self.predictions = [] + Task { + self.healthData = try await healthDataFetcher.fetchAndProcessHealthData() + + let splitNum: Int = self.healthData.count / 2 + print(splitNum) + + let initial = self.healthData.prefix(splitNum) + let recent = self.healthData.suffix(splitNum) + + var initial_total = 0 + var initial_count = 0 + + var final_total = 0 + var final_count = 0 + + for myValue in Array(initial) { + if myValue.steps != nil { + initial_total += Int(myValue.steps!) + initial_count += 1 + } + } + + for myValue in Array(recent) { + if myValue.steps != nil { + final_total += Int(myValue.steps!) + final_count += 1 + } + } + + var init_avg = initial_total / initial_count + var rece_avg = final_total / final_count + + var percentage = rece_avg*100/init_avg + + var pred = Analysis(image: "figure.walk.motion", prediction: "", category: "Steps") + + if abs(percentage-100) > 5 { + if (percentage-100) > 0 { + pred.prediction = "Your steps average in the last 7 days has been higher compared to the week before by (\(percentage-100))%" + } else { + pred.prediction = "Your steps average in the last 7 days has been lower compared to the week before" + } + } else { + pred.prediction = "Your steps average in the last 7 days is relatively simimlar compared to the week before." + } + + self.predictions.append(pred) + + // Sleep + + initial_total = 0 + initial_count = 0 + + final_total = 0 + final_count = 0 + + for myValue in Array(initial) { + if myValue.sleepHours != nil { + initial_total += Int(myValue.sleepHours!) + initial_count += 1 + } + } + + for myValue in Array(recent) { + if myValue.sleepHours != nil { + final_total += Int(myValue.sleepHours!) + final_count += 1 + } + } + + init_avg = initial_total / initial_count + rece_avg = final_total / final_count + + percentage = rece_avg*100/init_avg + + + pred = Analysis(image: "bed.double", prediction: "", category: "Sleep") + if abs(percentage-100) > 5 { + if (percentage-100) > 0 { + pred.prediction = "Your sleep average in the last 7 days has been higher compared to the week before by (\(percentage-100))%" + } else { + pred.prediction = "You have been sleeping \(init_avg - rece_avg) hours fewer compared to last week" + pred.rank = -1 + } + } else { + pred.prediction = "Your sleep average in the last 7 days is relatively simimlar compared to the week before." + } + + self.predictions.append(pred) + + // Exercise Minutes + + initial_total = 0 + initial_count = 0 + + for myValue in Array(initial) { + if myValue.exerciseMinutes != nil { + initial_total += Int(myValue.exerciseMinutes!) + initial_count += 1 + } else { + initial_count += 1 + } + } + + for myValue in Array(recent) { + if myValue.exerciseMinutes != nil { + initial_total += Int(myValue.sleepHours!) + initial_count += 1 + } else { + initial_count += 1 + } + } + + init_avg = initial_total / initial_count + + + pred = Analysis(image: "figure.play", prediction: "You have spent an average of \(init_avg) minutes exercising every day in the past two weeks", category: "Exercise Minutes") + + if init_avg < 20 { + pred.rank = -1 + } else if init_avg > 60 { + pred.rank = 1 + } + + self.predictions.append(pred) + + // END + } + } + } + .navigationTitle("Trend Analysis") + } + } +} + +#Preview { + DataAtAGlance() +} diff --git a/iCUrHealth/DetailedAnalysisView.swift b/iCUrHealth/DetailedAnalysisView.swift new file mode 100644 index 0000000..1ca9e6f --- /dev/null +++ b/iCUrHealth/DetailedAnalysisView.swift @@ -0,0 +1,58 @@ +// +// DetailedAnalysisView.swift +// iCUrHealth +// +// Created by Navan Chauhan on 2/10/24. +// + +import SwiftUI +import Charts + +struct DetailedAnalysisView: View { + @State var healthData: [HealthData] + @State var llmInput: String = "" + let prediction: Analysis + + + var body: some View { + NavigationView { + List { + Text(prediction.prediction) + + Chart { + ForEach(self.healthData) { data in + if prediction.category == "Steps" { + if let stepCount = data.steps { + BarMark(x: .value("Date", data.date), y: .value("Steps", stepCount)) + } + } else if prediction.category == "Sleep" { + if let sleepHours = data.sleepHours { + BarMark(x: .value("Date", data.date), y: .value("Sleep Hours", sleepHours)) + } + } else if prediction.category == "Exercise Minutes" { + if let exerMinutes = data.exerciseMinutes { + BarMark(x: .value("Date", data.date), y: .value("Exercise Minutes", exerMinutes)) + } + } + } + }.frame(height: 250) + + if prediction.category == "Sleep" { + Text("Sleep is crucial for various aspects of health and well-being. It allows the body and mind to recharge, enabling better cognitive function, such as improved concentration and memory retention. Adequate sleep also plays a vital role in physical health, as it is involved in the repair of the heart and blood vessels, and it supports growth and stress regulation. Furthermore, it helps regulate mood and is associated with lower risk of chronic health issues, contributing to overall quality of life.") + } else if prediction.category == "Steps" { + Text("Taking regular steps, such as walking, is fundamental for maintaining physical health. It enhances cardiovascular fitness, aiding in the reduction of heart disease risk, and supports the management of body weight by burning calories. Engaging in regular walking can also strengthen bones and muscles, reducing the risk of osteoporosis and muscle loss. Additionally, it can improve mental health by reducing stress, anxiety, and depressive symptoms, contributing to an overall sense of well-being.") + } else if prediction.category == "Exercise Minutes" { + Text("Regular exercise, even in short durations, is highly beneficial for health. Just a few minutes of physical activity each day can boost cardiovascular health, improving heart function and reducing the risk of heart disease. These exercise minutes can also aid in weight management by increasing metabolic rate and burning extra calories. Furthermore, engaging in daily physical activity, even briefly, can enhance mental health by releasing endorphins that reduce stress and improve mood.") + } + + if prediction.rank == -1 { + if prediction.category == "Sleep" { + Text("It looks like you have not been sleeping well this week. Has something changed?") + TextField("Response", text: $llmInput) + } + } + + }.listRowSpacing(10) + }.navigationTitle(prediction.category) + } +} diff --git a/iCUrHealth/HealthData.swift b/iCUrHealth/HealthData.swift new file mode 100644 index 0000000..38f95ed --- /dev/null +++ b/iCUrHealth/HealthData.swift @@ -0,0 +1,232 @@ +// +// HealthData.swift +// iCUrHealth +// +// Created by Navan Chauhan on 2/10/24. +// + +// Some code is part of the Stanford HealthGPT project +// SPDX-FileCopyrightText: 2023 Stanford University & Project Contributors +// SPDX-License-Identifier: MIT + +import Foundation +import HealthKit + +extension Date { + /// - Returns: A `Date` object representing the start of the current day. + static func startOfDay() -> Date { + Calendar.current.startOfDay(for: Date()) + } + + /// - Returns: A `Date` object representing the start of the day exactly two weeks ago. + func twoWeeksAgoStartOfDay() -> Date { + Calendar.current.date(byAdding: DateComponents(day: -14), to: Date.startOfDay()) ?? Date() + } +} + +struct HealthData: Codable, Identifiable { + var id = UUID() + var date: String + var steps: Double? + var activeEnergy: Double? + var exerciseMinutes: Double? + var bodyWeight: Double? + var sleepHours: Double? + var heartRate: Double? +} + +enum HealthDataFetcherError: Error { + case healthDataNotAvailable + case invalidObjectType + case resultsNotFound + case authorizationFailed +} + +class HealthDataFetcher { + private let healthStore = HKHealthStore() + func requestAuthorization() async throws { + guard HKHealthStore.isHealthDataAvailable() else { + throw HKError(.errorHealthDataUnavailable) + } + + let types: Set = [ + HKQuantityType(.stepCount), + HKQuantityType(.appleExerciseTime), + HKQuantityType(.bodyMass), + HKQuantityType(.heartRate), + HKCategoryType(.sleepAnalysis) + ] + + try await healthStore.requestAuthorization(toShare: Set(), read: types) + } + + func fetchLastTwoWeeksQuantityData( + for identifier: HKQuantityTypeIdentifier, + unit: HKUnit, + options: HKStatisticsOptions + ) async throws -> [Double] { + guard let quantityType = HKObjectType.quantityType(forIdentifier: identifier) else { + throw HealthDataFetcherError.invalidObjectType + } + + let predicate = createLastTwoWeeksPredicate() + + let quantityLastTwoWeeks = HKSamplePredicate.quantitySample( + type: quantityType, + predicate: predicate + ) + + let query = HKStatisticsCollectionQueryDescriptor( + predicate: quantityLastTwoWeeks, + options: options, + anchorDate: Date.startOfDay(), + intervalComponents: DateComponents(day: 1) + ) + + let quantityCounts = try await query.result(for: healthStore) + + var dailyData = [Double]() + + quantityCounts.enumerateStatistics( + from: Date().twoWeeksAgoStartOfDay(), + to: Date.startOfDay() + ) { statistics, _ in + if let quantity = statistics.sumQuantity() { + dailyData.append(quantity.doubleValue(for: unit)) + } else { + dailyData.append(0) + } + } + + return dailyData + } + + func fetchLastTwoWeeksStepCount() async throws -> [Double] { + try await fetchLastTwoWeeksQuantityData( + for: .stepCount, + unit: HKUnit.count(), + options: [.cumulativeSum] + ) + } + + func fetchLastTwoWeeksActiveEnergy() async throws -> [Double] { + try await fetchLastTwoWeeksQuantityData( + for: .activeEnergyBurned, + unit: HKUnit.largeCalorie(), + options: [.cumulativeSum] + ) + } + + func fetchLastTwoWeeksExerciseTime() async throws -> [Double] { + try await fetchLastTwoWeeksQuantityData( + for: .appleExerciseTime, + unit: .minute(), + options: [.cumulativeSum] + ) + } + + func fetchLastTwoWeeksBodyWeight() async throws -> [Double] { + try await fetchLastTwoWeeksQuantityData( + for: .bodyMass, + unit: .pound(), + options: [.discreteAverage] + ) + } + + func fetchLastTwoWeeksHeartRate() async throws -> [Double] { + try await fetchLastTwoWeeksQuantityData( + for: .heartRate, + unit: .count(), + options: [.discreteAverage] + ) + } + + func fetchLastTwoWeeksSleep() async throws -> [Double] { + var dailySleepData: [Double] = [] + + // We go through all possible days in the last two weeks. + for day in -14..<0 { + // We start the calculation at 3 PM the previous day to 3 PM on the day in question. + guard let startOfSleepDay = Calendar.current.date(byAdding: DateComponents(day: day - 1), to: Date.startOfDay()), + let startOfSleep = Calendar.current.date(bySettingHour: 15, minute: 0, second: 0, of: startOfSleepDay), + let endOfSleepDay = Calendar.current.date(byAdding: DateComponents(day: day), to: Date.startOfDay()), + let endOfSleep = Calendar.current.date(bySettingHour: 15, minute: 0, second: 0, of: endOfSleepDay) else { + dailySleepData.append(0) + continue + } + + + let sleepType = HKCategoryType(.sleepAnalysis) + + let dateRangePredicate = HKQuery.predicateForSamples(withStart: startOfSleep, end: endOfSleep, options: .strictEndDate) + let allAsleepValuesPredicate = HKCategoryValueSleepAnalysis.predicateForSamples(equalTo: HKCategoryValueSleepAnalysis.allAsleepValues) + let compoundPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [dateRangePredicate, allAsleepValuesPredicate]) + + let descriptor = HKSampleQueryDescriptor( + predicates: [.categorySample(type: sleepType, predicate: compoundPredicate)], + sortDescriptors: [] + ) + + let results = try await descriptor.result(for: healthStore) + + var secondsAsleep = 0.0 + for result in results { + secondsAsleep += result.endDate.timeIntervalSince(result.startDate) + } + + // Append the hours of sleep for that date + dailySleepData.append(secondsAsleep / (60 * 60)) + } + + return dailySleepData + } + + private func createLastTwoWeeksPredicate() -> NSPredicate { + let now = Date() + let startDate = Calendar.current.date(byAdding: DateComponents(day: -14), to: now) ?? Date() + return HKQuery.predicateForSamples(withStart: startDate, end: now, options: .strictStartDate) + } +} + +extension HealthDataFetcher { + func fetchAndProcessHealthData() async throws -> [HealthData] { + try await requestAuthorization() + + let calendar = Calendar.current + let today = Date() + var healthData: [HealthData] = [] + + for day in 1...14 { + guard let endDate = calendar.date(byAdding: .day, value: -day, to: today) else { continue } + healthData.append( + HealthData( + date: DateFormatter.localizedString(from: endDate, dateStyle: .short, timeStyle: .none) + ) + ) + } + + healthData = healthData.reversed() + + async let stepCounts = fetchLastTwoWeeksStepCount() + async let sleepHours = fetchLastTwoWeeksSleep() + async let caloriesBurned = fetchLastTwoWeeksActiveEnergy() + async let exerciseTime = fetchLastTwoWeeksExerciseTime() + async let bodyMass = fetchLastTwoWeeksBodyWeight() + + let fetchedStepCounts = try? await stepCounts + let fetchedSleepHours = try? await sleepHours + let fetchedCaloriesBurned = try? await caloriesBurned + let fetchedExerciseTime = try? await exerciseTime + let fetchedBodyMass = try? await bodyMass + + for day in 0...13 { + healthData[day].steps = fetchedStepCounts?[day] + healthData[day].sleepHours = fetchedSleepHours?[day] + healthData[day].activeEnergy = fetchedCaloriesBurned?[day] + healthData[day].exerciseMinutes = fetchedExerciseTime?[day] + healthData[day].bodyWeight = fetchedBodyMass?[day] + } + + return healthData + } +} -- cgit v1.2.3