179 lines
6.8 KiB
Swift
179 lines
6.8 KiB
Swift
import UIKit
|
|
|
|
private final class CommitHeaderView: UIView {
|
|
private let stack = UIStackView()
|
|
private let titleLabel = UILabel()
|
|
private var descriptionView: UIView?
|
|
private let metadataStack = UIStackView()
|
|
private let bottomSeparator = separator()
|
|
|
|
override init(frame: CGRect) {
|
|
super.init(frame: frame)
|
|
backgroundColor = .systemBackground
|
|
directionalLayoutMargins = .init(top: 20, leading: 20, bottom: 20, trailing: 20)
|
|
|
|
titleLabel.font = .preferredFont(forTextStyle: .title2)
|
|
titleLabel.adjustsFontForContentSizeCategory = true
|
|
titleLabel.numberOfLines = 0
|
|
titleLabel.accessibilityTraits = .header
|
|
|
|
metadataStack.axis = .vertical
|
|
metadataStack.spacing = 10
|
|
stack.axis = .vertical
|
|
stack.spacing = 12
|
|
stack.translatesAutoresizingMaskIntoConstraints = false
|
|
stack.addArrangedSubview(titleLabel)
|
|
stack.addArrangedSubview(metadataStack)
|
|
addSubview(stack)
|
|
bottomSeparator.translatesAutoresizingMaskIntoConstraints = false
|
|
addSubview(bottomSeparator)
|
|
NSLayoutConstraint.activate([
|
|
stack.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor),
|
|
stack.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor),
|
|
stack.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor),
|
|
stack.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor),
|
|
bottomSeparator.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
bottomSeparator.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
bottomSeparator.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
])
|
|
}
|
|
|
|
@available(*, unavailable)
|
|
required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") }
|
|
|
|
func configure(_ page: CommitDetailsPage) {
|
|
titleLabel.text = page.title
|
|
descriptionView?.removeFromSuperview()
|
|
if !page.description.isEmpty {
|
|
let view = markdownView(page.description)
|
|
stack.insertArrangedSubview(view, at: 1)
|
|
descriptionView = view
|
|
}
|
|
metadataStack.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
|
for metadata in page.metadata {
|
|
let label = UILabel()
|
|
label.text = metadata.label.uppercased()
|
|
label.font = .preferredFont(forTextStyle: .caption2)
|
|
label.adjustsFontForContentSizeCategory = true
|
|
label.textColor = .secondaryLabel
|
|
|
|
let value = UILabel()
|
|
value.text = metadata.value
|
|
value.font = metadata.monospaced
|
|
? UIFontMetrics(forTextStyle: .subheadline).scaledFont(
|
|
for: .monospacedSystemFont(ofSize: 15, weight: .regular)
|
|
)
|
|
: .preferredFont(forTextStyle: .subheadline)
|
|
value.adjustsFontForContentSizeCategory = true
|
|
value.numberOfLines = 0
|
|
value.lineBreakMode = metadata.monospaced ? .byCharWrapping : .byWordWrapping
|
|
|
|
let row = UIStackView(arrangedSubviews: [label, value])
|
|
row.axis = .vertical
|
|
row.spacing = 2
|
|
row.isAccessibilityElement = true
|
|
row.accessibilityLabel = "\(metadata.label): \(metadata.value)"
|
|
metadataStack.addArrangedSubview(row)
|
|
}
|
|
}
|
|
}
|
|
@MainActor
|
|
final class FilesViewController: RefreshingTableViewController {
|
|
private let context: AppContext
|
|
private let owner: String
|
|
private let repository: String
|
|
private let sha: String
|
|
private let branch: String?
|
|
private let commitHeader = CommitHeaderView()
|
|
private var page: CommitDetailsPage?
|
|
|
|
init(context: AppContext, owner: String, repository: String, sha: String, branch: String? = nil) {
|
|
self.context = context
|
|
self.owner = owner
|
|
self.repository = repository
|
|
self.sha = sha
|
|
self.branch = branch
|
|
super.init()
|
|
title = "Changed Files"
|
|
}
|
|
|
|
@available(*, unavailable)
|
|
required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") }
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
loadContent(refreshing: false)
|
|
}
|
|
|
|
override func viewDidLayoutSubviews() {
|
|
super.viewDidLayoutSubviews()
|
|
guard let header = tableView.tableHeaderView else { return }
|
|
let size = header.systemLayoutSizeFitting(
|
|
CGSize(width: tableView.bounds.width, height: 0),
|
|
withHorizontalFittingPriority: .required,
|
|
verticalFittingPriority: .fittingSizeLevel
|
|
)
|
|
guard header.frame.height != size.height else { return }
|
|
header.frame.size.height = size.height
|
|
tableView.tableHeaderView = header
|
|
}
|
|
|
|
override func loadContent(refreshing: Bool) {
|
|
beginLoading(refreshing: refreshing)
|
|
loadingTask?.cancel()
|
|
loadingTask = Task {
|
|
do {
|
|
let page = try await context.core.commitDetails(
|
|
owner: owner,
|
|
repository: repository,
|
|
sha: sha,
|
|
branch: branch
|
|
)
|
|
self.page = page
|
|
commitHeader.configure(page)
|
|
tableView.tableHeaderView = commitHeader
|
|
tableView.reloadData()
|
|
tableView.backgroundView = page.files.isEmpty
|
|
? EmptyBackgroundView(title: "No changed files", detail: "This commit does not contain file changes.")
|
|
: nil
|
|
} catch {
|
|
if !Task.isCancelled { show(error: error) }
|
|
}
|
|
endLoading()
|
|
}
|
|
}
|
|
|
|
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
|
page?.files.count ?? 0
|
|
}
|
|
|
|
override func tableView(
|
|
_ tableView: UITableView,
|
|
cellForRowAt indexPath: IndexPath
|
|
) -> UITableViewCell {
|
|
let cell = tableView.dequeueReusableCell(withIdentifier: "file")
|
|
?? UITableViewCell(style: .subtitle, reuseIdentifier: "file")
|
|
let row = page!.files[indexPath.row]
|
|
configureTextCell(cell, title: row.path, detail: row.status)
|
|
cell.accessoryType = .disclosureIndicator
|
|
return cell
|
|
}
|
|
|
|
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
|
tableView.deselectRow(at: indexPath, animated: true)
|
|
guard let row = page?.files[indexPath.row] else { return }
|
|
navigationController?.pushViewController(
|
|
DiffViewController(
|
|
context: context,
|
|
source: .commit(
|
|
owner: owner,
|
|
repository: repository,
|
|
sha: sha,
|
|
path: row.path
|
|
)
|
|
),
|
|
animated: true
|
|
)
|
|
}
|
|
}
|