WatchUserinfoManager.swift 10.3 KB
//
//  WatchUserinfoManager.swift
//  hippo-watch Watch App
//
//  Created by shihao on 2025/7/8.
//

import Foundation
import WidgetKit
import Combine

extension WatchThemeModel{
    func cleanCache() {
        if let energeticImageURL{
            AppGroupConstants.defaults?.removeObject(forKey: energeticImageURL)
        }
        if let normalImageURL{
            AppGroupConstants.defaults?.removeObject(forKey: normalImageURL)
        }
        if let slightStressImageURL{
            AppGroupConstants.defaults?.removeObject(forKey: slightStressImageURL)
        }
        if let stressfulImageURL{
            AppGroupConstants.defaults?.removeObject(forKey: stressfulImageURL)
        }
    }
}

@MainActor
final class WatchUserinfoManager: ObservableObject {
    static let share = WatchUserinfoManager()

    private let service = UserInfoService()
    private let themeService = ThemeService()

    var isLogin: Bool{
        return token?.count ?? 0 > 0
    }

    var isMeVip: Bool? {
        vipInfo?.isAvaiableVip
    }
    var baseUrl: String{
        myUserInfo?.baseUrl ?? "https://api.doublefeel.cn"
    }

    var token: String?{
        myUserInfo?.token
    }

    private(set) var myUserInfo: UserInfoModel?

    @Published var displayFriend: HAppFriend?
    @Published var vipInfo: HVipInfo?
    @Published var showRealtimeHRV: Bool
    @Published var interactionActionType: InteractionActionType = .stick

    var myUserinfo: UserInfo? {
        nil
    }

    // 表盘主题
    @Published var myTheme: WatchThemeModel?
    @Published var otherTheme: WatchThemeModel?

    private var didSetup = false

    var displayType: HealthType{
        showRealtimeHRV ? .realtime_stress : .hrv
    }

    private init(){
        showRealtimeHRV = AppGroupConstants.defaults?.bool(
            forKey: AppGroupConstants.Key.showRealtimeHRV
        ) ?? false

        guard let data = AppGroupConstants.defaults?.data(forKey: AppGroupConstants.Key.myUserInfo) else{
            return
        }
        do{
            self.myUserInfo = try JSONDecoder().decode(UserInfoModel.self, from: data)
        }catch{}
    }

    func setup() {
        guard !didSetup else { return }
        didSetup = true

        Task { @MainActor [weak self] in
            await Task.yield()
            self?.reloadLocalFriend()
            self?.loadLocalWatchTheme()

            self?.reloadLogin()
        }
    }

    func login(userInfo: UserInfoModel){
        guard let _ = userInfo.token, let _ = userInfo.userId, let _ = userInfo.baseUrl else{
            return
        }
        self.myUserInfo = userInfo

        reloadLogin()
    }

    @MainActor
    func reloadLogin(){
        // 刷新所有数据
        if isLogin{
            reloadVip()
            reloadFriend()
        }
        if let id = self.myUserInfo?.userId {
            ThinkSDKUtil.login(id)
        }
    }
    @MainActor
    func logout(){
        myTheme?.cleanCache()
        otherTheme?.cleanCache()
        myUserInfo = nil
        displayFriend = nil
        vipInfo = nil
        showRealtimeHRV = false
        myTheme = nil
        otherTheme = nil
        AppGroupConstants.clearUserSessionData()

        ThinkSDKUtil.logout()
    }

    @MainActor
    func switchHRVType(to realtime: Bool){
        showRealtimeHRV = realtime
        AppGroupConstants.defaults?.set(showRealtimeHRV, forKey: AppGroupConstants.Key.showRealtimeHRV)
        // 刷新数据
        WatchDataManager.share.getLatestHealthData()
        WatchDataManager.share.getMyLatestData()
        if let _ = displayFriend?.friend_user_id{
            WatchDataManager.share.getFriendLatestData()
        }
    }

    func reloadTheme(){
        reloadFriend()
    }
    //MARK: - 表盘主题
    @MainActor
    private func loadLocalWatchTheme(){
        if let data = AppGroupConstants.defaults?.data(forKey: AppGroupConstants.Key.myWatchTheme),
           let model = try? JSONDecoder().decode(WatchThemeModel.self, from: data) {
            self.myTheme = model
        }else{
            self.myTheme = nil
        }

        if let data = AppGroupConstants.defaults?.data(forKey: AppGroupConstants.Key.otherWatchTheme),
           let model = try? JSONDecoder().decode(WatchThemeModel.self, from: data) {
            self.otherTheme = model
        }else{
            self.otherTheme = nil
        }
        downloadThemeImageIfNeeded()
    }

