Show milestone and pull request states

This commit is contained in:
Georg Bauer
2026-07-31 18:39:10 +02:00
parent acf2c839ad
commit 128be97e20
6 changed files with 148 additions and 33 deletions

View File

@@ -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]

View File

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

View File

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