krz/hutch

an ios client for sourcehut

clone: git clone https://gitbay.org/krz/hutch.git

v2.1: Hutch/Views/Pastes/PasteListViewModel.swift · raw

  1import Foundation
  2
  3@Observable
  4@MainActor
  5final class PasteListViewModel {
  6    private(set) var pastes: [Paste] = []
  7    private(set) var isLoading = false
  8    private(set) var isLoadingMore = false
  9    private(set) var isRefreshing = false
 10    private(set) var isCreatingPaste = false
 11    var error: String?
 12
 13    private var cursor: String?
 14    private var hasMore = true
 15    private let service: PasteService
 16
 17    init(service: PasteService) {
 18        self.service = service
 19    }
 20
 21    func loadPastes() async {
 22        if pastes.isEmpty, let cached = service.loadCachedPastes() {
 23            pastes = cached.results
 24            cursor = cached.cursor
 25            hasMore = cached.cursor != nil
 26        }
 27
 28        if pastes.isEmpty {
 29            isLoading = true
 30        } else {
 31            isRefreshing = true
 32        }
 33        error = nil
 34        cursor = nil
 35        hasMore = true
 36
 37        do {
 38            let page = try await service.listPastes(cursor: nil, useCache: true)
 39            pastes = page.results
 40            cursor = page.cursor
 41            hasMore = page.cursor != nil
 42        } catch {
 43            if pastes.isEmpty {
 44                self.error = error.userFacingMessage
 45            }
 46        }
 47
 48        isLoading = false
 49        isRefreshing = false
 50    }
 51
 52    func loadMoreIfNeeded(currentItem: Paste) async {
 53        guard let last = pastes.last,
 54              last.id == currentItem.id,
 55              hasMore,
 56              !isLoadingMore else {
 57            return
 58        }
 59
 60        isLoadingMore = true
 61        defer { isLoadingMore = false }
 62
 63        do {
 64            let page = try await service.listPastes(cursor: cursor, useCache: false)
 65            pastes.append(contentsOf: page.results)
 66            cursor = page.cursor
 67            hasMore = page.cursor != nil
 68        } catch {
 69            self.error = error.userFacingMessage
 70        }
 71    }
 72
 73    func createPaste(files: [PasteUploadDraft], visibility: Visibility) async -> Paste? {
 74        guard !isCreatingPaste else { return nil }
 75
 76        let normalizedFiles = files.filter {
 77            !$0.contents.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
 78        }
 79        guard !normalizedFiles.isEmpty else {
 80            error = "Add at least one file with text content."
 81            return nil
 82        }
 83
 84        isCreatingPaste = true
 85        error = nil
 86        defer { isCreatingPaste = false }
 87
 88        do {
 89            let paste = try await service.createPaste(files: normalizedFiles, visibility: visibility)
 90            upsertPaste(paste)
 91            return paste
 92        } catch {
 93            self.error = error.userFacingMessage
 94            return nil
 95        }
 96    }
 97
 98    func upsertPaste(_ paste: Paste) {
 99        if let index = pastes.firstIndex(where: { $0.id == paste.id }) {
100            pastes[index] = paste
101        } else {
102            pastes.insert(paste, at: 0)
103        }
104    }
105
106    func removePaste(id: String) {
107        pastes.removeAll { $0.id == id }
108    }
109}