import SwiftUI import FirebaseCore import FirebaseFirestore import RevenueCat @main struct CloserApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate @StateObject private var appState = AppState() var body: some Scene { WindowGroup { ContentView() .environmentObject(appState) } } } // MARK: - App Delegate class AppDelegate: NSObject, UIApplicationDelegate { func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil ) -> Bool { FirebaseApp.configure() // Configure RevenueCat Purchases.logLevel = .debug Purchases.configure(withAPIKey: Secrets.rcApiKey) // Configure notifications NotificationService.shared.configure() return true } func application( _ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data ) { Messaging.messaging().apnsToken = deviceToken NotificationService.shared.updateFCMToken() } } // MARK: - App State @MainActor final class AppState: ObservableObject { @Published var authState: AuthState = .loading @Published var currentUser: User? @Published var currentCouple: Couple? @Published var currentPartner: User? @Published var isPremium = false private let authService = AuthService.shared private let firestore = FirestoreService.shared private var authTask: Task? private var partnerListener: ListenerRegistration? private var observedPartnerId: String? init() { observeAuthState() } func observeAuthState() { authTask = Task { for await state in authService.authStateStream() { self.authState = state if case .authenticated(let userId, _) = state { await loadUserData(userId) } else { self.currentUser = nil self.currentCouple = nil self.currentPartner = nil self.observePartner(nil) } } } } func loadUserData(_ userId: String) async { do { let user: User? = try await firestore.getDocument(at: firestore.userDocument(userId)) self.currentUser = user if let coupleId = user?.coupleId { let couple: Couple? = try await firestore.getDocument(at: firestore.coupleDocument(coupleId)) self.currentCouple = couple let partnerId = couple?.userIds.first { $0 != userId } observePartner(partnerId) } else { self.currentCouple = nil observePartner(nil) } } catch { print("Failed to load user data: \(error)") } } private func observePartner(_ partnerId: String?) { guard observedPartnerId != partnerId else { return } partnerListener?.remove() partnerListener = nil observedPartnerId = partnerId currentPartner = nil guard let partnerId else { return } partnerListener = firestore.userDocument(partnerId).addSnapshotListener { [weak self] snapshot, error in Task { @MainActor in guard let self else { return } if let error { print("Failed to observe partner profile: \(error)") return } self.currentPartner = try? snapshot?.data(as: User.self) } } } func refreshData() async { guard case .authenticated(let userId, _) = authState else { return } await loadUserData(userId) } deinit { authTask?.cancel() partnerListener?.remove() } } // MARK: - Secrets enum Secrets { /// RevenueCat API key — must be set before building static let rcApiKey: String = { guard let key = Bundle.main.object(forInfoDictionaryKey: "RC_API_KEY") as? String, !key.isEmpty, key != "$(RC_API_KEY)" else { print("⚠️ RevenueCat API key not configured. Set RC_API_KEY in Info.plist or build settings.") return "" } return key }() } // MARK: - Import for Messaging import FirebaseMessaging