krz/hutch
an ios client for sourcehut
clone: git clone https://gitbay.org/krz/hutch.git
v3.2.1: Hutch/Views/Repositories/LinkedFileSheetView.swift · raw
1import SwiftUI
2
3/// A sheet that fetches and displays a single repository file by path,
4/// using the `repository.path()` GraphQL field — one API call, no tree traversal.
5struct LinkedFileSheetView: View {
6 let rid: String
7 let service: SRHTService
8 let client: SRHTClient
9 let request: LinkedFileRequest
10
11 @AppStorage(AppStorageKeys.wrapRepositoryFileLines) private var wrapLines = false
12 @Environment(\.dismiss) private var dismiss
13
14 @State private var entry: TreeEntry?
15 @State private var isLoading = true
16 @State private var error: String?
17
18 var body: some View {
19 NavigationStack {
20 Group {
21 if isLoading {
22 SRHTLoadingStateView(message: "Loading \(request.fileName)…")
23 } else if let error {
24 SRHTErrorStateView(
25 title: "Couldn't Load File",
26 message: error,
27 retryAction: { await load() }
28 )
29 } else if let entry, let object = entry.object {
30 fileContentView(entry: entry, object: object)
31 } else {
32 ContentUnavailableView(
33 "File Not Found",
34 systemImage: "doc.questionmark",
35 description: Text("\(request.path) could not be found in this repository.")
36 )
37 }
38 }
39 .navigationTitle(request.fileName)
40 .navigationBarTitleDisplayMode(.inline)
41 .toolbar {
42 ToolbarItem(placement: .topBarTrailing) {
43 Button("Done") { dismiss() }
44 }
45 }
46 }
47 .task { await load() }
48 }
49
50 private func load() async {
51 isLoading = true
52 error = nil
53 let vm = FileTreeViewModel(repositoryRid: rid, service: service, client: client)
54 do {
55 entry = try await vm.fetchLinkedFile(path: request.path, revspec: request.revspec)
56 } catch {
57 self.error = error.userFacingMessage
58 }
59 isLoading = false
60 }
61
62 @ViewBuilder
63 private func fileContentView(entry: TreeEntry, object: GitObject) -> some View {
64 switch object {
65 case .textBlob(let blob):
66 CodeFileTextView(
67 text: blob.text ?? "",
68 fileName: entry.name,
69 wrapLines: wrapLines
70 )
71 case .binaryBlob:
72 ContentUnavailableView(
73 "Binary File",
74 systemImage: "doc.zipper",
75 description: Text("Binary files cannot be displayed inline.")
76 )
77 default:
78 ContentUnavailableView(
79 "Unknown File",
80 systemImage: "questionmark.folder",
81 description: Text("This object type cannot be displayed.")
82 )
83 }
84 }
85}