a native ios client for gitbay

client ios swift

https://gitbay.org

gitbay/Repos/RepoListViewModel.swift

main
gitbay-ios/gitbay/Repos/RepoListViewModel.swift history · blame · raw

69 lines · 2361 bytes

 1import Foundation
 2import Observation
 3
 4/// The `repo list` screen, paginated. A typed query searches server-side
 5/// (`repo search` covers name, description and topics across everything
 6/// reachable  including repos not in your list), with the loaded pages
 7/// filtered client-side while the search is in flight.
 8@Observable
 9@MainActor
10final class RepoListViewModel {
11
12    let list: PagedListModel<RepoSummary>
13    var searchText = "" {
14        didSet {
15            guard searchText != oldValue else { return }
16            scheduleSearch()
17        }
18    }
19    /// Server results for the current query; nil while none has landed.
20    private(set) var searchResults: [RepoSummary]?
21
22    private let client: GitbayClient
23    private var searchTask: Task<Void, Never>?
24
25    init(client: GitbayClient) {
26        self.client = client
27        list = PagedListModel(
28            client: client,
29            argv: ["repo", "list"],
30            emptyMessage: "No repositories yet. Create one over SSH: gitbay repo create <name>",
31            pageSize: 100
32        )
33    }
34
35    var state: LoadState<[RepoSummary]> { list.state }
36
37    var visibleRepos: [RepoSummary] {
38        let query = searchText.trimmingCharacters(in: .whitespaces).lowercased()
39        guard !query.isEmpty else { return list.state.value ?? [] }
40        if let searchResults {
41            return searchResults
42        }
43        // The server search has not answered yet; filter what is loaded.
44        return (list.state.value ?? []).filter {
45            $0.path.lowercased().contains(query)
46                || ($0.description ?? "").lowercased().contains(query)
47        }
48    }
49
50    func load() async {
51        await list.reload()
52    }
53
54    private func scheduleSearch() {
55        searchTask?.cancel()
56        searchResults = nil
57        let query = searchText.trimmingCharacters(in: .whitespaces)
58        guard !query.isEmpty else { return }
59        searchTask = Task { [client] in
60            // Debounce a person typing; a cancelled sleep throws.
61            guard (try? await Task.sleep(for: .milliseconds(300))) != nil else { return }
62            let results = try? await client.readList(
63                ["repo", "search", query], of: RepoSummary.self
64            )
65            guard !Task.isCancelled, let results else { return }
66            self.searchResults = results
67        }
68    }
69}