Files
meterios/common/WCManager.swift
T

123 lines
5.3 KiB
Swift

//
// 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 let session: WCSession = WCSession.default
override init() {
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.default.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.default.CurrentAccount()!.GetDrinks()
let favouriteDrinks = drinks.filter({ AccountManager.default.CurrentAccount()!.IsFavourite($0.id) && $0.active })
let availableDrinks = drinks.filter({ !AccountManager.default.CurrentAccount()!.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.default.CurrentAccount()!.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<MeteClientDrink>, Array<MeteClientDrink>) -> Void, errorHandler: @escaping ((any Error)?) -> Void) {
session.sendMessage([
"request": WCMeteriosMessageTypes.DrinkList.rawValue
], replyHandler: { reply in
let favouriteDrinks = reply["favouriteDrinks"] as! Array<String>
let availableDrinks = reply["availableDrinks"] as! Array<String>
// Create Drink objects from response
let favouriteDrinksArray = favouriteDrinks.map { drink in try! JSONDecoder().decode(MeteClientDrink.self, from: Data(base64Encoded: drink)!) }
let availableDrinksArray = availableDrinks.map { drink in try! JSONDecoder().decode(MeteClientDrink.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)
})
}
}