import SwiftUI import BackgroundTasks extension Color { init(hex: String) { let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) var int: UInt64 = 0 Scanner(string: hex).scanHexInt64(&int) let a, r, g, b: UInt64 switch hex.count { case 3: (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17) case 6: (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF) case 8: (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF) default: (a, r, g, b) = (255, 0, 0, 0) } self.init(.sRGB, red: Double(r)/255, green: Double(g)/255, blue: Double(b)/255, opacity: Double(a)/255) } } @main struct PulseApp: App { @StateObject private var authManager = AuthManager() init() { BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.daniil.pulsehealth.healthsync", using: nil) { task in Self.handleHealthSync(task: task as! BGProcessingTask) } } var body: some Scene { WindowGroup { Group { if authManager.isLoggedIn { MainTabView() } else { LoginView() } } .environmentObject(authManager) .onAppear { APIService.shared.authManager = authManager Self.scheduleHealthSync() } } } static func scheduleHealthSync() { let request = BGProcessingTaskRequest(identifier: "com.daniil.pulsehealth.healthsync") request.earliestBeginDate = Date(timeIntervalSinceNow: 30 * 60) // 30 минут request.requiresNetworkConnectivity = true try? BGTaskScheduler.shared.submit(request) } static func handleHealthSync(task: BGProcessingTask) { // Запланировать следующий синк scheduleHealthSync() let syncTask = Task { let service = HealthKitService() let apiKey = UserDefaults.standard.string(forKey: "healthApiKey") ?? "health-cosmo-2026" try await service.syncToServer(apiKey: apiKey) } task.expirationHandler = { syncTask.cancel() } Task { do { try await syncTask.value task.setTaskCompleted(success: true) } catch { task.setTaskCompleted(success: false) } } } } class AuthManager: ObservableObject { @Published var isLoggedIn: Bool = false @Published var token: String = "" @Published var refreshToken: String = "" @Published var userName: String = "" @Published var userId: Int = 0 @Published var healthApiKey: String = "health-cosmo-2026" init() { token = UserDefaults.standard.string(forKey: "pulseToken") ?? "" refreshToken = UserDefaults.standard.string(forKey: "pulseRefreshToken") ?? "" userName = UserDefaults.standard.string(forKey: "userName") ?? "" userId = UserDefaults.standard.integer(forKey: "userId") healthApiKey = UserDefaults.standard.string(forKey: "healthApiKey") ?? "health-cosmo-2026" isLoggedIn = !token.isEmpty } func login(token: String, refreshToken: String? = nil, user: UserInfo) { self.token = token self.refreshToken = refreshToken ?? "" self.userName = user.displayName self.userId = user.id UserDefaults.standard.set(token, forKey: "pulseToken") if let rt = refreshToken { UserDefaults.standard.set(rt, forKey: "pulseRefreshToken") } UserDefaults.standard.set(user.displayName, forKey: "userName") UserDefaults.standard.set(user.id, forKey: "userId") isLoggedIn = true } func updateTokens(accessToken: String, refreshToken: String?) { self.token = accessToken UserDefaults.standard.set(accessToken, forKey: "pulseToken") if let rt = refreshToken { self.refreshToken = rt UserDefaults.standard.set(rt, forKey: "pulseRefreshToken") } } func logout() { token = ""; refreshToken = ""; userName = ""; userId = 0 UserDefaults.standard.removeObject(forKey: "pulseToken") UserDefaults.standard.removeObject(forKey: "pulseRefreshToken") UserDefaults.standard.removeObject(forKey: "userName") UserDefaults.standard.removeObject(forKey: "userId") isLoggedIn = false } }