~ knotless Search/
↑↓ move↵ open⇧↵ all results

grain.social/grain-ios

1 collaborator
0
1
main
69 lines · 2.4 KB Raw History
import Foundation

/// What the search tab shows before anything is typed: the recent feed as a
/// grid, headed by chips for the most-used cameras and places.
///
/// The chips are kept apart from the galleries because they arrive on their
/// own requests and fail on their own: a camera index that is down should
/// not take the recent galleries with it, and the other way round.
@Observable
@MainActor
final class ExploreViewModel {
    var cameras: [CameraItem] = []
    var locations: [LocationItem] = []
    var galleries: [GrainGallery] = []
    var isLoading = true
    var isLoadingMore = false

    private let client: XRPCClient
    private var cursor: String?
    private var loaded = false

    init(client: XRPCClient) {
        self.client = client
    }

    /// The first load, once. The tab is composed each time it is shown, and
    /// a second look should not blank what was already there.
    func loadIfNeeded(auth: AuthContext? = nil) async {
        guard !loaded else { return }
        loaded = true
        await load(auth: auth)
    }

    func refresh(auth: AuthContext? = nil) async {
        await load(auth: auth)
    }

    private func load(auth: AuthContext?) async {
        // Three requests, none waiting on another. Each keeps what it had on
        // failure: a chip row that empties because one refresh timed out
        // would read as the cameras having gone.
        async let camerasReq = client.getCameras(auth: auth)
        async let locationsReq = client.getLocations(auth: auth)
        async let feedReq = client.getFeed(feed: "recent", auth: auth)

        if let response = try? await camerasReq {
            cameras = response.cameras ?? []
        }
        if let response = try? await locationsReq {
            locations = response.locations ?? []
        }
        if let response = try? await feedReq {
            galleries = response.items ?? []
            cursor = response.cursor
        }
        isLoading = false
    }

    func loadMore(auth: AuthContext? = nil) async {
        guard let cursor, !isLoadingMore, !isLoading else { return }
        isLoadingMore = true
        do {
            let response = try await client.getFeed(feed: "recent", cursor: cursor, auth: auth)
            galleries.append(contentsOf: response.items ?? [])
            self.cursor = response.cursor
        } catch {}
        isLoadingMore = false
    }
}