misc watch stuff including taps, sounds, maybe working
This commit is contained in:
@@ -6,12 +6,16 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import AVKit
|
import AVKit
|
||||||
|
import AVFoundation
|
||||||
|
|
||||||
class AudioEngine {
|
class AudioEngine {
|
||||||
// var audioPlayer: AVAudioPlayer?
|
static let shared = AudioEngine()
|
||||||
// var avPlayer: AVPlayer?
|
private init() { }
|
||||||
|
|
||||||
static func playRemoteAudio(fromURL url: URL) {
|
var audioPlayer: AVAudioPlayer?
|
||||||
|
var avPlayer: AVPlayer?
|
||||||
|
|
||||||
|
func playRemoteAudio(fromURL url: URL) {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
let playerItem = AVPlayerItem(url: url)
|
let playerItem = AVPlayerItem(url: url)
|
||||||
do {
|
do {
|
||||||
@@ -20,15 +24,15 @@ class AudioEngine {
|
|||||||
options: [.mixWithOthers])
|
options: [.mixWithOthers])
|
||||||
try AVAudioSession.sharedInstance().setActive(true)
|
try AVAudioSession.sharedInstance().setActive(true)
|
||||||
|
|
||||||
let avPlayer = AVPlayer(playerItem: playerItem)
|
avPlayer = AVPlayer(playerItem: playerItem)
|
||||||
avPlayer.play()
|
avPlayer?.play()
|
||||||
} catch {
|
} catch {
|
||||||
print("ERROR")
|
print("ERROR")
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static func playBeep() {
|
func playBeep() {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
if let path = Bundle.main.path(forResource: "short_beep", ofType: "m4a") {
|
if let path = Bundle.main.path(forResource: "short_beep", ofType: "m4a") {
|
||||||
do {
|
do {
|
||||||
@@ -37,8 +41,8 @@ class AudioEngine {
|
|||||||
options: [.mixWithOthers])
|
options: [.mixWithOthers])
|
||||||
try AVAudioSession.sharedInstance().setActive(true)
|
try AVAudioSession.sharedInstance().setActive(true)
|
||||||
|
|
||||||
let audioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: path))
|
audioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: path))
|
||||||
audioPlayer.play()
|
audioPlayer?.play()
|
||||||
} catch {
|
} catch {
|
||||||
print("ERROR")
|
print("ERROR")
|
||||||
}
|
}
|
||||||
@@ -46,7 +50,7 @@ class AudioEngine {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static func playFinished() {
|
func playFinished() {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
if let path = Bundle.main.path(forResource: "long_beep", ofType: "m4a") {
|
if let path = Bundle.main.path(forResource: "long_beep", ofType: "m4a") {
|
||||||
do {
|
do {
|
||||||
@@ -55,8 +59,8 @@ class AudioEngine {
|
|||||||
options: [.mixWithOthers])
|
options: [.mixWithOthers])
|
||||||
try AVAudioSession.sharedInstance().setActive(true)
|
try AVAudioSession.sharedInstance().setActive(true)
|
||||||
|
|
||||||
let audioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: path))
|
audioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: path))
|
||||||
audioPlayer.play()
|
audioPlayer?.play()
|
||||||
} catch {
|
} catch {
|
||||||
print("ERROR")
|
print("ERROR")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,11 +42,11 @@ extension BridgeModule {
|
|||||||
switch audioQueue.audioType {
|
switch audioQueue.audioType {
|
||||||
|
|
||||||
case .shortBeep:
|
case .shortBeep:
|
||||||
AudioEngine.playBeep()
|
AudioEngine.shared.playBeep()
|
||||||
case .finishBeep:
|
case .finishBeep:
|
||||||
AudioEngine.playFinished()
|
AudioEngine.shared.playFinished()
|
||||||
case .remoteURL(let url):
|
case .remoteURL(let url):
|
||||||
AudioEngine.playRemoteAudio(fromURL: url)
|
AudioEngine.shared.playRemoteAudio(fromURL: url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,28 +14,22 @@ extension BridgeModule: WCSessionDelegate {
|
|||||||
func sendResetToWatch() {
|
func sendResetToWatch() {
|
||||||
let watchModel = PhoneToWatchActions.reset
|
let watchModel = PhoneToWatchActions.reset
|
||||||
let data = try! JSONEncoder().encode(watchModel)
|
let data = try! JSONEncoder().encode(watchModel)
|
||||||
|
send(data)
|
||||||
// user transferUserInfo b/c its guranteed to reach
|
// self.session.transferUserInfo(["package": data])
|
||||||
// and end the workout
|
|
||||||
self.session.transferUserInfo(["package": data])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendStartWorkoutToWatch() {
|
func sendStartWorkoutToWatch() {
|
||||||
let model = PhoneToWatchActions.startWorkout
|
let model = PhoneToWatchActions.startWorkout
|
||||||
let data = try! JSONEncoder().encode(model)
|
let data = try! JSONEncoder().encode(model)
|
||||||
|
send(data)
|
||||||
// user transferUserInfo b/c its guranteed to reach
|
// self.session.transferUserInfo(["package": data])
|
||||||
// and start the workout
|
|
||||||
self.session.transferUserInfo(["package": data])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendWorkoutCompleteToWatch() {
|
func sendWorkoutCompleteToWatch() {
|
||||||
let model = PhoneToWatchActions.endWorkout
|
let model = PhoneToWatchActions.endWorkout
|
||||||
let data = try! JSONEncoder().encode(model)
|
let data = try! JSONEncoder().encode(model)
|
||||||
|
send(data)
|
||||||
// user transferUserInfo b/c its guranteed to reach
|
// self.session.transferUserInfo(["package": data])
|
||||||
// and end the workout
|
|
||||||
self.session.transferUserInfo(["package": data])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendCurrentExerciseToWatch() {
|
func sendCurrentExerciseToWatch() {
|
||||||
@@ -43,6 +37,7 @@ extension BridgeModule: WCSessionDelegate {
|
|||||||
let duration = currentExercise.duration ,
|
let duration = currentExercise.duration ,
|
||||||
duration > 0 {
|
duration > 0 {
|
||||||
let watchModel = WatchPackageModel(currentExerciseName: currentExercise.exercise.name,
|
let watchModel = WatchPackageModel(currentExerciseName: currentExercise.exercise.name,
|
||||||
|
currentExerciseID: currentExercise.id ?? -1,
|
||||||
currentTimeLeft: currentExerciseTimeLeft,
|
currentTimeLeft: currentExerciseTimeLeft,
|
||||||
workoutStartDate: workoutStartDate ?? Date())
|
workoutStartDate: workoutStartDate ?? Date())
|
||||||
let model = PhoneToWatchActions.inExercise(watchModel)
|
let model = PhoneToWatchActions.inExercise(watchModel)
|
||||||
@@ -55,7 +50,7 @@ extension BridgeModule: WCSessionDelegate {
|
|||||||
|
|
||||||
// if not a timer we need to set the watch display with number of reps
|
// if not a timer we need to set the watch display with number of reps
|
||||||
// if timer it will set when timer updates
|
// if timer it will set when timer updates
|
||||||
let watchModel = WatchPackageModel(currentExerciseName: currentExercise.exercise.name, currentTimeLeft: reps, workoutStartDate: self.workoutStartDate ?? Date())
|
let watchModel = WatchPackageModel(currentExerciseName: currentExercise.exercise.name, currentExerciseID: currentExercise.id ?? -1, currentTimeLeft: reps, workoutStartDate: self.workoutStartDate ?? Date())
|
||||||
let model = PhoneToWatchActions.inExercise(watchModel)
|
let model = PhoneToWatchActions.inExercise(watchModel)
|
||||||
let data = try! JSONEncoder().encode(model)
|
let data = try! JSONEncoder().encode(model)
|
||||||
self.send(data)
|
self.send(data)
|
||||||
@@ -68,7 +63,7 @@ extension BridgeModule: WCSessionDelegate {
|
|||||||
switch model {
|
switch model {
|
||||||
case .nextExercise:
|
case .nextExercise:
|
||||||
nextExercise()
|
nextExercise()
|
||||||
AudioEngine.playFinished()
|
AudioEngine.shared.playFinished()
|
||||||
case .workoutComplete(let data):
|
case .workoutComplete(let data):
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
let model = try! JSONDecoder().decode(WatchFinishWorkoutModel.self, from: data)
|
let model = try! JSONDecoder().decode(WatchFinishWorkoutModel.self, from: data)
|
||||||
|
|||||||
@@ -77,8 +77,6 @@ extension BridgeModule {
|
|||||||
currentWorkoutRunTimer = nil
|
currentWorkoutRunTimer = nil
|
||||||
isPaused = false
|
isPaused = false
|
||||||
|
|
||||||
sendStartWorkoutToWatch()
|
|
||||||
|
|
||||||
if let superetExercise = currentExerciseInfo.currentExercise {
|
if let superetExercise = currentExerciseInfo.currentExercise {
|
||||||
updateCurrent(exercise: superetExercise)
|
updateCurrent(exercise: superetExercise)
|
||||||
startWorkoutTimer()
|
startWorkoutTimer()
|
||||||
@@ -88,6 +86,7 @@ extension BridgeModule {
|
|||||||
if WCSession.isSupported() {
|
if WCSession.isSupported() {
|
||||||
session.delegate = self
|
session.delegate = self
|
||||||
session.activate()
|
session.activate()
|
||||||
|
sendStartWorkoutToWatch()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import Foundation
|
|||||||
|
|
||||||
struct WatchPackageModel: Codable {
|
struct WatchPackageModel: Codable {
|
||||||
var currentExerciseName: String
|
var currentExerciseName: String
|
||||||
|
var currentExerciseID: Int
|
||||||
var currentTimeLeft: Int
|
var currentTimeLeft: Int
|
||||||
var workoutStartDate: Date
|
var workoutStartDate: Date
|
||||||
var workoutEndDate: Date?
|
var workoutEndDate: Date?
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ struct ActionsView: View {
|
|||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
} else {
|
} else {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
|
AudioEngine.shared.playFinished()
|
||||||
nextExercise()
|
nextExercise()
|
||||||
}, label: {
|
}, label: {
|
||||||
Image(systemName: "arrow.forward")
|
Image(systemName: "arrow.forward")
|
||||||
@@ -68,6 +69,7 @@ struct ActionsView: View {
|
|||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
|
|
||||||
Button(action: {
|
Button(action: {
|
||||||
|
AudioEngine.shared.playFinished()
|
||||||
bridgeModule.pauseWorkout()
|
bridgeModule.pauseWorkout()
|
||||||
}, label: {
|
}, label: {
|
||||||
bridgeModule.isPaused ?
|
bridgeModule.isPaused ?
|
||||||
@@ -84,6 +86,7 @@ struct ActionsView: View {
|
|||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
|
|
||||||
Button(action: {
|
Button(action: {
|
||||||
|
AudioEngine.shared.playFinished()
|
||||||
completedWorkout?()
|
completedWorkout?()
|
||||||
}, label: {
|
}, label: {
|
||||||
Image(systemName: "checkmark")
|
Image(systemName: "checkmark")
|
||||||
|
|||||||
@@ -44,9 +44,15 @@ struct WatchControlView: View {
|
|||||||
Button(action: {
|
Button(action: {
|
||||||
vm.pauseWorkout()
|
vm.pauseWorkout()
|
||||||
}, label: {
|
}, label: {
|
||||||
Image(systemName: "pause")
|
if vm.isPaused {
|
||||||
.font(.title)
|
Image(systemName: "play")
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
.font(.title)
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
|
} else {
|
||||||
|
Image(systemName: "pause")
|
||||||
|
.font(.title)
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.buttonStyle(BorderedButtonStyle(tint: .blue))
|
.buttonStyle(BorderedButtonStyle(tint: .blue))
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ extension WatchMainViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
isInWorkout = true
|
isInWorkout = true
|
||||||
|
isPaused = false
|
||||||
|
WKInterfaceDevice.current().play(.start)
|
||||||
} else {
|
} else {
|
||||||
print("did not init workout")
|
print("did not init workout")
|
||||||
}
|
}
|
||||||
@@ -69,6 +71,7 @@ extension WatchMainViewModel {
|
|||||||
self.hkBuilder = nil
|
self.hkBuilder = nil
|
||||||
self.heartRates.removeAll()
|
self.heartRates.removeAll()
|
||||||
self.isInWorkout = false
|
self.isInWorkout = false
|
||||||
|
self.isPaused = false
|
||||||
|
|
||||||
guard let id = workout?.uuid else {
|
guard let id = workout?.uuid else {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -20,13 +20,14 @@ class WatchMainViewModel: NSObject, ObservableObject {
|
|||||||
@Published var heartValue: Int?
|
@Published var heartValue: Int?
|
||||||
|
|
||||||
static var defualtPackageModle: WatchPackageModel {
|
static var defualtPackageModle: WatchPackageModel {
|
||||||
WatchPackageModel(currentExerciseName: "", currentTimeLeft: -1, workoutStartDate: Date())
|
WatchPackageModel(currentExerciseName: "", currentExerciseID: -1, currentTimeLeft: -1, workoutStartDate: Date())
|
||||||
}
|
}
|
||||||
|
|
||||||
let healthStore = HKHealthStore()
|
let healthStore = HKHealthStore()
|
||||||
var hkWorkoutSession: HKWorkoutSession?
|
var hkWorkoutSession: HKWorkoutSession?
|
||||||
var hkBuilder: HKLiveWorkoutBuilder?
|
var hkBuilder: HKLiveWorkoutBuilder?
|
||||||
var heartRates = [Int]()
|
var heartRates = [Int]()
|
||||||
|
@Published var isPaused = false
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
session = WCSession.default
|
session = WCSession.default
|
||||||
@@ -56,38 +57,51 @@ class WatchMainViewModel: NSObject, ObservableObject {
|
|||||||
let nextExerciseAction = WatchActions.nextExercise
|
let nextExerciseAction = WatchActions.nextExercise
|
||||||
let data = try! JSONEncoder().encode(nextExerciseAction)
|
let data = try! JSONEncoder().encode(nextExerciseAction)
|
||||||
send(data)
|
send(data)
|
||||||
|
WKInterfaceDevice.current().play(.start)
|
||||||
}
|
}
|
||||||
|
|
||||||
func restartExercise() {
|
func restartExercise() {
|
||||||
let nextExerciseAction = WatchActions.restartExercise
|
let nextExerciseAction = WatchActions.restartExercise
|
||||||
let data = try! JSONEncoder().encode(nextExerciseAction)
|
let data = try! JSONEncoder().encode(nextExerciseAction)
|
||||||
send(data)
|
send(data)
|
||||||
|
WKInterfaceDevice.current().play(.start)
|
||||||
}
|
}
|
||||||
|
|
||||||
func previousExercise() {
|
func previousExercise() {
|
||||||
let nextExerciseAction = WatchActions.previousExercise
|
let nextExerciseAction = WatchActions.previousExercise
|
||||||
let data = try! JSONEncoder().encode(nextExerciseAction)
|
let data = try! JSONEncoder().encode(nextExerciseAction)
|
||||||
send(data)
|
send(data)
|
||||||
|
WKInterfaceDevice.current().play(.start)
|
||||||
}
|
}
|
||||||
|
|
||||||
func completeWorkout() {
|
func completeWorkout() {
|
||||||
let nextExerciseAction = WatchActions.stopWorkout
|
let nextExerciseAction = WatchActions.stopWorkout
|
||||||
let data = try! JSONEncoder().encode(nextExerciseAction)
|
let data = try! JSONEncoder().encode(nextExerciseAction)
|
||||||
send(data)
|
send(data)
|
||||||
|
WKInterfaceDevice.current().play(.start)
|
||||||
}
|
}
|
||||||
|
|
||||||
func pauseWorkout() {
|
func pauseWorkout() {
|
||||||
let nextExerciseAction = WatchActions.pauseWorkout
|
let nextExerciseAction = WatchActions.pauseWorkout
|
||||||
let data = try! JSONEncoder().encode(nextExerciseAction)
|
let data = try! JSONEncoder().encode(nextExerciseAction)
|
||||||
send(data)
|
send(data)
|
||||||
|
isPaused = !isPaused
|
||||||
|
WKInterfaceDevice.current().play(.start)
|
||||||
}
|
}
|
||||||
|
|
||||||
func dataToAction(messageData: Data) {
|
func dataToAction(messageData: Data) {
|
||||||
if let model = try? JSONDecoder().decode(PhoneToWatchActions.self, from: messageData) {
|
if let model = try? JSONDecoder().decode(PhoneToWatchActions.self, from: messageData) {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
switch model {
|
switch model {
|
||||||
case .inExercise(let data):
|
case .inExercise(let newWatchPackageModel):
|
||||||
self.watchPackageModel = data
|
if !self.isInWorkout {
|
||||||
|
self.startWorkout()
|
||||||
|
}
|
||||||
|
if self.watchPackageModel.currentExerciseID != newWatchPackageModel.currentExerciseID {
|
||||||
|
self.isPaused = false
|
||||||
|
WKInterfaceDevice.current().play(.start)
|
||||||
|
}
|
||||||
|
self.watchPackageModel = newWatchPackageModel
|
||||||
case .reset:
|
case .reset:
|
||||||
self.isInWorkout = false
|
self.isInWorkout = false
|
||||||
self.watchPackageModel = WatchMainViewModel.defualtPackageModle
|
self.watchPackageModel = WatchMainViewModel.defualtPackageModle
|
||||||
|
|||||||
Reference in New Issue
Block a user