import SwiftUI struct ExploreView: View { @State private var events: [FeedEvent] = [] @State private var cursor: String? @State private var failure: String? @State private var loaded = false var body: some View { Group { if let failure, events.isEmpty { ContentUnavailableView { SwiftUI.Label("This didn't load", systemImage: "wifi.exclamationmark") } description: { Text(failure) } actions: { Button("Try again") { Task { await reload() } } } } else if !loaded { ProgressView() } else if events.isEmpty { ContentUnavailableView("Nothing yet", systemImage: "globe", description: Text("Activity on public repositories shows here.")) } else { List { Group { ForEach(events) { event in NavigationLink(value: target(event)) { EventRow(event: event) } .onAppear { if event == events.last { Task { await more() } } } } } .listRowBackground(Color.page) } .listStyle(.plain) .refreshable { await reload() } } } .navigationTitle("Explore") .task { if !loaded { await reload() } } } private func target(_ event: FeedEvent) -> Route { if let subject = event.action.subject, kind(ofURI: subject) != nil { return .thread(event.repo, uri: subject) } return .repo(event.repo) } private func reload() async { do { let feed: Feed = try await Client.shared.query("getFeed", ["limit": "40"]) events = feed.events cursor = feed.cursor failure = nil } catch is CancellationError { } catch { failure = error.localizedDescription } loaded = true } private func more() async { guard let cursor else { return } let feed: Feed? = try? await Client.shared.query("getFeed", ["limit": "40", "cursor": cursor]) guard let feed else { return } self.cursor = feed.cursor events += feed.events.filter { !events.contains($0) } } } struct EventRow: View { let event: FeedEvent var body: some View { HStack(alignment: .top, spacing: Space.s3) { Avatar(url: event.actor.avatar, name: event.actor.handle, did: event.actor.did, size: Size.Avatar.medium) VStack(alignment: .leading, spacing: Space.s1) { HStack { Text(event.repo.label).lineLimit(1).foregroundStyle(.secondary) Spacer() Text(When.short(event.createdAt)).foregroundStyle(.secondary) } .font(.subheadline) Text("\(Text(event.actor.handle).fontWeight(.medium)) \(event.action.summary)") .font(.subheadline) if let title = event.action.title { Text(title).font(.body.weight(.medium)).lineLimit(2) } } } .padding(.vertical, Space.s1) } }