123 lines
5.3 KiB
Swift
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)
|
|
})
|
|
}
|
|
}
|