krz/hutch

an ios client for sourcehut

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

v2.14.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}