114 lines
4.2 KiB
Swift
114 lines
4.2 KiB
Swift
import UIKit
|
||
|
||
extension UIViewController {
|
||
func promptForSearchText(title: String, current: String, apply: @escaping (String) -> Void) {
|
||
let alert = UIAlertController(title: title, message: nil, preferredStyle: .alert)
|
||
alert.addTextField { field in
|
||
field.text = current
|
||
field.placeholder = "Search text"
|
||
field.accessibilityLabel = "Search text"
|
||
field.clearButtonMode = .whileEditing
|
||
field.returnKeyType = .search
|
||
}
|
||
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
|
||
alert.addAction(UIAlertAction(title: "Apply", style: .default) { [weak alert] _ in
|
||
apply(alert?.textFields?.first?.text ?? "")
|
||
})
|
||
present(alert, animated: true)
|
||
}
|
||
}
|
||
@MainActor
|
||
protocol IssueSwipeActionHost: AnyObject {
|
||
var context: AppContext { get }
|
||
var owner: String { get }
|
||
var repository: String { get }
|
||
var issueMutationTask: Task<Void, Never>? { get set }
|
||
func reloadIssuesAfterMutation()
|
||
}
|
||
|
||
extension IssueSwipeActionHost where Self: UIViewController {
|
||
func issueSwipeActions(for row: IssueRow) -> UISwipeActionsConfiguration {
|
||
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?.confirmDeleteIssue(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
|
||
) {
|
||
issueMutationTask?.cancel()
|
||
issueMutationTask = 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)
|
||
reloadIssuesAfterMutation()
|
||
} catch {
|
||
completion(false)
|
||
if !Task.isCancelled { show(error: error) }
|
||
}
|
||
}
|
||
}
|
||
|
||
private func confirmDeleteIssue(_ 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.issueMutationTask?.cancel()
|
||
self.issueMutationTask = 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.reloadIssuesAfterMutation()
|
||
} catch {
|
||
completion(false)
|
||
if !Task.isCancelled { self.show(error: error) }
|
||
}
|
||
}
|
||
})
|
||
present(alert, animated: true)
|
||
}
|
||
}
|