Close, reopen, and delete issues on iPhone
This commit is contained in:
@@ -26,6 +26,7 @@ final class IssuesViewController: RefreshingTableViewController {
|
||||
private var rows: [IssueRow] = []
|
||||
private var filterOptions: IssueFilterOptions?
|
||||
private var filterTask: Task<Void, Never>?
|
||||
private var mutationTask: Task<Void, Never>?
|
||||
private var currentPage: UInt32 = 0
|
||||
private lazy var filterButton = UIBarButtonItem(
|
||||
image: context.symbol("line.3.horizontal.decrease.circle")
|
||||
@@ -42,7 +43,10 @@ final class IssuesViewController: RefreshingTableViewController {
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") }
|
||||
|
||||
deinit { filterTask?.cancel() }
|
||||
deinit {
|
||||
filterTask?.cancel()
|
||||
mutationTask?.cancel()
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
@@ -143,6 +147,94 @@ final class IssuesViewController: RefreshingTableViewController {
|
||||
)
|
||||
}
|
||||
|
||||
override func tableView(
|
||||
_ tableView: UITableView,
|
||||
trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath
|
||||
) -> UISwipeActionsConfiguration? {
|
||||
let row = rows[indexPath.row]
|
||||
let close = row.state != "closed"
|
||||
let stateAction = UIContextualAction(
|
||||
style: .normal,
|
||||
title: close ? "Close" : "Open"
|
||||
) { [weak self] _, _, completion in
|
||||
self?.setIssue(row, closed: close, completion: completion) ?? completion(false)
|
||||
}
|
||||
stateAction.image = context.symbol(close ? "checkmark.circle" : "arrow.uturn.left.circle")
|
||||
stateAction.backgroundColor = close ? .systemPurple : .systemGreen
|
||||
|
||||
let deleteAction = UIContextualAction(style: .destructive, title: "Delete") {
|
||||
[weak self] _, _, completion in
|
||||
self?.confirmDelete(row, completion: completion) ?? completion(false)
|
||||
}
|
||||
deleteAction.image = context.symbol("trash")
|
||||
|
||||
let configuration = UISwipeActionsConfiguration(actions: [stateAction, deleteAction])
|
||||
configuration.performsFirstActionWithFullSwipe = true
|
||||
return configuration
|
||||
}
|
||||
|
||||
private func setIssue(
|
||||
_ row: IssueRow,
|
||||
closed: Bool,
|
||||
completion: @escaping (Bool) -> Void
|
||||
) {
|
||||
mutationTask?.cancel()
|
||||
mutationTask = Task {
|
||||
do {
|
||||
try await context.core.setIssueClosed(
|
||||
owner: owner,
|
||||
repository: repository,
|
||||
number: row.number,
|
||||
closed: closed
|
||||
)
|
||||
guard !Task.isCancelled else {
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
completion(true)
|
||||
loadIssues(refreshing: false)
|
||||
} catch {
|
||||
completion(false)
|
||||
if !Task.isCancelled { show(error: error) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func confirmDelete(_ row: IssueRow, completion: @escaping (Bool) -> Void) {
|
||||
let alert = UIAlertController(
|
||||
title: "Delete Issue #\(row.number)?",
|
||||
message: "“\(row.title)” will be permanently deleted. This can’t be undone.",
|
||||
preferredStyle: .alert
|
||||
)
|
||||
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { _ in completion(false) })
|
||||
alert.addAction(UIAlertAction(title: "Delete", style: .destructive) { [weak self] _ in
|
||||
guard let self else {
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
self.mutationTask?.cancel()
|
||||
self.mutationTask = Task {
|
||||
do {
|
||||
try await self.context.core.deleteIssue(
|
||||
owner: self.owner,
|
||||
repository: self.repository,
|
||||
number: row.number
|
||||
)
|
||||
guard !Task.isCancelled else {
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
completion(true)
|
||||
self.loadIssues(refreshing: false)
|
||||
} catch {
|
||||
completion(false)
|
||||
if !Task.isCancelled { self.show(error: error) }
|
||||
}
|
||||
}
|
||||
})
|
||||
present(alert, animated: true)
|
||||
}
|
||||
|
||||
private func updateFilterMenu() {
|
||||
let current = context.core.settings().issueStatus
|
||||
let status = UIMenu(
|
||||
|
||||
@@ -17,6 +17,7 @@ final class IssueEditorViewController: UIViewController, UITextFieldDelegate, UI
|
||||
private let previewView = UIView()
|
||||
private let labelsButton = UIButton(type: .system)
|
||||
private let milestoneButton = UIButton(type: .system)
|
||||
private let closedSwitch = UISwitch()
|
||||
private let dueSwitch = UISwitch()
|
||||
private let duePicker = UIDatePicker()
|
||||
private let spinner = UIActivityIndicatorView(style: .medium)
|
||||
@@ -103,6 +104,14 @@ final class IssueEditorViewController: UIViewController, UITextFieldDelegate, UI
|
||||
labelsButton.configuration?.image = context.symbol("tag")
|
||||
milestoneButton.configuration?.image = context.symbol("flag")
|
||||
|
||||
let closedLabel = UILabel()
|
||||
closedLabel.text = "Closed"
|
||||
closedLabel.font = .preferredFont(forTextStyle: .body)
|
||||
closedLabel.adjustsFontForContentSizeCategory = true
|
||||
let closedRow = UIStackView(arrangedSubviews: [closedLabel, closedSwitch])
|
||||
closedRow.alignment = .center
|
||||
closedSwitch.accessibilityLabel = "Closed issue"
|
||||
|
||||
let dueLabel = UILabel()
|
||||
dueLabel.text = "Due Date"
|
||||
dueLabel.font = .preferredFont(forTextStyle: .body)
|
||||
@@ -133,6 +142,7 @@ final class IssueEditorViewController: UIViewController, UITextFieldDelegate, UI
|
||||
stack.addArrangedSubview(titleField)
|
||||
stack.addArrangedSubview(labelsButton)
|
||||
stack.addArrangedSubview(milestoneButton)
|
||||
stack.addArrangedSubview(closedRow)
|
||||
stack.addArrangedSubview(dueRow)
|
||||
stack.addArrangedSubview(duePicker)
|
||||
stack.addArrangedSubview(modeControl)
|
||||
@@ -164,6 +174,7 @@ final class IssueEditorViewController: UIViewController, UITextFieldDelegate, UI
|
||||
self.page = page
|
||||
titleField.text = page.title
|
||||
bodyView.text = page.body
|
||||
closedSwitch.isOn = page.closed
|
||||
selectedLabels = Set(page.labels.filter(\.selected).map(\.id))
|
||||
selectedMilestone = page.milestones.first(where: \.selected)?.id
|
||||
if let timestamp = page.dueDate {
|
||||
@@ -310,7 +321,8 @@ final class IssueEditorViewController: UIViewController, UITextFieldDelegate, UI
|
||||
body: bodyView.text ?? "",
|
||||
labelIds: Array(selectedLabels),
|
||||
milestoneId: selectedMilestone,
|
||||
dueDate: dueSwitch.isOn ? utcTimestamp(forLocalDate: duePicker.date) : nil
|
||||
dueDate: dueSwitch.isOn ? utcTimestamp(forLocalDate: duePicker.date) : nil,
|
||||
closed: closedSwitch.isOn
|
||||
)
|
||||
guard !Task.isCancelled else { return }
|
||||
saved(number)
|
||||
|
||||
Reference in New Issue
Block a user