summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNavan Chauhan <navanchauhan@gmail.com>2024-02-10 22:18:43 -0700
committerNavan Chauhan <navanchauhan@gmail.com>2024-02-11 07:52:40 -0700
commitb8befff442ec910b2c6343f02699a66319667afb (patch)
treea24efb2e4c74b3d70d0295ac0bf8db0ef56efc7c
parenta4002ba58668d3dcad35552f7bd0bb39ab4fa6be (diff)
add workout card
-rw-r--r--iCUrHealth/ContentView.swift246
-rw-r--r--iCUrHealth/DataAtAGlance.swift1
-rw-r--r--iCUrHealth/DetailedAnalysisView.swift3
-rw-r--r--iCUrHealth/HealthData.swift8
4 files changed, 221 insertions, 37 deletions
diff --git a/iCUrHealth/ContentView.swift b/iCUrHealth/ContentView.swift
index 812dd56..78cf965 100644
--- a/iCUrHealth/ContentView.swift
+++ b/iCUrHealth/ContentView.swift
@@ -9,6 +9,7 @@ import SwiftUI
import HealthKit
import HealthKitUI
import Charts
+import MapKit
struct chartData: Identifiable {
let tag: String
@@ -19,6 +20,7 @@ struct chartData: Identifiable {
let allTypes: Set = [
HKQuantityType.workoutType(),
+ HKSeriesType.workoutRoute(),
HKQuantityType(.activeEnergyBurned),
HKQuantityType(.distanceCycling),
HKQuantityType(.distanceWalkingRunning),
@@ -28,56 +30,212 @@ let allTypes: Set = [
HKCategoryType(.sleepAnalysis)
]
+import HealthKit
+import CoreLocation
+
+struct WorkoutRoute {
+ var coordinates: [CLLocationCoordinate2D]
+}
+
+class WorkoutViewModel: ObservableObject {
+ @Published var workout: HKWorkout?
+ @Published var workoutRoute: WorkoutRoute? {
+ didSet {
+ workoutRouteCoordinates = workoutRoute?.coordinates ?? []
+ }
+ }
+ @Published var workoutRouteCoordinates: [CLLocationCoordinate2D] = []
+ private var healthStore = HKHealthStore()
+
+ func fetchAndProcessWorkoutRoute() {
+ let workoutPredicate = HKQuery.predicateForWorkouts(with: .downhillSkiing)
+ let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)
+
+ let workoutQuery = HKSampleQuery(sampleType: .workoutType(), predicate: workoutPredicate, limit: 1, sortDescriptors: [sortDescriptor]) { (query, samples, error) in
+ guard let workouts = samples as? [HKWorkout], error == nil else {
+ // Handle the error here.
+ return
+ }
+
+ // Process the fetched workouts here.
+ for workout in workouts {
+ let routePredicate = HKQuery.predicateForObjects(from: workout)
+ let routeQuery = HKAnchoredObjectQuery(type: HKSeriesType.workoutRoute(), predicate: routePredicate, anchor: nil, limit: HKObjectQueryNoLimit) { (query, routeSamples, deletedObjects, anchor, error) in
+ guard let routeSamples = routeSamples as? [HKWorkoutRoute], error == nil else {
+ // Handle the error here
+ return
+ }
+
+ for routeSample in routeSamples {
+ let workoutRouteQuery = HKWorkoutRouteQuery(route: routeSample) { (query, locationsOrNil, done, errorOrNil) in
+ guard let locations = locationsOrNil, errorOrNil == nil else {
+ // Handle error
+ return
+ }
+
+ let allCoordinates = locations.map { $0.coordinate }
+
+ // Once all coordinates are fetched, update the published property
+ DispatchQueue.main.async {
+ self.workoutRoute = WorkoutRoute(coordinates: allCoordinates)
+ self.workout = workout
+ }
+
+ if done {
+ // Finish processing as needed
+ }
+ }
+ self.healthStore.execute(workoutRouteQuery)
+ }
+ }
+ self.healthStore.execute(routeQuery)
+ }
+ }
+
+ self.healthStore.execute(workoutQuery)
+ }
+}
+
+struct IdentifiableCoordinate: Identifiable {
+ let id = UUID()
+ var coordinate: CLLocationCoordinate2D
+}
+
+struct MapView: View {
+ var route: WorkoutRoute
+
+ // Convert coordinates to identifiable coordinates
+ var identifiableCoordinates: [IdentifiableCoordinate] {
+ route.coordinates.map { IdentifiableCoordinate(coordinate: $0) }
+ }
+
+ var body: some View {
+ Map(coordinateRegion: .constant(regionForRoute()),
+ showsUserLocation: false,
+ userTrackingMode: .none,
+ annotationItems: identifiableCoordinates) { item in
+ MapPin(coordinate: item.coordinate, tint: .blue)
+ }
+ .overlay(
+ MapOverlay(coordinates: route.coordinates)
+ .stroke(Color.blue, lineWidth: 3)
+ )
+ .cornerRadius(10) // Optional: Adds rounded corners to the map
+ }
+
+ func regionForRoute() -> MKCoordinateRegion {
+// guard let firstCoordinate = route.coordinates.first else {
+// return MKCoordinateRegion()
+// }
+
+ let count = route.coordinates.count / 2
+ guard let firstCoordinate = route.coordinates.prefix(count).last else {
+ return MKCoordinateRegion()
+ }
+
+ return MKCoordinateRegion(center: firstCoordinate, span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01))
+ }
+}
+
+struct MapOverlay: Shape {
+ var coordinates: [CLLocationCoordinate2D]
+
+ func path(in rect: CGRect) -> Path {
+ var path = Path()
+
+ guard let firstCoordinate = coordinates.first else {
+ return path
+ }
+
+ let mapRect = MKMapRect.world
+ let firstPoint = MKMapPoint(firstCoordinate)
+ let startPoint = CGPoint(x: (firstPoint.x / mapRect.size.width) * rect.size.width, y: (1 - firstPoint.y / mapRect.size.height) * rect.size.height)
+ path.move(to: startPoint)
+
+ for coordinate in coordinates.dropFirst() {
+ let mapPoint = MKMapPoint(coordinate)
+ let point = CGPoint(x: (mapPoint.x / mapRect.size.width) * rect.size.width, y: (1 - mapPoint.y / mapRect.size.height) * rect.size.height)
+ path.addLine(to: point)
+ }
+
+ return path
+ }
+}
+
+
struct ContentView: View {
@State var authenticated = false
@State var trigger = false
+ @StateObject private var viewModel = WorkoutViewModel()
+
let healthStore = HKHealthStore()
@State private var data: [chartData] = []
var body: some View {
- NavigationView {
TabView{
- VStack {
- HStack {
- Text("Steps")
- Chart(data) {
- BarMark(x: .value("Date", $0.dateInterval),
- y: .value("Count", $0.data)
- )
-
- }
- }.frame(maxHeight: 100)
- Button(action: {
- Task {
- try await fetchStepCountData()
- }
- })
- {
- Text("Exp Function")
- }
- Spacer()
- .onAppear() {
- if HKHealthStore.isHealthDataAvailable() {
- trigger.toggle()
+ NavigationView {
+ List {
+ HStack {
+ Text("Steps")
+ Chart(data) {
+ BarMark(x: .value("Date", $0.dateInterval),
+ y: .value("Count", $0.data)
+ )
+
+ }
+ }.frame(maxHeight: 100)
+ .navigationTitle("iCUrHealth")
+ Button(action: {
+ Task {
+ try await fetchStepCountData()
}
+ })
+ {
+ Text("Exp Function")
}
- .healthDataAccessRequest(store: healthStore,
- readTypes: allTypes,
- trigger: trigger) { result in
- switch result {
-
- case .success(_):
- authenticated = true
- Task {
- try await fetchStepCountData()
+ if !viewModel.workoutRouteCoordinates.isEmpty {
+ VStack {
+ HStack {
+ Text("Latest Downhill Skiing Workout").font(.callout)
+ Spacer()
+ }
+ MapView(route: viewModel.workoutRoute!)
+ .frame(height: 300)
+ HStack {
+ VStack {
+ Text("Top Speed")
+ Text("\(viewModel.workout!.totalDistance!)")
+ }
}
- case .failure(let error):
- // Handle the error here.
- fatalError("*** An error occurred while requesting authentication: \(error) ***")
}
+ } else {
+ Text("Fetching workout route...")
+ .onAppear {
+ if HKHealthStore.isHealthDataAvailable() {
+ trigger.toggle()
+ }
+ viewModel.fetchAndProcessWorkoutRoute()
+ }
+ .healthDataAccessRequest(store: healthStore,
+ readTypes: allTypes,
+ trigger: trigger) { result in
+ switch result {
+
+ case .success(_):
+ authenticated = true
+ Task {
+ try await fetchStepCountData()
+ }
+ case .failure(let error):
+ // Handle the error here.
+ fatalError("*** An error occurred while requesting authentication: \(error) ***")
+ }
+ }
}
+
+ }.listRowSpacing(10)
}
.padding()
.navigationTitle("iCUrHealth")
@@ -97,6 +255,24 @@ struct ContentView: View {
Text("Trends")
}
}
+
+ }
+
+ private func experimentWithSkiWorkout() async throws {
+ let workoutPredicate = HKQuery.predicateForWorkouts(with: .downhillSkiing)
+ let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)
+
+ let workoutQuery = HKSampleQuery(sampleType: .workoutType(), predicate: workoutPredicate, limit: 3, sortDescriptors: [sortDescriptor]) { (query, samples, error) in
+
+ guard let workouts = samples as? [HKWorkout], error == nil else {
+ // Handle the error here.
+ return
+ }
+
+ // Process the fetched workouts here.
+ for workout in workouts {
+ print(workout)
+ }
}
}
diff --git a/iCUrHealth/DataAtAGlance.swift b/iCUrHealth/DataAtAGlance.swift
index aef3315..b610bf9 100644
--- a/iCUrHealth/DataAtAGlance.swift
+++ b/iCUrHealth/DataAtAGlance.swift
@@ -156,6 +156,7 @@ struct DataAtAGlance: View {
} else {
initial_count += 1
}
+ print(myValue.date)
}
init_avg = initial_total / initial_count
diff --git a/iCUrHealth/DetailedAnalysisView.swift b/iCUrHealth/DetailedAnalysisView.swift
index 1ca9e6f..e13969c 100644
--- a/iCUrHealth/DetailedAnalysisView.swift
+++ b/iCUrHealth/DetailedAnalysisView.swift
@@ -7,12 +7,15 @@
import SwiftUI
import Charts
+import Foundation
struct DetailedAnalysisView: View {
@State var healthData: [HealthData]
@State var llmInput: String = ""
let prediction: Analysis
+ let dateFormatter = DateFormatter()
+
var body: some View {
NavigationView {
diff --git a/iCUrHealth/HealthData.swift b/iCUrHealth/HealthData.swift
index 38f95ed..1eebec2 100644
--- a/iCUrHealth/HealthData.swift
+++ b/iCUrHealth/HealthData.swift
@@ -26,7 +26,7 @@ extension Date {
struct HealthData: Codable, Identifiable {
var id = UUID()
- var date: String
+ var date: Date
var steps: Double?
var activeEnergy: Double?
var exerciseMinutes: Double?
@@ -195,12 +195,16 @@ extension HealthDataFetcher {
let calendar = Calendar.current
let today = Date()
var healthData: [HealthData] = []
+
+ let dateFormatter = DateFormatter()
+ dateFormatter.dateFormat = "M/d/yy"
+
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)
+ date: dateFormatter.date(from: DateFormatter.localizedString(from: endDate, dateStyle: .short, timeStyle: .none))!
)
)
}