import Foundation import Observation import OrgSocialKit @Observable @MainActor final class GroupsViewModel { struct GroupItem: Identifiable, Hashable { let name: String let slug: String var id: String { slug } } var groups: [GroupItem] = [] var selectedGroup: GroupItem? var posts: [OrgSocialPost] = [] var isLoadingGroups = false var isLoadingPosts = false var groupsError: String? var postsError: String? private var relayURL: URL? { guard let raw = UserDefaults.standard.string(forKey: "relayURL"), let url = URL(string: raw) else { return nil } return url } var useRelay: Bool { UserDefaults.standard.object(forKey: "useRelay") as? Bool ?? true } func loadGroups() async { guard useRelay else { groupsError = "Groups require a relay. Enable Use Relay in Settings." groups = [] return } guard let relay = relayURL else { groupsError = "No relay URL configured." return } isLoadingGroups = true groupsError = nil defer { isLoadingGroups = false } do { let raw = try await RelayClient().fetchGroupList(from: relay) groups = raw.map { GroupItem(name: $0.name, slug: $0.slug) } } catch { groupsError = error.localizedDescription } } func loadPosts(for group: GroupItem) async { guard let relay = relayURL else { return } selectedGroup = group isLoadingPosts = true postsError = nil posts = [] defer { isLoadingPosts = false } do { let postURLs = try await RelayClient().fetchGroupPostURLs(slug: group.slug, from: relay) posts = try await resolvePosts(from: postURLs) } catch { postsError = error.localizedDescription } } private func resolvePosts(from postURLs: [String]) async throws -> [OrgSocialPost] { // Group post URLs by feed URL so we fetch each feed only once var byFeed: [String: [String]] = [:] for postURL in postURLs { guard let hashIdx = postURL.lastIndex(of: "#") else { continue } let feedStr = String(postURL[.. $1.date } } }