WatchSessionManager.swift 8.5 KB
//
//  WatchSessionManager.swift
//  hippo-watch Watch App
//
//  Created by shihao on 2025/7/7.
//

import Foundation
import WatchConnectivity
import Combine
import ClockKit

class WatchSessionManager: NSObject, WCSessionDelegate, ObservableObject {

    static let share = WatchSessionManager()

    override init() {
        super.init()
    }

    func addListen() {
        if WCSession.isSupported() {
            WCSession.default.delegate = self
            if WCSession.default.activationState == .notActivated {
                WCSession.default.activate()
            } else if WCSession.default.activationState == .activated {
                requestLoginStateFromPhone()
            }
        }
    }

    private func handleLoginData(_ data: Data) {
        do {
            let model = try JSONDecoder().decode(UserInfoModel.self, from: data)
            Task { @MainActor in
                applyLoginInfo(model, data: data)
            }
        } catch {
            print("decode watch login info failed: \(error.localizedDescription)")
        }
    }

    @MainActor
    private func applyLoginInfo(_ model: UserInfoModel, data: Data? = nil) {
        guard model.token?.isEmpty == false else {
            applyLogout()
            return
        }

        let storeData = data ?? (try? JSONEncoder().encode(model))
        if let storeData {
            AppGroupConstants.defaults?.set(storeData, forKey: AppGroupConstants.Key.myUserInfo)
        }

        guard model != WatchUserinfoManager.share.myUserInfo else {
            return
        }

        WatchUserinfoManager.share.login(userInfo: model)
        WatchDataManager.share.fetchMyTodayData()
        WatchDataManager.share.fetchCoupleLatestHRV()
        WatchHealthObserverUploader.shared.performFullUpload()
    }

    @MainActor
    private func applyLogout() {
        WatchUserinfoManager.share.logout()
        WatchDataManager.share.logout()
    }

    private func requestLoginStateFromPhone() {
        guard WCSession.default.isReachable else { return }
        WCSession.default.sendMessage(
            ["command": AppGroupMessageKey.requestLoginState],
            replyHandler: { [weak self] reply in
                self?.handleLoginStatePayload(reply)
            },
            errorHandler: { error in
                print("❌ Failed to request login state: \(error.localizedDescription)")
            }
        )
    }

    private func handleLoginStatePayload(_ payload: [String: Any]) {
        if let command = payload["command"] as? String,
           command == AppGroupMessageKey.logout {
            Task { @MainActor in
                self.applyLogout()
            }
            return
        }

        if let json = payload["json"] as? String,
           let data = json.data(using: .utf8) {
            handleLoginData(data)
            return
        }

        do {
            let data = try JSONSerialization.data(withJSONObject: payload)
            handleLoginData(data)
        } catch {
            print("serialize watch login state failed: \(error.localizedDescription)")
        }
    }

    private func handleCommand(command: String, json: String?, payload: [String: Any]? = nil){
        if command == AppGroupMessageKey.login{
            // 登录, 刷新所有数据
            guard let data = json?.data(using: .utf8) else{
                return
            }
            handleLoginData(data)

        }else if command == AppGroupMessageKey.reloadTheme {
            // 刷新表盘等
            Task { @MainActor in
                if let payload, let theme = WatchLocalTheme(payload: payload) {
                    WatchUserinfoManager.share.applySyncedTheme(theme)
                } else {
                    WatchUserinfoManager.share.reloadTheme()
                }
            }
        }else if command == AppGroupMessageKey.deleteTheme{
            Task { @MainActor in
                WatchUserinfoManager.share.deleteLocalTheme()
            }
        }else if command == AppGroupMessageKey.reloadAll{
            // 刷新数据
            Task { @MainActor in
                WatchUserinfoManager.share.reloadLogin()
            }
        }else if command == AppGroupMessageKey.reloadVip{
            // 刷新会员
            Task { @MainActor in
                WatchUserinfoManager.share.reloadVip()
            }
        }else if command == AppGroupMessageKey.logout{
            // 退出登录
            Task { @MainActor in
                self.applyLogout()
            }
        }else if command == AppGroupMessageKey.statusPulseRefresh{
            Task { @MainActor in
                WatchDataManager.share.fetchMyTodayData()
            }
        }
    }

    func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) {
        print("message-replyHandler: \(message)")
        if let command = message["command"] as? String {
            handleCommand(
                command: command,
                json: message["json"] as? String,
                payload: message["payload"] as? [String: Any]
            )
        }
        replyHandler(["success": true])
    }

    func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any] = [:]) {
        print("received userInfo transform: \(userInfo)")
        if let command = userInfo["command"] as? String {
            handleCommand(
                command: command,
                json: userInfo["json"] as? String,
                payload: userInfo["payload"] as? [String: Any]
            )
        }
    }

    func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
        print("message: \(message)")
        if let command = message["command"] as? String {
            handleCommand(
                command: command,
                json: message["json"] as? String,
                payload: message["payload"] as? [String: Any]
            )
        }
    }

    func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) {
        print("received applicationContext: \(applicationContext)")
        if let command = applicationContext["command"] as? String {
            if command != AppGroupMessageKey.login || applicationContext["json"] != nil {
                handleCommand(
                    command: command,
                    json: applicationContext["json"] as? String,
                    payload: applicationContext["payload"] as? [String: Any]
                )
                return
            }
        }
        do{
            let data = try JSONSerialization.data(withJSONObject: applicationContext)
            let model = try JSONDecoder().decode(UserInfoModel.self, from: data)
            Task { @MainActor in
                self.applyLoginInfo(model, data: data)
            }
        }catch{
            print("handle applicationContext failed: \(error.localizedDescription)")
        }
    }

    func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: (any Error)?) {
        print("Activation state: \(activationState.rawValue)")
        if let error = error {
            print("Activation error: \(error.localizedDescription)")
        } else if activationState == .activated {
            requestLoginStateFromPhone()
        }
    }
    #if os(iOS)
    func sessionDidBecomeInactive(_ session: WCSession) {

    }
    func sessionDidDeactivate(_ session: WCSession) {

            WCSession.default.activate()

        }
    #endif
}


extension WatchSessionManager {
    func notifyPhoneToRefreshPulse() {
        if WCSession.default.isReachable {
            WCSession.default.sendMessage(["command": AppGroupMessageKey.statusPulseRefresh],
                                          replyHandler: nil,
                                          errorHandler: { error in
                print("❌ Failed to send refresh message: \(error)")
            })
        } else {
            print("📵 iPhone not reachable")
        }
    }

    func sendWatchDeviceToken(token: String){
        if WCSession.default.isReachable {
            WCSession.default.sendMessage(["command": AppGroupMessageKey.watchDeviceToken,
                                           "token": token
                                          ],
                                          replyHandler: nil,
                                          errorHandler: { error in
                print("❌ Failed to send token message: \(error)")
            })
        } else {
            print("📵 iPhone not reachable")
        }
    }
}