    private func loadRemoteWatchTheme() async{
            let myNewTheme = try? await themeService.getCurrentTheme(userId: nil)
        var otherNewTheme: WatchThemeModel?
        if let userId = displayFriend?.user_id{
            otherNewTheme = try? await themeService.getCurrentTheme(userId: userId)
        }
            if let myNewTheme{
                if myNewTheme != myTheme{
                    // 清除缓存
                    myTheme?.cleanCache()
                    myTheme = myNewTheme
                    do{
                        let data = try JSONEncoder().encode(myNewTheme)
                        AppGroupConstants.defaults?.set(data, forKey: AppGroupConstants.Key.myWatchTheme)
                    }catch{}
                }
            }else{
                myTheme?.cleanCache()
                myTheme = nil
                AppGroupConstants.defaults?.removeObject(forKey: AppGroupConstants.Key.myWatchTheme)
            }
            if let otherNewTheme{
                if otherNewTheme != otherTheme{
                    // 清除缓存
                    otherTheme?.cleanCache()
                    otherTheme = otherNewTheme
                    do{
                        let data = try JSONEncoder().encode(otherNewTheme)
                        AppGroupConstants.defaults?.set(data, forKey: AppGroupConstants.Key.otherWatchTheme)
                    }catch{}
                }
            }else{
                otherTheme?.cleanCache()
                otherTheme = nil
                AppGroupConstants.defaults?.removeObject(forKey: AppGroupConstants.Key.otherWatchTheme)
            }

            downloadThemeImageIfNeeded()
    }

    @MainActor
    private func downloadThemeImageIfNeeded(){
        guard let defaults = AppGroupConstants.defaults else { return }

        let imageURLs = [myTheme, otherTheme]
            .compactMap { $0 }
            .flatMap {
                [
                    $0.energeticImageURL,
                    $0.normalImageURL,
                    $0.slightStressImageURL,
                    $0.stressfulImageURL
                ]
            }
            .compactMap { $0 }

        let downloadUrls = imageURLs.filter({ defaults.data(forKey: $0) == nil })

        let downloadGroup = DispatchGroup()
        var hasDownloadTask = false

        for urlString in Set(downloadUrls) {
            guard let url = URL(string: urlString) else { continue }

            hasDownloadTask = true
            downloadGroup.enter()
            URLSession.shared.dataTask(with: url) { data, _, error in
                defer { downloadGroup.leave() }
                guard error == nil, let data else { return }
                defaults.set(data, forKey: urlString)
            }.resume()
        }

        guard hasDownloadTask else {
            WidgetCenter.shared.reloadTimelines(ofKind: HippoWidgetKind.hrv.rawValue)
            return
        }
        downloadGroup.notify(queue: .main) { [weak self] in
            // 通过published刷新了watch
            self?.objectWillChange.send()
            // 再刷新小组件
            WidgetCenter.shared.reloadTimelines(ofKind: HippoWidgetKind.hrv.rawValue)
        }
    }

    //MARK: - 我的信息
//    private func reloadUserInfo(){
//        guard isLogin else{
//            return
//        }
//        Task{
//            myUserInfo = await service.getMyUserInfo()
//            guard let myUserInfo else{
//                AppGroupConstants.defaults?.removeObject(forKey: AppGroupConstants.Key.myUserInfo)
//                return
//            }
//            do{
//                let data = try JSONEncoder().encode(myUserInfo)
//                AppGroupConstants.defaults?.set(data, forKey: AppGroupConstants.Key.myUserInfo)
//            }catch{}
//        }
//    }

    private func reloadFriend(){
        guard isLogin else{
            return
        }
        Task{
            let friendList = await service.getFriendInfo()
            let friend = friendList?.list?.first(where: { $0.show_in_dial == 1 })
            if let _ = friend?.friend_user_id{
                WatchDataManager.share.getFriendLatestData()
            }
            self.saveOtherData(displayFriend: friend)

           await loadRemoteWatchTheme()
        }
    }
    func reloadVip(){
        guard isLogin else{
            return
        }
        Task{
            let vipInfo = await service.getVipInfo()
            self.saveVip(info: vipInfo)
        }
    }


    //MARK: - OtherData
    private func reloadLocalFriend(){
        if let data = AppGroupConstants.defaults?.data(forKey: AppGroupConstants.Key.showOtherUserInfo),
           let model = try? JSONDecoder().decode(HAppFriend.self, from: data) {
            self.displayFriend = model
        }else{
            self.displayFriend = nil
        }
    }
    private func saveOtherData(displayFriend: HAppFriend?){
        self.displayFriend = displayFriend

        guard let displayFriend else{
            AppGroupConstants.defaults?.removeObject(forKey: AppGroupConstants.Key.showOtherUserInfo)
            return
        }
        do{
            let data = try JSONEncoder().encode(displayFriend)
            AppGroupConstants.defaults?.set(data, forKey: AppGroupConstants.Key.showOtherUserInfo)
        }catch{}
    }

    //MARK: - VIP
    private func reloadVipInfo(){
        if let data = AppGroupConstants.defaults?.data(forKey: AppGroupConstants.Key.vipInfo),
           let model = try? JSONDecoder().decode(HVipInfo.self, from: data) {
            self.vipInfo = model
        }else{
            self.vipInfo = nil
        }
        reloadVip()
    }
    private func saveVip(info: HVipInfo?){
        self.vipInfo = info
        if let info{
            do{
                let data = try JSONEncoder().encode(info)
                AppGroupConstants.defaults?.set(data, forKey: AppGroupConstants.Key.vipInfo)
            }catch{}
        }else{
            AppGroupConstants.defaults?.removeObject(forKey: AppGroupConstants.Key.vipInfo)
        }
    }

}