BusinessCard/BusinessCardWatch Watch App/Services/WatchConnectivityService.swift

128 lines
4.5 KiB
Swift

import Foundation
import WatchConnectivity
/// Manages WatchConnectivity session and receives card data from iPhone
@MainActor
final class WatchConnectivityService: NSObject {
static let shared = WatchConnectivityService()
/// Callback when cards are received from iPhone
var onCardsReceived: (([WatchCard]) -> Void)?
private var session: WCSession?
private override init() {
super.init()
if WCSession.isSupported() {
session = WCSession.default
session?.delegate = self
session?.activate()
WatchDesign.debugLog("WatchConnectivity: Session activating...")
} else {
WatchDesign.debugLog("WatchConnectivity: Not supported")
}
}
/// Check for any existing application context on launch
func checkForExistingContext() {
guard let session = session else { return }
let context = session.receivedApplicationContext
if !context.isEmpty {
WatchDesign.debugLog("WatchConnectivity: Found existing context on launch")
processReceivedContext(context)
} else {
WatchDesign.debugLog("WatchConnectivity: No existing context found")
}
}
private func processReceivedContext(_ context: [String: Any]) {
guard let cardsData = context["cards"] as? Data else {
WatchDesign.debugLog("WatchConnectivity: No cards data in context")
return
}
WatchDesign.debugLog("WatchConnectivity: Received \(cardsData.count) bytes")
do {
let syncableCards = try JSONDecoder().decode([SyncableCard].self, from: cardsData)
let watchCards = syncableCards.map { syncable in
WatchCard(
id: syncable.id,
fullName: syncable.fullName,
role: syncable.role,
company: syncable.company,
email: syncable.email,
phone: syncable.phone,
website: syncable.website,
location: syncable.location,
isDefault: syncable.isDefault,
qrCodeImageData: syncable.qrCodeImageData,
appClipQRCodeImageData: syncable.appClipQRCodeImageData
)
}
WatchDesign.debugLog("WatchConnectivity: Decoded \(watchCards.count) cards")
onCardsReceived?(watchCards)
} catch {
WatchDesign.debugLog("WatchConnectivity: ERROR - Failed to decode: \(error)")
}
}
}
// MARK: - WCSessionDelegate
extension WatchConnectivityService: WCSessionDelegate {
nonisolated func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
Task { @MainActor in
if let error = error {
WatchDesign.debugLog("WatchConnectivity: Activation failed: \(error)")
} else {
WatchDesign.debugLog("WatchConnectivity: Activated with state: \(activationState.rawValue)")
// Check for existing context after activation
checkForExistingContext()
}
}
}
nonisolated func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String: Any]) {
Task { @MainActor in
WatchDesign.debugLog("WatchConnectivity: Received application context update")
processReceivedContext(applicationContext)
}
}
nonisolated func session(_ session: WCSession, didReceiveUserInfo userInfo: [String: Any] = [:]) {
Task { @MainActor in
WatchDesign.debugLog("WatchConnectivity: Received userInfo transfer")
processReceivedContext(userInfo)
}
}
nonisolated func session(_ session: WCSession, didReceiveMessage message: [String: Any]) {
Task { @MainActor in
WatchDesign.debugLog("WatchConnectivity: Received message")
processReceivedContext(message)
}
}
}
/// Syncable card structure matching iOS side (for decoding)
private struct SyncableCard: Codable, Identifiable {
let id: UUID
var fullName: String
var role: String
var company: String
var email: String
var phone: String
var website: String
var location: String
var isDefault: Bool
var pronouns: String
var bio: String
var linkedIn: String
var twitter: String
var instagram: String
var qrCodeImageData: Data?
var appClipQRCodeImageData: Data?
}