From 7ce996e451ec8cbe6bc31e18a222b75ecc274749 Mon Sep 17 00:00:00 2001 From: Trey t Date: Fri, 21 Jun 2024 10:41:37 -0500 Subject: [PATCH] split files up, fix complete workout screen not showing if remove workout details screen --- iphone/Werkout_ios.xcodeproj/project.pbxproj | 32 +- .../xcschemes/Werkout_ios.xcscheme | 2 +- .../Werkout_watch Watch App.xcscheme | 2 +- iphone/Werkout_ios/AudioEngine.swift | 66 ++++ iphone/Werkout_ios/BridgeModule+Timer.swift | 58 +++ iphone/Werkout_ios/BridgeModule+Watch.swift | 140 +++++++ .../BridgeModule+WorkoutActions.swift | 123 ++++++ iphone/Werkout_ios/BridgeModule.swift | 350 +----------------- .../WorkoutDetail/WorkoutDetailView.swift | 16 +- .../WatchMainViewModel+WorkoutActions.swift | 2 +- .../WatchMainViewModel.swift | 8 +- 11 files changed, 441 insertions(+), 358 deletions(-) create mode 100644 iphone/Werkout_ios/AudioEngine.swift create mode 100644 iphone/Werkout_ios/BridgeModule+Timer.swift create mode 100644 iphone/Werkout_ios/BridgeModule+Watch.swift create mode 100644 iphone/Werkout_ios/BridgeModule+WorkoutActions.swift diff --git a/iphone/Werkout_ios.xcodeproj/project.pbxproj b/iphone/Werkout_ios.xcodeproj/project.pbxproj index 6aa6699..6266120 100644 --- a/iphone/Werkout_ios.xcodeproj/project.pbxproj +++ b/iphone/Werkout_ios.xcodeproj/project.pbxproj @@ -9,6 +9,14 @@ /* Begin PBXBuildFile section */ 1C0494812C23C53E003D18BB /* WatchMainViewModel+WorkoutActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0494802C23C53E003D18BB /* WatchMainViewModel+WorkoutActions.swift */; }; 1C0494832C23C56E003D18BB /* WatchMainViewModel+WCSessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0494822C23C56E003D18BB /* WatchMainViewModel+WCSessionDelegate.swift */; }; + 1C0494872C23E7BD003D18BB /* BridgeModule+Watch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0494862C23E7BD003D18BB /* BridgeModule+Watch.swift */; }; + 1C0494882C23E7C5003D18BB /* BridgeModule+Watch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0494862C23E7BD003D18BB /* BridgeModule+Watch.swift */; }; + 1C04948A2C25CB4F003D18BB /* BridgeModule+WorkoutActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0494892C25CB4F003D18BB /* BridgeModule+WorkoutActions.swift */; }; + 1C04948C2C25CB80003D18BB /* AudioEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C04948B2C25CB80003D18BB /* AudioEngine.swift */; }; + 1C04948D2C25CC93003D18BB /* AudioEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C04948B2C25CB80003D18BB /* AudioEngine.swift */; }; + 1C04948E2C25CD3D003D18BB /* BridgeModule+WorkoutActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0494892C25CB4F003D18BB /* BridgeModule+WorkoutActions.swift */; }; + 1C0494932C25CEF0003D18BB /* BridgeModule+Timer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0494922C25CEF0003D18BB /* BridgeModule+Timer.swift */; }; + 1C0494942C25CEF4003D18BB /* BridgeModule+Timer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0494922C25CEF0003D18BB /* BridgeModule+Timer.swift */; }; 1C31C8842A53AE3E00350540 /* short_beep.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 1C31C8822A53AE3E00350540 /* short_beep.m4a */; }; 1C31C8852A53AE3E00350540 /* long_beep.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 1C31C8832A53AE3E00350540 /* long_beep.m4a */; }; 1C31C8872A55B2CC00350540 /* PlayerUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C31C8862A55B2CC00350540 /* PlayerUIView.swift */; }; @@ -148,6 +156,10 @@ /* Begin PBXFileReference section */ 1C0494802C23C53E003D18BB /* WatchMainViewModel+WorkoutActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WatchMainViewModel+WorkoutActions.swift"; sourceTree = ""; }; 1C0494822C23C56E003D18BB /* WatchMainViewModel+WCSessionDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WatchMainViewModel+WCSessionDelegate.swift"; sourceTree = ""; }; + 1C0494862C23E7BD003D18BB /* BridgeModule+Watch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BridgeModule+Watch.swift"; sourceTree = ""; }; + 1C0494892C25CB4F003D18BB /* BridgeModule+WorkoutActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BridgeModule+WorkoutActions.swift"; sourceTree = ""; }; + 1C04948B2C25CB80003D18BB /* AudioEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioEngine.swift; sourceTree = ""; }; + 1C0494922C25CEF0003D18BB /* BridgeModule+Timer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BridgeModule+Timer.swift"; sourceTree = ""; }; 1C31C8822A53AE3E00350540 /* short_beep.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = short_beep.m4a; sourceTree = ""; }; 1C31C8832A53AE3E00350540 /* long_beep.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = long_beep.m4a; sourceTree = ""; }; 1C31C8862A55B2CC00350540 /* PlayerUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerUIView.swift; sourceTree = ""; }; @@ -344,9 +356,13 @@ 1CF65AB22A452F360042FFBD /* WatchPackageModel.swift */, 1CF65A822A42347D0042FFBD /* Extensions.swift */, 1CF65A272A3972840042FFBD /* Persistence.swift */, + 1C04948B2C25CB80003D18BB /* AudioEngine.swift */, 1CD0C6662A5CA19600970E52 /* BaseURLs.swift */, 1C4AFF1A2A65FB190027710B /* CurrentWorkoutInfo.swift */, 1CF65A4F2A3A1EA90042FFBD /* BridgeModule.swift */, + 1C0494922C25CEF0003D18BB /* BridgeModule+Timer.swift */, + 1C0494862C23E7BD003D18BB /* BridgeModule+Watch.swift */, + 1C0494892C25CB4F003D18BB /* BridgeModule+WorkoutActions.swift */, 1CF65A802A412AA30042FFBD /* DataStore.swift */, 1CF65AB92A4894430042FFBD /* UserStore.swift */, 1C6BF28E2A56602B00450FD7 /* Keychain.swift */, @@ -565,7 +581,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1430; - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1530; TargetAttributes = { 1CF65A212A3972840042FFBD = { CreatedOnToolsVersion = 14.3; @@ -640,6 +656,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 1C04948A2C25CB4F003D18BB /* BridgeModule+WorkoutActions.swift in Sources */, 1CF65A7F2A4129320042FFBD /* Fetchables.swift in Sources */, 1CF65A812A412AA30042FFBD /* DataStore.swift in Sources */, 1CF65A6B2A3C1EAC0042FFBD /* CreateWorkoutMainView.swift in Sources */, @@ -697,9 +714,12 @@ 1C485C8C2A49D65600A6F896 /* WorkoutHistoryView.swift in Sources */, 1CAF4D952A52180600B00E50 /* PlanWorkoutView.swift in Sources */, 1C5190C62A589CC100885849 /* ActionsView.swift in Sources */, + 1C0494932C25CEF0003D18BB /* BridgeModule+Timer.swift in Sources */, 1CC092F52C1FAE7B0004E1E6 /* ShowNextUpView.swift in Sources */, 1CF65A5B2A3BF4BE0042FFBD /* Equipment.swift in Sources */, 1C4AFF152A60F25F0027710B /* ThotStyle.swift in Sources */, + 1C0494872C23E7BD003D18BB /* BridgeModule+Watch.swift in Sources */, + 1C04948C2C25CB80003D18BB /* AudioEngine.swift in Sources */, 1C5190C82A589CDA00885849 /* CurrentWorkoutElapsedTimeView.swift in Sources */, 1CC092F92C1FB1420004E1E6 /* AllExerciseView.swift in Sources */, 1CC093012C20B0E90004E1E6 /* TitleView.swift in Sources */, @@ -719,13 +739,16 @@ 1CF65A962A452D270042FFBD /* Werkout_watchApp.swift in Sources */, 1C4AFF192A65CD6F0027710B /* Superset.swift in Sources */, 1CF65AA92A452D9C0042FFBD /* Workout.swift in Sources */, + 1C0494882C23E7C5003D18BB /* BridgeModule+Watch.swift in Sources */, 1CF65AA62A452D9C0042FFBD /* Equipment.swift in Sources */, 1C4AFF162A60F27E0027710B /* ThotStyle.swift in Sources */, 1C4AFF212A8801090027710B /* AudioQueue.swift in Sources */, 1CF65AB12A452E1A0042FFBD /* BridgeModule.swift in Sources */, 1C0494832C23C56E003D18BB /* WatchMainViewModel+WCSessionDelegate.swift in Sources */, 1CF65AAA2A452D9C0042FFBD /* RegisteredUser.swift in Sources */, + 1C04948D2C25CC93003D18BB /* AudioEngine.swift in Sources */, 1CF65AB62A4532940042FFBD /* WatchMainViewModel.swift in Sources */, + 1C0494942C25CEF4003D18BB /* BridgeModule+Timer.swift in Sources */, 1CD0C6682A5CA1A200970E52 /* BaseURLs.swift in Sources */, 1CF65AA72A452D9C0042FFBD /* Muscle.swift in Sources */, 1C4AFF1C2A65FB2B0027710B /* CurrentWorkoutInfo.swift in Sources */, @@ -733,6 +756,7 @@ 1CF65AA52A452D9C0042FFBD /* CompletedWorkout.swift in Sources */, 1CF65AA82A452D9C0042FFBD /* Exercise.swift in Sources */, 1C5190D22A59ACA400885849 /* WatchControlView.swift in Sources */, + 1C04948E2C25CD3D003D18BB /* BridgeModule+WorkoutActions.swift in Sources */, 1C5190D42A59AEDE00885849 /* MainWatchView.swift in Sources */, 1CF65AB42A4530200042FFBD /* WatchPackageModel.swift in Sources */, 1C0494812C23C53E003D18BB /* WatchMainViewModel+WorkoutActions.swift in Sources */, @@ -783,9 +807,11 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -841,9 +867,11 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -869,6 +897,7 @@ CODE_SIGN_ENTITLEMENTS = Werkout_ios/Werkout_ios.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"Werkout_ios/Preview Content\""; DEVELOPMENT_TEAM = V3PF3M6B6U; ENABLE_HARDENED_RUNTIME = YES; @@ -913,6 +942,7 @@ CODE_SIGN_ENTITLEMENTS = Werkout_ios/Werkout_ios.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"Werkout_ios/Preview Content\""; DEVELOPMENT_TEAM = V3PF3M6B6U; ENABLE_HARDENED_RUNTIME = YES; diff --git a/iphone/Werkout_ios.xcodeproj/xcshareddata/xcschemes/Werkout_ios.xcscheme b/iphone/Werkout_ios.xcodeproj/xcshareddata/xcschemes/Werkout_ios.xcscheme index c9bf622..36dfc5e 100644 --- a/iphone/Werkout_ios.xcodeproj/xcshareddata/xcschemes/Werkout_ios.xcscheme +++ b/iphone/Werkout_ios.xcodeproj/xcshareddata/xcschemes/Werkout_ios.xcscheme @@ -1,6 +1,6 @@ 1 { + currentExerciseTimeLeft -= 1 + + if let currentExercise = currentExerciseInfo.allSupersetExecercise, let audioQueues = currentExercise.audioQueues { + if let audioQueue = audioQueues.first(where: { + $0.playAt == currentExerciseTimeLeft + }) { + switch audioQueue.audioType { + + case .shortBeep: + AudioEngine.playBeep() + case .finishBeep: + AudioEngine.playFinished() + case .remoteURL(let url): + AudioEngine.playRemoteAudio(fromURL: url) + } + } + } + sendCurrentExerciseToWatch() + } else { + nextExercise() + } + } +} diff --git a/iphone/Werkout_ios/BridgeModule+Watch.swift b/iphone/Werkout_ios/BridgeModule+Watch.swift new file mode 100644 index 0000000..6da7cf3 --- /dev/null +++ b/iphone/Werkout_ios/BridgeModule+Watch.swift @@ -0,0 +1,140 @@ +// +// BridgeModule+Watch.swift +// Werkout_ios +// +// Created by Trey Tartt on 6/19/24. +// + +import Foundation +import WatchConnectivity +import AVFoundation +import HealthKit + +extension BridgeModule: WCSessionDelegate { + func sendResetToWatch() { + let watchModel = PhoneToWatchActions.reset + let data = try! JSONEncoder().encode(watchModel) + + // user transferUserInfo b/c its guranteed to reach + // and end the workout + self.session.transferUserInfo(["package": data]) + } + + func sendStartWorkoutToWatch() { + let model = PhoneToWatchActions.startWorkout + let data = try! JSONEncoder().encode(model) + + // user transferUserInfo b/c its guranteed to reach + // and start the workout + self.session.transferUserInfo(["package": data]) + } + + func sendWorkoutCompleteToWatch() { + let model = PhoneToWatchActions.endWorkout + let data = try! JSONEncoder().encode(model) + + // user transferUserInfo b/c its guranteed to reach + // and end the workout + self.session.transferUserInfo(["package": data]) + } + + func sendCurrentExerciseToWatch() { + if let currentExercise = currentExerciseInfo.currentExercise, + let duration = currentExercise.duration , + duration > 0 { + let watchModel = WatchPackageModel(currentExerciseName: currentExercise.exercise.name, + currentTimeLeft: currentExerciseTimeLeft, + workoutStartDate: workoutStartDate ?? Date()) + let model = PhoneToWatchActions.inExercise(watchModel) + let data = try! JSONEncoder().encode(model) + send(data) + } else { + if let currentExercise = currentExerciseInfo.currentExercise, + let reps = currentExercise.reps, + reps > 0 { + + // if not a timer we need to set the watch display with number of reps + // if timer it will set when timer updates + let watchModel = WatchPackageModel(currentExerciseName: currentExercise.exercise.name, currentTimeLeft: reps, workoutStartDate: self.workoutStartDate ?? Date()) + let model = PhoneToWatchActions.inExercise(watchModel) + let data = try! JSONEncoder().encode(model) + self.send(data) + } + } + } + + func session(_ session: WCSession, didReceiveMessageData messageData: Data) { + if let model = try? JSONDecoder().decode(WatchActions.self, from: messageData) { + switch model { + case .nextExercise: + nextExercise() + AudioEngine.playFinished() + case .workoutComplete(let data): + DispatchQueue.main.async { + let model = try! JSONDecoder().decode(WatchFinishWorkoutModel.self, from: data) + self.healthKitUUID = model.healthKitUUID + } + case .restartExercise: + restartExercise() + case .previousExercise: + previousExercise() + case .stopWorkout: + completeWorkout() + case .pauseWorkout: + pauseWorkout() + } + } + } + + + func session(_ session: WCSession, + activationDidCompleteWith activationState: WCSessionActivationState, + error: Error?) { + switch activationState { + case .notActivated: + print("notActivated") + case .inactive: + print("inactive") + case .activated: + print("activated") +#if os(iOS) + let workoutConfiguration = HKWorkoutConfiguration() + workoutConfiguration.activityType = .functionalStrengthTraining + workoutConfiguration.locationType = .indoor + if WCSession.isSupported(), session.activationState == .activated, session.isWatchAppInstalled { + HKHealthStore().startWatchApp(with: workoutConfiguration, completion: { (success, error) in + print(error.debugDescription) + }) + } +#endif + @unknown default: + print("default") + } + } +#if os(iOS) + func sessionDidBecomeInactive(_ session: WCSession) { + + } + + func sessionDidDeactivate(_ session: WCSession) { + session.activate() + } +#endif + func send(_ data: Data) { + guard session.activationState == .activated else { + return + } +#if os(iOS) + guard session.isWatchAppInstalled else { + return + } +#else + guard session.isCompanionAppInstalled else { + return + } +#endif + session.sendMessageData(data, replyHandler: nil) { error in + print("Cannot send message: \(String(describing: error))") + } + } +} diff --git a/iphone/Werkout_ios/BridgeModule+WorkoutActions.swift b/iphone/Werkout_ios/BridgeModule+WorkoutActions.swift new file mode 100644 index 0000000..4db1fee --- /dev/null +++ b/iphone/Werkout_ios/BridgeModule+WorkoutActions.swift @@ -0,0 +1,123 @@ +// +// BridgeModule+WorkoutActions.swift +// Werkout_ios +// +// Created by Trey Tartt on 6/21/24. +// + +import Foundation +import WatchConnectivity + +extension BridgeModule { + func pauseWorkout() { + if let _ = currentExerciseTimer { + currentExerciseTimer?.invalidate() + currentExerciseTimer = nil + isPaused = true + } else { + isPaused = false + startExerciseTimerWith(duration: currentExerciseTimeLeft) + } + } + + func nextExercise() { + if let nextSupersetExercise = currentExerciseInfo.goToNextExercise { + updateCurrent(exercise: nextSupersetExercise) + } else { + completeWorkout() + } + } + + func previousExercise() { + if let nextSupersetExercise = currentExerciseInfo.previousExercise { + updateCurrent(exercise: nextSupersetExercise) + } else { + completeWorkout() + } + } + + func restartExercise() { + if let currentExercise = currentExerciseInfo.currentExercise { + updateCurrent(exercise: currentExercise) + } + } + + func updateCurrent(exercise: SupersetExercise) { + DispatchQueue.main.async { + self.currentExerciseTimer?.invalidate() + self.currentExerciseTimer = nil + + if let duration = exercise.duration, duration > 0 { + self.startExerciseTimerWith(duration: duration) + } + self.sendCurrentExerciseToWatch() + } + } + + func completeWorkout() { + self.currentExerciseTimer?.invalidate() + self.currentExerciseTimer = nil + self.isInWorkout = false + + workoutEndDate = Date() + if let completedWorkout = completedWorkout { + completedWorkout() + self.completedWorkout = nil + } + } + + func start(workout: Workout) { + currentExerciseInfo.complete = { + self.completeWorkout() + } + + currentExerciseInfo.start(workout: workout) + currentWorkoutRunTimeInSeconds = 0 + currentWorkoutRunTimer?.invalidate() + currentWorkoutRunTimer = nil + isPaused = false + + sendStartWorkoutToWatch() + + if let superetExercise = currentExerciseInfo.currentExercise { + updateCurrent(exercise: superetExercise) + startWorkoutTimer() + workoutStartDate = Date() + isInWorkout = true + + if WCSession.isSupported() { + session.delegate = self + session.activate() + } + } + } + + func goToExerciseAt(section: Int, row: Int) { + if let superetExercise = currentExerciseInfo.goToWorkoutAt(supersetIndex: section, + exerciseIndex: row) { + updateCurrent(exercise: superetExercise) + } + } + + var nextExerciseObject: SupersetExercise? { + currentExerciseInfo.goToNextExercise + } + + func resetCurrentWorkout() { + DispatchQueue.main.async { + self.currentWorkoutRunTimeInSeconds = 0 + self.currentWorkoutRunTimer?.invalidate() + self.currentWorkoutRunTimer = nil + + self.currentExerciseTimer?.invalidate() + self.currentExerciseTimer = nil + + self.currentWorkoutRunTimeInSeconds = -1 + self.currentExerciseInfo.reset() + + self.isInWorkout = false + self.workoutStartDate = nil + self.workoutEndDate = nil + } + } +} diff --git a/iphone/Werkout_ios/BridgeModule.swift b/iphone/Werkout_ios/BridgeModule.swift index 4fdf7ed..6130aa1 100644 --- a/iphone/Werkout_ios/BridgeModule.swift +++ b/iphone/Werkout_ios/BridgeModule.swift @@ -23,6 +23,7 @@ enum PhoneToWatchActions: Codable { case inExercise(WatchPackageModel) case reset case endWorkout + case startWorkout } class BridgeModule: NSObject, ObservableObject { @@ -34,11 +35,11 @@ class BridgeModule: NSObject, ObservableObject { @Published var isInWorkout = false var completedWorkout: (() -> Void)? @Published var currentWorkoutRunTimeInSeconds: Int = -1 - private var currentWorkoutRunTimer: Timer? + var currentWorkoutRunTimer: Timer? - public private(set) var workoutStartDate: Date? + public var workoutStartDate: Date? - private var currentExerciseTimer: Timer? + public var currentExerciseTimer: Timer? @Published public private(set) var currentExerciseInfo = CurrentWorkoutInfo() @Published var previewWorkout: Workout? @@ -48,346 +49,9 @@ class BridgeModule: NSObject, ObservableObject { private var isWatchConnected = false // workoutEndDate fills out WatchPackageModel.workoutEndDate which // tells the watch app to stop the workout - public private(set) var workoutEndDate: Date? - @Published public private(set) var healthKitUUID: UUID? + public var workoutEndDate: Date? + @Published var healthKitUUID: UUID? @Published var isPaused = false - var audioPlayer: AVAudioPlayer? - var avPlayer: AVPlayer? - private let session: WCSession = WCSession.default - - func start(workout: Workout) { - currentExerciseInfo.complete = { - self.completeWorkout() - } - - currentExerciseInfo.start(workout: workout) - currentWorkoutRunTimeInSeconds = 0 - currentWorkoutRunTimer?.invalidate() - currentWorkoutRunTimer = nil - isPaused = false - - if let superetExercise = currentExerciseInfo.currentExercise { - updateCurrent(exercise: superetExercise) - startWorkoutTimer() - workoutStartDate = Date() - isInWorkout = true - - if WCSession.isSupported() { - session.delegate = self - session.activate() - } - } - } - - func goToExerciseAt(section: Int, row: Int) { - if let superetExercise = currentExerciseInfo.goToWorkoutAt(supersetIndex: section, - exerciseIndex: row) { - updateCurrent(exercise: superetExercise) - } - } - - var nextExerciseObject: SupersetExercise? { - currentExerciseInfo.goToNextExercise - } - - func resetCurrentWorkout() { - DispatchQueue.main.async { - self.currentWorkoutRunTimeInSeconds = 0 - self.currentWorkoutRunTimer?.invalidate() - self.currentWorkoutRunTimer = nil - - self.currentExerciseTimer?.invalidate() - self.currentExerciseTimer = nil - - self.currentWorkoutRunTimeInSeconds = -1 - self.currentExerciseInfo.reset() - - self.isInWorkout = false - self.workoutStartDate = nil - self.workoutEndDate = nil - } - } - - private func startWorkoutTimer() { - currentWorkoutRunTimer?.invalidate() - currentWorkoutRunTimer = nil - currentWorkoutRunTimer = Timer.scheduledTimer(timeInterval: 1, - target: self, - selector: #selector(addOneToWorkoutRunTime), - userInfo: nil, - repeats: true) - currentWorkoutRunTimer?.fire() - } - - private func startExerciseTimerWith(duration: Int) { - DispatchQueue.main.async { - self.currentExerciseTimer?.invalidate() - self.currentExerciseTimer = nil - self.currentExerciseTimeLeft = duration - self.currentExerciseTimer = Timer.scheduledTimer(timeInterval: 1, - target: self, - selector: #selector(self.updateCurrentExerciseTimer), - userInfo: nil, - repeats: true) - self.currentExerciseTimer?.fire() - } - } - - @objc func updateCurrentExerciseTimer() { - if currentExerciseTimeLeft > 1 { - currentExerciseTimeLeft -= 1 - - if let currentExercise = currentExerciseInfo.allSupersetExecercise, let audioQueues = currentExercise.audioQueues { - if let audioQueue = audioQueues.first(where: { - $0.playAt == currentExerciseTimeLeft - }) { - switch audioQueue.audioType { - - case .shortBeep: - playBeep() - case .finishBeep: - playFinished() - case .remoteURL(let url): - playRemoteAudio(fromURL: url) - } - } - } - sendCurrentExerciseToWatch() - } else { - nextExercise() - } - } - - func pauseWorkout() { - if let _ = currentExerciseTimer { - currentExerciseTimer?.invalidate() - currentExerciseTimer = nil - isPaused = true - } else { - isPaused = false - startExerciseTimerWith(duration: currentExerciseTimeLeft) - } - } - - func nextExercise() { - if let nextSupersetExercise = currentExerciseInfo.goToNextExercise { - updateCurrent(exercise: nextSupersetExercise) - } else { - completeWorkout() - } - } - - func previousExercise() { - if let nextSupersetExercise = currentExerciseInfo.previousExercise { - updateCurrent(exercise: nextSupersetExercise) - } else { - completeWorkout() - } - } - - func restartExercise() { - if let currentExercise = currentExerciseInfo.currentExercise { - updateCurrent(exercise: currentExercise) - } - } - - @objc func addOneToWorkoutRunTime() { - currentWorkoutRunTimeInSeconds += 1 - } - - func updateCurrent(exercise: SupersetExercise) { - DispatchQueue.main.async { - self.currentExerciseTimer?.invalidate() - self.currentExerciseTimer = nil - - if let duration = exercise.duration, duration > 0 { - self.startExerciseTimerWith(duration: duration) - } - self.sendCurrentExerciseToWatch() - } - } - - func completeWorkout() { - self.currentExerciseTimer?.invalidate() - self.currentExerciseTimer = nil - self.isInWorkout = false - - workoutEndDate = Date() - if let completedWorkout = completedWorkout { - completedWorkout() - self.completedWorkout = nil - } - } - - func playRemoteAudio(fromURL url: URL) { -#if os(iOS) - let playerItem = AVPlayerItem(url: url) - do { - try AVAudioSession.sharedInstance().setCategory(.playback, - mode: .default, - options: [.mixWithOthers]) - try AVAudioSession.sharedInstance().setActive(true) - - avPlayer = AVPlayer(playerItem: playerItem) - avPlayer?.play() - } catch { - print("ERROR") - } -#endif - } - - func playBeep() { -#if os(iOS) - if let path = Bundle.main.path(forResource: "short_beep", ofType: "m4a") { - do { - try AVAudioSession.sharedInstance().setCategory(.playback, - mode: .default, - options: [.mixWithOthers]) - try AVAudioSession.sharedInstance().setActive(true) - - audioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: path)) - audioPlayer?.play() - } catch { - print("ERROR") - } - } -#endif - } - - func playFinished() { -#if os(iOS) - if let path = Bundle.main.path(forResource: "long_beep", ofType: "m4a") { - do { - try AVAudioSession.sharedInstance().setCategory(.playback, - mode: .default, - options: [.mixWithOthers]) - try AVAudioSession.sharedInstance().setActive(true) - - audioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: path)) - audioPlayer?.play() - } catch { - print("ERROR") - } - } -#endif - } -} - -extension BridgeModule: WCSessionDelegate { - func sendResetToWatch() { - let watchModel = PhoneToWatchActions.reset - let data = try! JSONEncoder().encode(watchModel) - - // user transferUserInfo b/c its guranteed to reach - // and end the workout - self.session.transferUserInfo(["package": data]) - } - - func sendWorkoutCompleteToWatch() { - let model = PhoneToWatchActions.endWorkout - let data = try! JSONEncoder().encode(model) - - // user transferUserInfo b/c its guranteed to reach - // and end the workout - self.session.transferUserInfo(["package": data]) - } - - func sendCurrentExerciseToWatch() { - if let currentExercise = currentExerciseInfo.currentExercise, - let duration = currentExercise.duration , - duration > 0 { - let watchModel = WatchPackageModel(currentExerciseName: currentExercise.exercise.name, currentTimeLeft: currentExerciseTimeLeft, workoutStartDate: workoutStartDate ?? Date()) - let model = PhoneToWatchActions.inExercise(watchModel) - let data = try! JSONEncoder().encode(model) - send(data) - } else { - if let currentExercise = currentExerciseInfo.currentExercise, - let reps = currentExercise.reps, - reps > 0 { - - // if not a timer we need to set the watch display with number of reps - // if timer it will set when timer updates - let watchModel = WatchPackageModel(currentExerciseName: currentExercise.exercise.name, currentTimeLeft: reps, workoutStartDate: self.workoutStartDate ?? Date()) - let model = PhoneToWatchActions.inExercise(watchModel) - let data = try! JSONEncoder().encode(model) - self.send(data) - } - } - } - - func session(_ session: WCSession, didReceiveMessageData messageData: Data) { - if let model = try? JSONDecoder().decode(WatchActions.self, from: messageData) { - switch model { - case .nextExercise: - nextExercise() - playFinished() - case .workoutComplete(let data): - DispatchQueue.main.async { - let model = try! JSONDecoder().decode(WatchFinishWorkoutModel.self, from: data) - self.healthKitUUID = model.healthKitUUID - } - case .restartExercise: - restartExercise() - case .previousExercise: - previousExercise() - case .stopWorkout: - completeWorkout() - case .pauseWorkout: - pauseWorkout() - } - } - } - - - func session(_ session: WCSession, - activationDidCompleteWith activationState: WCSessionActivationState, - error: Error?) { - switch activationState { - case .notActivated: - print("notActivated") - case .inactive: - print("inactive") - case .activated: - print("activated") -#if os(iOS) - let workoutConfiguration = HKWorkoutConfiguration() - workoutConfiguration.activityType = .functionalStrengthTraining - workoutConfiguration.locationType = .indoor - if WCSession.isSupported(), session.activationState == .activated, session.isWatchAppInstalled { - HKHealthStore().startWatchApp(with: workoutConfiguration, completion: { (success, error) in - print(error.debugDescription) - }) - } -#endif - @unknown default: - print("default") - } - } -#if os(iOS) - func sessionDidBecomeInactive(_ session: WCSession) { - - } - - func sessionDidDeactivate(_ session: WCSession) { - session.activate() - } -#endif - func send(_ data: Data) { - guard session.activationState == .activated else { - return - } -#if os(iOS) - guard session.isWatchAppInstalled else { - return - } -#else - guard session.isCompanionAppInstalled else { - return - } -#endif - session.sendMessageData(data, replyHandler: nil) { error in - print("Cannot send message: \(String(describing: error))") - } - } + let session: WCSession = WCSession.default } diff --git a/iphone/Werkout_ios/Views/WorkoutDetail/WorkoutDetailView.swift b/iphone/Werkout_ios/Views/WorkoutDetail/WorkoutDetailView.swift index ca99769..f7ea538 100644 --- a/iphone/Werkout_ios/Views/WorkoutDetail/WorkoutDetailView.swift +++ b/iphone/Werkout_ios/Views/WorkoutDetail/WorkoutDetailView.swift @@ -172,6 +172,14 @@ struct WorkoutDetailView: View { avPlayer.play() } } + + bridgeModule.completedWorkout = { + if let workoutData = createWorkoutData() { + workoutComplete = .completedWorkout(workoutData) + } else { + bridgeModule.resetCurrentWorkout() + } + } } .onReceive(NotificationCenter.default.publisher( for: UIScene.willEnterForegroundNotification)) { _ in @@ -194,14 +202,6 @@ struct WorkoutDetailView: View { } func startWorkout(workout: Workout) { - bridgeModule.completedWorkout = { - if let workoutData = createWorkoutData() { - workoutComplete = .completedWorkout(workoutData) - } else { - bridgeModule.resetCurrentWorkout() - } - } - bridgeModule.start(workout: workout) } diff --git a/iphone/Werkout_watch Watch App/WatchMainViewModel+WorkoutActions.swift b/iphone/Werkout_watch Watch App/WatchMainViewModel+WorkoutActions.swift index ea7caa7..c77b7f6 100644 --- a/iphone/Werkout_watch Watch App/WatchMainViewModel+WorkoutActions.swift +++ b/iphone/Werkout_watch Watch App/WatchMainViewModel+WorkoutActions.swift @@ -52,7 +52,7 @@ extension WatchMainViewModel { } isInWorkout = true } else { - print("didn not init workout") + print("did not init workout") } } diff --git a/iphone/Werkout_watch Watch App/WatchMainViewModel.swift b/iphone/Werkout_watch Watch App/WatchMainViewModel.swift index 6295816..f9a61af 100644 --- a/iphone/Werkout_watch Watch App/WatchMainViewModel.swift +++ b/iphone/Werkout_watch Watch App/WatchMainViewModel.swift @@ -87,9 +87,6 @@ class WatchMainViewModel: NSObject, ObservableObject { DispatchQueue.main.async { switch model { case .inExercise(let data): - if self.isInWorkout == false { - self.startWorkout() - } self.watchPackageModel = data case .reset: self.isInWorkout = false @@ -99,6 +96,11 @@ class WatchMainViewModel: NSObject, ObservableObject { self.isInWorkout = false self.watchPackageModel = WatchMainViewModel.defualtPackageModle self.stopWorkout(sendDetails: true) + case .startWorkout: + if self.isInWorkout { + self.stopWorkout(sendDetails: false) + } + self.startWorkout() } } }