import Foundation import Observation import OrgSocialKit @Observable @MainActor final class NotificationsViewModel { var notifications: [OrgSocialNotification] = [] var isLoading = false var errorMessage: String? private let client = RelayClient() var feedURL: URL? { guard let raw = UserDefaults.standard.string(forKey: "publicFeedURL"), let url = URL(string: raw), url.scheme?.hasPrefix("http") == true else { return nil } return url } var relayURL: URL { let raw = UserDefaults.standard.string(forKey: "relayURL") ?? "https://relay.org-social.org" return URL(string: raw) ?? URL(string: "https://relay.org-social.org")! } var useRelay: Bool { UserDefaults.standard.object(forKey: "useRelay") as? Bool ?? true } func load() async { guard !isLoading else { return } guard useRelay else { errorMessage = "Notifications require a relay. Enable Use Relay in Settings." notifications = [] return } guard let feedURL else { errorMessage = "Configure your Public Feed URL in Settings." return } isLoading = true errorMessage = nil defer { isLoading = false } do { let fetched = try await client.fetchNotifications(for: feedURL, from: relayURL) // Deduplicate by ID to prevent ForEach from receiving duplicate keys, // which causes UICollectionView item-count mismatches and crashes. var seen = Set() notifications = fetched.filter { seen.insert($0.id).inserted } } catch RelayError.httpError(let code) where code == 404 { try? await client.registerFeed(feedURL, with: relayURL) errorMessage = "Your feed isn't indexed by the relay yet. It will be available in a few minutes." } catch { errorMessage = error.localizedDescription } } }