// // WCManager.swift // meterios // // (c) 2025 Martin "maride" Dessauer // import SwiftUI import WatchConnectivity enum WCMeteriosMessageTypes: String { case DrinkList case BookDrink } /// **WCManager** managed the Meterios-specific communication between an iOS and an watchOS device class WCManager: NSObject, WCSessionDelegate, ObservableObject { private var accountManager: AccountManager private let session: WCSession = WCSession.default override init() { self.accountManager = AccountManager.default super.init() if (WCSession.isSupported()) { session.delegate = self session.activate() } } // Functions required by WCSessionDelegate protocol, but not of interest for Meterios #if os(iOS) func sessionDidBecomeInactive(_ session: WCSession) {} func sessionDidDeactivate(_ session: WCSession) {} #endif // os(iOS) func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: (any Error)?) {} // session(...) gets called for every incoming message through the WatchConnectivity channel // As Meterios currently only needs messages from watchOS to iOS and responses back to the watch, this function currently only implements iOS-related responses. func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) { #if os(iOS) // Prevent crashes when WCSession is received before AccountManager is ready if accountManager.CurrentAccount() == nil { print("Request encountered in WCSession, but AccountManager is not ready.") return } // Check for the different possible message types switch message["request"] as! String { case WCMeteriosMessageTypes.DrinkList.rawValue: // Request for a list of available drinks Task { let drinks = try await accountManager.CurrentAccount()!.GetClient().GetDrinks() let favouriteDrinks = drinks.filter({ FavouritesManager.default.IsFavourite($0.id) && $0.active }) let availableDrinks = drinks.filter({ !FavouritesManager.default.IsFavourite($0.id) && $0.active }) // note that drinks which are not active/available are not sent to the watch - the screen is too small to waste pixels. // Create a plist-compatible variant of the Drink object containing the required information let favouriteDrinkArray = favouriteDrinks.map { drink in try! JSONEncoder().encode(drink).base64EncodedString() } let availableDrinkArray = availableDrinks.map { drink in try! JSONEncoder().encode(drink).base64EncodedString() } replyHandler([ "favouriteDrinks": favouriteDrinkArray, "availableDrinks": availableDrinkArray ]) } case WCMeteriosMessageTypes.BookDrink.rawValue: // Book the given drink at the backend Task { do { // Book let drinkID = message["drinkID"] as! Int try await accountManager.CurrentAccount()!.GetClient().BookDrink(drinkID) replyHandler([ "salut": SalutManager.getSalute() ]) } catch { // Error booking against the backend replyHandler([ "error": error, "salut": "" ]) } } default: // Malformed message from watch replyHandler([ "error": "request contains no request", ]) } #endif // os(iOS) } // requestDrinksList requests the list of drinks available at the backend from the paired device func requestDrinksList(replyHandler: @escaping (Array, Array) -> Void, errorHandler: @escaping ((any Error)?) -> Void) { session.sendMessage([ "request": WCMeteriosMessageTypes.DrinkList.rawValue ], replyHandler: { reply in let favouriteDrinks = reply["favouriteDrinks"] as! Array let availableDrinks = reply["availableDrinks"] as! Array // Create Drink objects from response let favouriteDrinksArray = favouriteDrinks.map { drink in try! JSONDecoder().decode(Drink.self, from: Data(base64Encoded: drink)!) } let availableDrinksArray = availableDrinks.map { drink in try! JSONDecoder().decode(Drink.self, from: Data(base64Encoded: drink)!) } // enjoy your drinks, replyHandler! replyHandler(favouriteDrinksArray, availableDrinksArray) }, errorHandler: { error in errorHandler(error) }) } // bookDrink books the drink given by drinkID func bookDrink(drinkID: Int, replyHandler: @escaping (String) -> Void, errorHandler: @escaping ((any Error)?) -> Void) { session.sendMessage([ "request": WCMeteriosMessageTypes.BookDrink.rawValue, "drinkID": drinkID, ], replyHandler: { reply in replyHandler(reply["salut"] as! String) }, errorHandler: { error in errorHandler(error) }) } }