Tusker/Tusker/Preferences/StatusSwipeAction.swift
Shadowfacts 23a4999196 Complete asynchronous swipe actions immediately
Fixes crash when the user things the action has failed and taps it
again, which results in an invalid completion handler later being called
2023-01-20 10:53:30 -05:00

176 lines
6.7 KiB
Swift

//
// StatusSwipeAction.swift
// Tusker
//
// Created by Shadowfacts on 11/26/22.
// Copyright © 2022 Shadowfacts. All rights reserved.
//
import UIKit
import Pachyderm
enum StatusSwipeAction: String, Codable, Hashable, CaseIterable {
case reply
case favorite
case reblog
case share
case bookmark
case openInSafari
var displayName: String {
switch self {
case .reply:
return "Reply"
case .favorite:
return "Favorite"
case .reblog:
return "Reblog"
case .share:
return "Share"
case .bookmark:
return "Bookmark"
case .openInSafari:
return "Open in Safari"
}
}
var systemImageName: String {
switch self {
case .reply:
return "arrowshape.turn.up.left.fill"
case .favorite:
return "star.fill"
case .reblog:
return "repeat"
case .share:
return "square.and.arrow.up"
case .bookmark:
return "bookmark.fill"
case .openInSafari:
return "safari"
}
}
func createAction(status: StatusMO, container: StatusSwipeActionContainer) -> UIContextualAction? {
switch self {
case .reply:
return createReplyAction(status: status, container: container)
case .favorite:
return createFavoriteAction(status: status, container: container)
case .reblog:
return createReblogAction(status: status, container: container)
case .share:
return createShareAction(status: status, container: container)
case .bookmark:
return createBookmarkAction(status: status, container: container)
case .openInSafari:
return createOpenInSafariAction(status: status, container: container)
}
}
}
protocol StatusSwipeActionContainer: UIView {
var mastodonController: MastodonController! { get }
var navigationDelegate: any TuskerNavigationDelegate { get }
var toastableViewController: ToastableViewController? { get }
var canReblog: Bool { get }
// necessary b/c the reblog-handling logic only exists in the cells
func performReplyAction()
}
private func createReplyAction(status: StatusMO, container: StatusSwipeActionContainer) -> UIContextualAction? {
guard container.mastodonController.loggedIn else {
return nil
}
let action = UIContextualAction(style: .normal, title: "Reply") { [unowned container] _, _, completion in
container.performReplyAction()
completion(true)
}
action.image = UIImage(systemName: "arrowshape.turn.up.left.fill")
action.backgroundColor = container.tintColor
return action
}
private func createFavoriteAction(status: StatusMO, container: StatusSwipeActionContainer) -> UIContextualAction? {
guard container.mastodonController.loggedIn else {
return nil
}
let title = status.favourited ? "Unfavorite" : "Favorite"
let action = UIContextualAction(style: .normal, title: title) { [unowned container] _, _, completion in
completion(true)
Task { @MainActor in
await FavoriteService(status: status, mastodonController: container.mastodonController, presenter: container.navigationDelegate).toggleFavorite()
}
}
action.image = UIImage(systemName: "star.fill")
action.backgroundColor = status.favourited ? UIColor(displayP3Red: 235/255, green: 77/255, blue: 62/255, alpha: 1) : UIColor(displayP3Red: 1, green: 204/255, blue: 0, alpha: 1)
return action
}
private func createReblogAction(status: StatusMO, container: StatusSwipeActionContainer) -> UIContextualAction? {
guard container.mastodonController.loggedIn,
container.canReblog else {
return nil
}
let title = status.reblogged ? "Unreblog" : "Reblog"
let action = UIContextualAction(style: .normal, title: title) { [unowned container] _, _, completion in
completion(true)
Task { @MainActor in
await ReblogService(status: status, mastodonController: container.mastodonController, presenter: container.navigationDelegate).toggleReblog()
}
}
action.image = UIImage(systemName: "repeat")
action.backgroundColor = status.reblogged ? UIColor(displayP3Red: 235/255, green: 77/255, blue: 62/255, alpha: 1) : container.tintColor
return action
}
private func createShareAction(status: StatusMO, container: StatusSwipeActionContainer) -> UIContextualAction {
let action = UIContextualAction(style: .normal, title: "Share") { [unowned container] _, _, completion in
container.navigationDelegate.showMoreOptions(forStatus: status.id, source: .view(container))
completion(true)
}
// bold to more closesly match other action symbols
let config = UIImage.SymbolConfiguration(weight: .bold)
action.image = UIImage(systemName: "square.and.arrow.up")!.applyingSymbolConfiguration(config)!
action.backgroundColor = .lightGray
return action
}
private func createBookmarkAction(status: StatusMO, container: StatusSwipeActionContainer) -> UIContextualAction? {
guard container.mastodonController.loggedIn else {
return nil
}
let bookmarked = status.bookmarked ?? false
let title = bookmarked ? "Unbookmark" : "Bookmark"
let action = UIContextualAction(style: .normal, title: title) { [unowned container] _, _, completion in
completion(true)
Task { @MainActor in
let request = (bookmarked ? Status.unbookmark : Status.bookmark)(status.id)
do {
let (status, _) = try await container.mastodonController.run(request)
container.mastodonController.persistentContainer.addOrUpdate(status: status)
} catch {
if let toastable = container.toastableViewController {
let config = ToastConfiguration(from: error, with: "Error \(bookmarked ? "Unb" : "B")ookmarking", in: toastable, retryAction: nil)
toastable.showToast(configuration: config, animated: true)
}
}
}
}
action.image = UIImage(systemName: "bookmark.fill")
action.backgroundColor = .systemRed
return action
}
private func createOpenInSafariAction(status: StatusMO, container: StatusSwipeActionContainer) -> UIContextualAction {
let action = UIContextualAction(style: .normal, title: "Open in Safari") { [unowned container] _, _, completion in
container.navigationDelegate.selected(url: status.url!, allowUniversalLinks: false)
completion(true)
}
action.image = UIImage(systemName: "safari")
action.backgroundColor = container.tintColor
return action
}