200 lines
6.9 KiB
Swift
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
|
|
}
|
|
}
|
|
}
|