krz/hutch
an ios client for sourcehut
clone: git clone https://gitbay.org/krz/hutch.git
v2.11.0: Hutch/Views/Repositories/RepositoryDetailView.swift · raw
1import SwiftUI
2
3struct RepositoryDetailView: View {
4 var repository: RepositorySummary
5 var onDeleted: (() -> Void)?
6
7 @Environment(AppState.self) private var appState
8 @Environment(\.dismiss) private var dismiss
9 @State private var viewModel: RepositoryDetailViewModel?
10 @State private var selectedTab: RepositoryDetailViewModel.Tab = .summary
11 @State private var showSettings = false
12 @State private var displayName: String
13
14 private var canManageRepository: Bool {
15 guard let currentUser = appState.currentUser else { return false }
16 return normalizedUsername(currentUser.username) == normalizedUsername(repository.owner.canonicalName)
17 }
18
19 init(repository: RepositorySummary, onDeleted: (() -> Void)? = nil) {
20 self.repository = repository
21 self.onDeleted = onDeleted
22 self._displayName = State(initialValue: repository.name)
23 }
24
25 var body: some View {
26 if repository.service == .hg {
27 HgRepositoryDetailView(repository: repository, onDeleted: onDeleted)
28 } else {
29 Group {
30 if let viewModel {
31 detailContent(viewModel)
32 } else {
33 SRHTLoadingStateView(message: "Loading repository…")
34 }
35 }
36 .navigationTitle(displayName)
37 .navigationBarTitleDisplayMode(.inline)
38 .toolbar {
39 ToolbarItemGroup(placement: .topBarTrailing) {
40 SRHTShareButton(url: SRHTWebURL.repository(repository), target: .repository) {
41 Image(systemName: "square.and.arrow.up")
42 }
43
44 if canManageRepository {
45 Button {
46 showSettings = true
47 } label: {
48 Image(systemName: "gear")
49 }
50 }
51 }
52 }
53 .sheet(isPresented: $showSettings) {
54 RepositorySettingsView(
55 repository: repository,
56 branches: viewModel?.branches ?? [],
57 client: appState.client,
58 onRenamed: { newName in
59 displayName = newName
60 },
61 onDeleted: {
62 dismiss()
63 onDeleted?()
64 }
65 )
66 }
67 .task {
68 if viewModel == nil {
69 viewModel = RepositoryDetailViewModel(
70 repository: repository,
71 client: appState.client
72 )
73 }
74 }
75 }
76 }
77
78 @ViewBuilder
79 private func detailContent(_ viewModel: RepositoryDetailViewModel) -> some View {
80 VStack(spacing: 0) {
81 Picker("Tab", selection: $selectedTab) {
82 ForEach(RepositoryDetailViewModel.Tab.allCases, id: \.self) { tab in
83 Text(tab.rawValue).tag(tab)
84 }
85 }
86 .pickerStyle(.segmented)
87 .padding(.horizontal)
88 .padding(.vertical, 8)
89
90 Divider()
91
92 switch selectedTab {
93 case .summary:
94 ReadmeView(viewModel: viewModel)
95 case .tree:
96 FileTreeView(
97 repository: repository,
98 client: appState.client
99 )
100 case .log:
101 CommitLogView(viewModel: viewModel)
102 case .refs:
103 ReferencesListView(viewModel: viewModel)
104 case .artifacts:
105 ArtifactsView(viewModel: viewModel)
106 }
107 }
108 .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
109 .srhtErrorBanner(error: Binding(
110 get: { viewModel.error },
111 set: { viewModel.error = $0 }
112 ))
113 }
114
115 private func normalizedUsername(_ value: String) -> String {
116 let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
117 return trimmed.hasPrefix("~") ? String(trimmed.dropFirst()) : trimmed
118 }
119}