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

grain.social/grain-ios

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

@Observable
@MainActor
final class SearchViewModel {
    var galleryResults: [GrainGallery] = []
    var profileResults: [ProfileSearchResult] = []
    var locations: [LocationItem] = []
    var cameras: [CameraItem] = []
    var isSearching = false
    /// True once a search has answered for the current text, so an empty
    /// list can say "nothing matched" rather than nothing at all.
    var hasSearched = false
    var searchText = ""
    var selectedTab: SearchTab = .galleries

    private let client: XRPCClient
    /// Bumped by every search and every clear. A search that comes back to
    /// find it moved on was overtaken by a later keystroke, and its answer
    /// would be to a question nobody is asking any more.
    private var generation = 0

    enum SearchTab: String, CaseIterable {
        case galleries = "Galleries"
        case profiles = "Profiles"
    }

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

    func loadDiscovery(auth: AuthContext? = nil) async {
        do {
            let locationsResponse = try await client.getLocations(auth: auth)
            let camerasResponse = try await client.getCameras(auth: auth)
            locations = locationsResponse.locations ?? []
            cameras = camerasResponse.cameras ?? []
        } catch {}
    }

    func search(auth: AuthContext? = nil) async {
        let query = searchText.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !query.isEmpty else { return }
        generation += 1
        let mine = generation
        isSearching = true

        do {
            switch selectedTab {
            case .galleries:
                let response = try await client.searchGalleries(query: query, auth: auth)
                guard mine == generation else { return }
                galleryResults = response.items ?? []
            case .profiles:
                let response = try await client.searchProfiles(query: query, auth: auth)
                guard mine == generation else { return }
                profileResults = response.items ?? []
            }
        } catch {}
        guard mine == generation else { return }
        isSearching = false
        hasSearched = true
    }

    /// The field emptied: nothing to show, and nothing in flight counts.
    func clearResults() {
        generation += 1
        galleryResults = []
        profileResults = []
        isSearching = false
        hasSearched = false
    }
}