// // CompletedWorkoutView.swift // Werkout_ios // // Created by Trey Tartt on 6/22/23. // import SwiftUI import HealthKit import SharedCore struct CompletedWorkoutView: View { @ObservedObject var bridgeModule = BridgeModule.shared @State private var healthKitWorkoutData: HealthKitWorkoutData? @State private var difficulty: Float = 0 @State private var notes: String = "" @State private var isUploading: Bool = false @State private var gettingHealthKitData: Bool = false @State private var hasError = false @State private var errorMessage = "" private let runtimeReporter = RuntimeReporter.shared var postData: [String: Any] let healthKitHelper = HealthKitHelper() let workout: Workout let completedWorkoutDismissed: ((Bool) -> Void)? @Environment(\.dismiss) var dismiss var body: some View { ZStack { WerkoutTheme.background .ignoresSafeArea() if isUploading { ProgressView("Uploading") .foregroundStyle(WerkoutTheme.textPrimary) .progressViewStyle(CircularProgressViewStyle(tint: WerkoutTheme.accent)) } VStack { WorkoutInfoView(workout: workout) Divider() .overlay(WerkoutTheme.divider) HStack { if let calsBurned = healthKitWorkoutData?.caloriesBurned { CaloriesBurnedView(healthKitWorkoutData: $healthKitWorkoutData, calsBurned: calsBurned) } } RateWorkoutView(difficulty: $difficulty) .frame(maxHeight: 88) Divider() .overlay(WerkoutTheme.divider) TextField("Notes", text: $notes) .font(WerkoutTheme.bodyText) .foregroundStyle(WerkoutTheme.textPrimary) .frame(height: 55) .textFieldStyle(PlainTextFieldStyle()) .padding([.horizontal], 4) .background(WerkoutTheme.surfaceElevated) .overlay( RoundedRectangle(cornerRadius: WerkoutTheme.buttonRadius, style: .continuous) .strokeBorder(WerkoutTheme.divider, lineWidth: 1) ) .clipShape(RoundedRectangle(cornerRadius: WerkoutTheme.buttonRadius, style: .continuous)) if gettingHealthKitData { ProgressView("Getting HealthKit data") .foregroundStyle(WerkoutTheme.textPrimary) .progressViewStyle(CircularProgressViewStyle(tint: WerkoutTheme.accent)) .padding() } Spacer() Button("Upload", action: { upload(postBody: postData) }) .font(.system(size: 16, weight: .bold)) .foregroundStyle(WerkoutTheme.textPrimary) .frame(maxWidth: .infinity, alignment: .center) .frame(height: 44) .glassEffect(.regular.interactive()) .tint(WerkoutTheme.success) .clipShape(RoundedRectangle(cornerRadius: WerkoutTheme.buttonRadius, style: .continuous)) .padding() .frame(maxWidth: .infinity) .disabled(isUploading || gettingHealthKitData) } .padding([.leading, .trailing]) } .alert("Upload Failed", isPresented: $hasError) { Button("OK", role: .cancel) {} } message: { Text(errorMessage) } } func upload(postBody: [String: Any]) { guard isUploading == false else { return } isUploading = true var _postBody = postBody _postBody["difficulty"] = difficulty _postBody["notes"] = notes if let healthKitUUID = bridgeModule.healthKitUUID { _postBody["health_kit_workout_uuid"] = healthKitUUID.uuidString } CompleteWorkoutFetchable(postData: _postBody).fetch(completion: { result in switch result { case .success(_): DispatchQueue.main.async { self.isUploading = false bridgeModule.resetCurrentWorkout() dismiss() completedWorkoutDismissed?(true) } case .failure(let failure): DispatchQueue.main.async { self.isUploading = false self.errorMessage = failure.localizedDescription self.hasError = true } runtimeReporter.recordError( "Completed workout upload failed", metadata: ["error": failure.localizedDescription] ) } }) } } struct CompletedWorkoutView_Previews: PreviewProvider { static let postBody = [ "difficulty": 1, "workout_start_time": Date().timeFormatForUpload, "workout": 1, "total_time": 140, "total_calories": Float(120.0), "heart_rates": [65,65,4,54,232,12] ] as [String : Any] static let workout = PreviewData.workout() static var previews: some View { CompletedWorkoutView(postData: CompletedWorkoutView_Previews.postBody, workout: workout, completedWorkoutDismissed: { _ in }) } }