Show milestone and pull request states
This commit is contained in:
@@ -518,6 +518,7 @@ final class MilestoneViewController: RefreshingTableViewController {
|
||||
super.viewDidLoad()
|
||||
tableView.register(MilestoneCell.self, forCellReuseIdentifier: "milestone")
|
||||
tableView.register(IssueCell.self, forCellReuseIdentifier: "issue")
|
||||
tableView.register(PullCell.self, forCellReuseIdentifier: "pull")
|
||||
tableView.rowHeight = UITableView.automaticDimension
|
||||
tableView.estimatedRowHeight = 118
|
||||
let editButton = UIBarButtonItem(
|
||||
@@ -633,11 +634,8 @@ final class MilestoneViewController: RefreshingTableViewController {
|
||||
cell.configure(page.issues[indexPath.row])
|
||||
return cell
|
||||
}
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "pull")
|
||||
?? UITableViewCell(style: .subtitle, reuseIdentifier: "pull")
|
||||
let pull = page.pulls[indexPath.row]
|
||||
configureTextCell(cell, title: pull.title, detail: "\(pull.summary)\n\(pull.meta)")
|
||||
cell.accessoryType = .disclosureIndicator
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "pull", for: indexPath) as! PullCell
|
||||
cell.configure(page.pulls[indexPath.row])
|
||||
return cell
|
||||
}
|
||||
|
||||
@@ -668,6 +666,7 @@ final class MilestoneViewController: RefreshingTableViewController {
|
||||
}
|
||||
|
||||
final class MilestoneCell: UITableViewCell {
|
||||
private let stateIcon = UIImageView()
|
||||
private let titleLabel = UILabel()
|
||||
private let descriptionLabel = UILabel()
|
||||
private let metaLabel = UILabel()
|
||||
@@ -677,6 +676,9 @@ final class MilestoneCell: UITableViewCell {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
titleLabel.font = .preferredFont(forTextStyle: .headline)
|
||||
titleLabel.numberOfLines = 2
|
||||
let titleStack = UIStackView(arrangedSubviews: [stateIcon, titleLabel])
|
||||
titleStack.alignment = .firstBaseline
|
||||
titleStack.spacing = 8
|
||||
descriptionLabel.font = .preferredFont(forTextStyle: .subheadline)
|
||||
descriptionLabel.textColor = .secondaryLabel
|
||||
descriptionLabel.numberOfLines = 2
|
||||
@@ -684,7 +686,9 @@ final class MilestoneCell: UITableViewCell {
|
||||
metaLabel.textColor = .tertiaryLabel
|
||||
metaLabel.numberOfLines = 2
|
||||
progress.progressTintColor = .systemGreen
|
||||
let stack = UIStackView(arrangedSubviews: [titleLabel, descriptionLabel, progress, metaLabel])
|
||||
let stack = UIStackView(
|
||||
arrangedSubviews: [titleStack, descriptionLabel, progress, metaLabel]
|
||||
)
|
||||
stack.axis = .vertical
|
||||
stack.spacing = 7
|
||||
stack.translatesAutoresizingMaskIntoConstraints = false
|
||||
@@ -702,6 +706,12 @@ final class MilestoneCell: UITableViewCell {
|
||||
|
||||
func configure(_ row: MilestoneRow, disclosure: Bool) {
|
||||
accessoryType = disclosure ? .disclosureIndicator : .none
|
||||
configureOpenClosedStateIcon(
|
||||
stateIcon,
|
||||
state: row.state,
|
||||
subject: "milestone",
|
||||
textStyle: .headline
|
||||
)
|
||||
titleLabel.text = row.title
|
||||
descriptionLabel.text = row.description
|
||||
metaLabel.text = row.meta
|
||||
@@ -712,6 +722,58 @@ final class MilestoneCell: UITableViewCell {
|
||||
}
|
||||
}
|
||||
|
||||
final class PullCell: UITableViewCell {
|
||||
private let stateIcon = UIImageView()
|
||||
private let titleLabel = UILabel()
|
||||
private let summaryLabel = UILabel()
|
||||
private let metaLabel = UILabel()
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
accessoryType = .disclosureIndicator
|
||||
titleLabel.font = .preferredFont(forTextStyle: .headline)
|
||||
titleLabel.numberOfLines = 2
|
||||
let titleStack = UIStackView(arrangedSubviews: [stateIcon, titleLabel])
|
||||
titleStack.alignment = .firstBaseline
|
||||
titleStack.spacing = 8
|
||||
summaryLabel.font = .preferredFont(forTextStyle: .subheadline)
|
||||
summaryLabel.textColor = .secondaryLabel
|
||||
summaryLabel.numberOfLines = 2
|
||||
metaLabel.font = .preferredFont(forTextStyle: .caption1)
|
||||
metaLabel.textColor = .tertiaryLabel
|
||||
metaLabel.numberOfLines = 2
|
||||
[titleLabel, summaryLabel, metaLabel].forEach {
|
||||
$0.adjustsFontForContentSizeCategory = true
|
||||
}
|
||||
let stack = UIStackView(arrangedSubviews: [titleStack, summaryLabel, metaLabel])
|
||||
stack.axis = .vertical
|
||||
stack.spacing = 6
|
||||
stack.translatesAutoresizingMaskIntoConstraints = false
|
||||
contentView.addSubview(stack)
|
||||
NSLayoutConstraint.activate([
|
||||
stack.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
|
||||
stack.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8),
|
||||
stack.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10),
|
||||
stack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10),
|
||||
])
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") }
|
||||
|
||||
func configure(_ row: PullRow) {
|
||||
configureOpenClosedStateIcon(
|
||||
stateIcon,
|
||||
state: row.state,
|
||||
subject: "pull request",
|
||||
textStyle: .headline
|
||||
)
|
||||
titleLabel.text = row.title
|
||||
summaryLabel.text = row.summary
|
||||
metaLabel.text = row.meta
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
final class PullsViewController: RefreshingTableViewController {
|
||||
private let context: AppContext
|
||||
@@ -733,6 +795,9 @@ final class PullsViewController: RefreshingTableViewController {
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
tableView.register(PullCell.self, forCellReuseIdentifier: "pull")
|
||||
tableView.rowHeight = UITableView.automaticDimension
|
||||
tableView.estimatedRowHeight = 116
|
||||
navigationItem.leftBarButtonItem = UIBarButtonItem(
|
||||
image: context.symbol("server.rack"),
|
||||
primaryAction: UIAction { [weak self] _ in
|
||||
@@ -821,22 +886,11 @@ final class PullsViewController: RefreshingTableViewController {
|
||||
_ tableView: UITableView,
|
||||
cellForRowAt indexPath: IndexPath
|
||||
) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "pull")
|
||||
?? UITableViewCell(style: .subtitle, reuseIdentifier: "pull")
|
||||
let row = rows[indexPath.row]
|
||||
configureTextCell(
|
||||
cell,
|
||||
title: row.title,
|
||||
detail: "\(row.summary)\n\(row.meta)"
|
||||
)
|
||||
cell.accessoryType = .disclosureIndicator
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "pull", for: indexPath) as! PullCell
|
||||
cell.configure(rows[indexPath.row])
|
||||
return cell
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
|
||||
116
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
let row = rows[indexPath.row]
|
||||
|
||||
@@ -227,7 +227,8 @@ final class IssueViewController: MarkdownPageViewController {
|
||||
if let page {
|
||||
replaceContent(detailViews(
|
||||
title: page.title,
|
||||
issueState: page.state,
|
||||
state: page.state,
|
||||
stateSubject: "issue",
|
||||
meta: page.meta,
|
||||
body: page.body,
|
||||
comments: page.comments,
|
||||
@@ -305,6 +306,8 @@ final class PullViewController: MarkdownPageViewController {
|
||||
if let page {
|
||||
var views = detailViews(
|
||||
title: page.title,
|
||||
state: page.state,
|
||||
stateSubject: "pull request",
|
||||
meta: page.meta,
|
||||
body: page.body,
|
||||
comments: []
|
||||
@@ -1138,7 +1141,8 @@ final class DiffViewController: UIViewController {
|
||||
|
||||
private func detailViews(
|
||||
title: String,
|
||||
issueState: String? = nil,
|
||||
state: String? = nil,
|
||||
stateSubject: String = "",
|
||||
meta: String,
|
||||
body: String,
|
||||
comments: [CommentRow],
|
||||
@@ -1156,7 +1160,10 @@ private func detailViews(
|
||||
metaLabel.textColor = .secondaryLabel
|
||||
metaLabel.numberOfLines = 0
|
||||
let bodyView = markdownView(body)
|
||||
var views: [UIView] = [issueState.map { issueTitle(titleLabel, state: $0) } ?? titleLabel, metaLabel]
|
||||
var views: [UIView] = [
|
||||
state.map { stateTitle(titleLabel, state: $0, subject: stateSubject) } ?? titleLabel,
|
||||
metaLabel,
|
||||
]
|
||||
if !milestone.isEmpty {
|
||||
let milestoneLabel = UILabel()
|
||||
milestoneLabel.attributedText = symbolText(
|
||||
@@ -1172,9 +1179,9 @@ private func detailViews(
|
||||
return views + [separator(), bodyView] + commentViews(comments, editComment: editComment)
|
||||
}
|
||||
|
||||
private func issueTitle(_ titleLabel: UILabel, state: String) -> UIView {
|
||||
private func stateTitle(_ titleLabel: UILabel, state: String, subject: String) -> UIView {
|
||||
let icon = UIImageView()
|
||||
configureIssueStateIcon(icon, state: state, textStyle: .title1)
|
||||
configureOpenClosedStateIcon(icon, state: state, subject: subject, textStyle: .title1)
|
||||
let stack = UIStackView(arrangedSubviews: [icon, titleLabel])
|
||||
stack.axis = .horizontal
|
||||
stack.alignment = .firstBaseline
|
||||
|
||||
@@ -267,17 +267,32 @@ func configureIssueStateIcon(
|
||||
_ icon: UIImageView,
|
||||
state: String,
|
||||
textStyle: UIFont.TextStyle
|
||||
) {
|
||||
configureOpenClosedStateIcon(icon, state: state, subject: "issue", textStyle: textStyle)
|
||||
}
|
||||
|
||||
func configureOpenClosedStateIcon(
|
||||
_ icon: UIImageView,
|
||||
state: String,
|
||||
subject: String,
|
||||
textStyle: UIFont.TextStyle
|
||||
) {
|
||||
let symbol: String
|
||||
let color: UIColor
|
||||
let accessibilityLabel: String
|
||||
switch state {
|
||||
case "open":
|
||||
(symbol, color, accessibilityLabel) = ("exclamationmark.circle.fill", .systemGreen, "Open issue")
|
||||
(symbol, color, accessibilityLabel) = (
|
||||
"exclamationmark.circle.fill", .systemGreen, "Open \(subject)"
|
||||
)
|
||||
case "closed":
|
||||
(symbol, color, accessibilityLabel) = ("checkmark.circle.fill", .systemPurple, "Closed issue")
|
||||
(symbol, color, accessibilityLabel) = (
|
||||
"checkmark.circle.fill", .systemPurple, "Closed \(subject)"
|
||||
)
|
||||
default:
|
||||
(symbol, color, accessibilityLabel) = ("questionmark.circle.fill", .systemGray, "Unknown issue state")
|
||||
(symbol, color, accessibilityLabel) = (
|
||||
"questionmark.circle.fill", .systemGray, "Unknown \(subject) state"
|
||||
)
|
||||
}
|
||||
icon.image = UIImage(systemName: symbol)
|
||||
icon.tintColor = color
|
||||
|
||||
Reference in New Issue
Block a user