a native ios client for gitbay

client ios swift

https://gitbay.org

gitbay/Views/Repos/FileView.swift

116 lines · 4228 bytes

  1import SwiftUI
  2
  3struct FileView: View {
  4
  5    @Environment(\.colorScheme) private var colorScheme
  6    @State private var model: FileViewModel
  7    @State private var editing = false
  8    @State private var draft = ""
  9
 10    init(client: GitbayClient, repo: String, path: String, ref: String?) {
 11        _model = State(initialValue: FileViewModel(
 12            client: client, repoPath: repo, filePath: path, ref: ref
 13        ))
 14    }
 15
 16    var body: some View {
 17        Group {
 18            if let file = model.state.value {
 19                content(file)
 20            } else {
 21                Color.clear
 22            }
 23        }
 24        .overlay { LoadStateOverlay(state: model.state) }
 25        .navigationTitle(model.fileName)
 26        .navigationBarTitleDisplayMode(.inline)
 27        .toolbar {
 28            ToolbarItem(placement: .topBarTrailing) {
 29                if model.state.value?.binary == false {
 30                    Menu {
 31                        NavigationLink(value: RepoRoute.log(
 32                            repo: model.repoPath, ref: model.ref, path: model.filePath
 33                        )) {
 34                            Label("History", systemImage: "clock")
 35                        }
 36                        NavigationLink(value: RepoRoute.blame(
 37                            repo: model.repoPath, path: model.filePath, ref: model.ref
 38                        )) {
 39                            Label("Blame", systemImage: "person.crop.rectangle.stack")
 40                        }
 41                        if let branch = model.editableBranch {
 42                            Button {
 43                                draft = model.state.value?.content ?? ""
 44                                editing = true
 45                            } label: {
 46                                Label("Edit on \(branch)", systemImage: "pencil")
 47                            }
 48                        }
 49                    } label: {
 50                        Image(systemName: "ellipsis.circle")
 51                    }
 52                    .accessibilityIdentifier("file-actions-menu")
 53                }
 54            }
 55        }
 56        .sheet(isPresented: $editing) {
 57            if let branch = model.editableBranch {
 58                FileEditSheet(model: model, branch: branch, content: $draft) {
 59                    editing = false
 60                }
 61            }
 62        }
 63        .task { await model.load() }
 64    }
 65
 66    @ViewBuilder
 67    private func content(_ file: FileContent) -> some View {
 68        if file.binary {
 69            ContentUnavailableView {
 70                Label("Binary file", systemImage: "doc.zipper")
 71            } description: {
 72                Text("\(file.file) · \(Int64(file.size).formatted(.byteCount(style: .file)))")
 73            }
 74        } else if let text = file.content {
 75            VStack(spacing: 0) {
 76                if file.isTruncated {
 77                    GBNotice(
 78                        "Truncated — showing the first \(Int64(file.size).formatted(.byteCount(style: .file))).",
 79                        .gbWarn
 80                    )
 81                    .padding(.horizontal, 12)
 82                    .padding(.vertical, 6)
 83                }
 84                CodeScrollView(text: text, fileName: model.fileName, colorScheme: colorScheme)
 85            }
 86        }
 87    }
 88}
 89
 90/// Both-axis scrolling for code, highlighted when the language is known.
 91private struct CodeScrollView: View {
 92
 93    let text: String
 94    let fileName: String
 95    let colorScheme: ColorScheme
 96
 97    var body: some View {
 98        ScrollView([.horizontal, .vertical]) {
 99            code
100                .padding(12)
101                .frame(maxWidth: .infinity, alignment: .leading)
102        }
103        .font(.gbMono(.caption))
104    }
105
106    private var code: some View {
107        let size = UIFont.preferredFont(forTextStyle: .caption1).pointSize
108        let font = UIFont(name: "Atkinson Hyperlegible Mono", size: size)
109            ?? .monospacedSystemFont(ofSize: size, weight: .regular)
110        let highlighter = SyntaxHighlighter(colorScheme: colorScheme)
111        if let highlighted = highlighter.highlight(text, fileName: fileName, font: font) {
112            return Text(AttributedString(highlighted))
113        }
114        return Text(text)
115    }
116}