Files
Gotcha/ios/Sources/RepositoryDirectoryScreen.swift
2026-08-03 17:54:07 +02:00

200 lines
6.9 KiB
Swift

import UIKit
@MainActor
final class RepositoryDirectoryViewController: RefreshingTableViewController {
private enum Mode: Int { case files, history }
private let context: AppContext
private let owner: String
private let repository: String
private let path: String
private var mode = Mode.files
private var rows: [RepositoryContentRow] = []
private var history: CommitPage?
private var currentPage: UInt32 = 0
private var loadedFiles = false
init(context: AppContext, owner: String, repository: String, path: String, name: String) {
self.context = context
self.owner = owner
self.repository = repository
self.path = path
super.init()
title = name
}
@available(*, unavailable)
required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") }
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(CommitCell.self, forCellReuseIdentifier: "commit")
navigationItem.prompt = title
let modeControl = UISegmentedControl(items: ["Files", "History"])
modeControl.selectedSegmentIndex = mode.rawValue
modeControl.addTarget(self, action: #selector(modeChanged(_:)), for: .valueChanged)
modeControl.accessibilityLabel = "Directory view"
navigationItem.titleView = modeControl
loadContent(refreshing: false)
}
override func loadContent(refreshing: Bool) {
switch mode {
case .files: loadFiles(refreshing: refreshing)
case .history: loadHistory(page: 1, refreshing: refreshing)
}
}
override func loadMoreContent() {
guard mode == .history else { return }
loadHistory(page: currentPage + 1, refreshing: false)
}
private func loadFiles(refreshing: Bool) {
resetPagination()
beginLoading(refreshing: refreshing)
loadingTask?.cancel()
loadingTask = Task {
do {
let rows = try await context.core.repositoryContents(
owner: owner,
repository: repository,
path: path
)
guard !Task.isCancelled, mode == .files else {
endLoading()
return
}
self.rows = rows
loadedFiles = true
tableView.reloadData()
updateEmptyView()
} catch {
if !Task.isCancelled { show(error: error) }
}
endLoading()
}
}
private func loadHistory(page requestedPage: UInt32, refreshing: Bool) {
if requestedPage == 1 {
resetPagination()
beginLoading(refreshing: refreshing)
}
loadingTask?.cancel()
loadingTask = Task {
do {
let history = try await context.core.commits(
owner: owner,
repository: repository,
branch: nil,
path: path,
pages: requestedPage
)
guard !Task.isCancelled, mode == .history else {
if requestedPage == 1 { endLoading() }
return
}
self.history = history
currentPage = requestedPage
finishPagination(hasMore: history.hasMore)
tableView.reloadData()
updateEmptyView()
} catch {
if !Task.isCancelled {
show(error: error)
failPagination()
}
}
if requestedPage == 1 { endLoading() }
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
mode == .files ? rows.count : history?.commits.count ?? 0
}
override func tableView(
_ tableView: UITableView,
cellForRowAt indexPath: IndexPath
) -> UITableViewCell {
switch mode {
case .files:
let cell = tableView.dequeueReusableCell(withIdentifier: "repository-content")
?? UITableViewCell(style: .subtitle, reuseIdentifier: "repository-content")
configureRepositoryContentCell(cell, row: rows[indexPath.row])
return cell
case .history:
let cell = tableView.dequeueReusableCell(withIdentifier: "commit", for: indexPath) as! CommitCell
if let history {
cell.configure(history.commits[indexPath.row], laneCount: history.laneCount)
}
return cell
}
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
mode == .history ? 86 : UITableView.automaticDimension
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
switch mode {
case .files:
showRepositoryContent(
rows[indexPath.row],
context: context,
owner: owner,
repository: repository,
navigationController: navigationController
)
case .history:
guard let commit = history?.commits[indexPath.row] else { return }
navigationController?.pushViewController(
FilesViewController(
context: context,
owner: owner,
repository: repository,
sha: commit.sha,
branch: commit.branchLabel
),
animated: true
)
}
}
@objc private func modeChanged(_ sender: UISegmentedControl) {
guard let mode = Mode(rawValue: sender.selectedSegmentIndex), mode != self.mode else { return }
loadingTask?.cancel()
endLoading()
self.mode = mode
resetPagination()
tableView.backgroundView = nil
tableView.reloadData()
switch mode {
case .files:
if loadedFiles { updateEmptyView() } else { loadFiles(refreshing: false) }
case .history:
if let history {
finishPagination(hasMore: history.hasMore)
updateEmptyView()
} else {
loadHistory(page: 1, refreshing: false)
}
}
}
private func updateEmptyView() {
switch mode {
case .files:
tableView.backgroundView = loadedFiles && rows.isEmpty
? EmptyBackgroundView(title: "Empty folder", detail: "This folder does not contain any files.")
: nil
case .history:
tableView.backgroundView = history?.commits.isEmpty == true
? EmptyBackgroundView(title: "No commits", detail: "No commits affect this folder.")
: nil
}
}
}