krz/hutch
an ios client for sourcehut
clone: git clone https://gitbay.org/krz/hutch.git
v3.10.0: Hutch/Views/Pastes/PasteDetailViewModel.swift · raw
1import Foundation
2
3@Observable
4@MainActor
5final class PasteDetailViewModel {
6 private(set) var paste: Paste?
7 private(set) var isLoading = false
8 private(set) var isUpdatingVisibility = false
9 private(set) var isDeleting = false
10 private(set) var loadingFileHashes: Set<String> = []
11 var error: String?
12
13 var selectedFileHash: String?
14 private(set) var fileContents: [String: String] = [:]
15
16 private let pasteID: String
17 private let service: PasteService
18
19 init(pasteID: String, initialPaste: Paste? = nil, service: PasteService) {
20 self.pasteID = pasteID
21 self.paste = initialPaste
22 self.service = service
23 self.selectedFileHash = initialPaste?.files.first?.hash
24 }
25
26 var selectedFile: PasteFile? {
27 let hash = selectedFileHash ?? paste?.files.first?.hash
28 return paste?.files.first(where: { $0.hash == hash })
29 }
30
31 var selectedFileContents: String? {
32 guard let selectedFile else { return nil }
33 return fileContents[selectedFile.hash]
34 }
35
36 func loadPaste() async {
37 guard !isLoading else { return }
38 isLoading = true
39 error = nil
40 defer { isLoading = false }
41
42 do {
43 let loaded = try await service.loadPaste(id: pasteID)
44 paste = loaded
45 if selectedFileHash == nil {
46 selectedFileHash = loaded?.files.first?.hash
47 }
48 await loadSelectedFileContentsIfNeeded()
49 } catch {
50 self.error = error.userFacingMessage
51 }
52 }
53
54 func selectFile(hash: String) {
55 selectedFileHash = hash
56 Task {
57 await loadSelectedFileContentsIfNeeded()
58 }
59 }
60
61 func updateVisibility(_ visibility: Visibility) async -> Paste? {
62 guard !isUpdatingVisibility else { return nil }
63 guard let paste else { return nil }
64 guard paste.visibility != visibility else { return paste }
65
66 isUpdatingVisibility = true
67 error = nil
68 defer { isUpdatingVisibility = false }
69
70 do {
71 let updatedPaste = try await service.updateVisibility(id: paste.id, visibility: visibility)
72 if let updatedPaste {
73 self.paste = updatedPaste
74 if selectedFileHash == nil {
75 selectedFileHash = updatedPaste.files.first?.hash
76 }
77 }
78 return updatedPaste
79 } catch {
80 self.error = error.userFacingMessage
81 return nil
82 }
83 }
84
85 func deletePaste() async -> Bool {
86 guard !isDeleting else { return false }
87 isDeleting = true
88 error = nil
89 defer { isDeleting = false }
90
91 do {
92 _ = try await service.deletePaste(id: pasteID)
93 return true
94 } catch {
95 self.error = error.userFacingMessage
96 return false
97 }
98 }
99
100 func loadSelectedFileContentsIfNeeded() async {
101 guard let file = selectedFile, fileContents[file.hash] == nil else { return }
102 guard let url = file.contents else { return }
103 guard !loadingFileHashes.contains(file.hash) else { return }
104
105 loadingFileHashes.insert(file.hash)
106 defer { loadingFileHashes.remove(file.hash) }
107
108 do {
109 fileContents[file.hash] = try await service.loadContents(from: url)
110 } catch {
111 self.error = error.userFacingMessage
112 }
113 }
114}