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 } } }