From 4991da16224adb4b077fb55237d7bc8f259b32b9 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Mon, 6 Jun 2022 22:58:14 -0400 Subject: [PATCH] Add favorite/reblog menu actions on iOS 16 --- Tusker/Screens/Utilities/Previewing.swift | 78 ++++++++++++++++++++--- 1 file changed, 70 insertions(+), 8 deletions(-) diff --git a/Tusker/Screens/Utilities/Previewing.swift b/Tusker/Screens/Utilities/Previewing.swift index 539746db..cc76889f 100644 --- a/Tusker/Screens/Utilities/Previewing.swift +++ b/Tusker/Screens/Utilities/Previewing.swift @@ -137,10 +137,9 @@ extension MenuActionProvider { }) ] } - let bookmarked = status.bookmarked ?? false - var actionsSection = [ + var toggleableSection = [ createAction(identifier: "bookmark", title: bookmarked ? "Unbookmark" : "Bookmark", systemImageName: bookmarked ? "bookmark.fill" : "bookmark", handler: { [weak self] (_) in guard let self = self else { return } let request = (bookmarked ? Status.unbookmark : Status.bookmark)(status.id) @@ -161,6 +160,57 @@ extension MenuActionProvider { }), ] + if #available(iOS 16.0, *) { + let favorited = status.favourited + // TODO: move this color into an asset catalog or something + var favImage = UIImage(systemName: favorited ? "star.fill" : "star")! + if favorited { + favImage = favImage.withTintColor(UIColor(displayP3Red: 1, green: 0.8, blue: 0, alpha: 1), renderingMode: .alwaysOriginal) + } + toggleableSection.insert(createAction(identifier: "favorite", title: favorited ? "Unfavorite" : "Favorite", image: favImage, handler: { [weak self] _ in + guard let self = self else { return } + let request = (favorited ? Status.favourite : Status.unfavourite)(status.id) + self.mastodonController?.run(request, completion: { response in + switch response { + case .success(let status, _): + self.mastodonController?.persistentContainer.addOrUpdate(status: status) + case .failure(let error): + if let toastable = self.toastableViewController { + let config = ToastConfiguration(from: error, with: "Error \(favorited ? "Unf" : "F")avoriting", in: toastable, retryAction: nil) + DispatchQueue.main.async { + toastable.showToast(configuration: config, animated: true) + } + } + } + }) + }), at: 0) + + let reblogged = status.reblogged + var reblogImage = UIImage(systemName: "repeat")! + if reblogged { + reblogImage = reblogImage.withTintColor(UIColor(displayP3Red: 1, green: 0.8, blue: 0, alpha: 1), renderingMode: .alwaysOriginal) + } + toggleableSection.insert(createAction(identifier: "reblog", title: reblogged ? "Unreblog" : "Reblog", image: reblogImage, handler: { [weak self] _ in + guard let self = self else { return } + let request = (reblogged ? Status.reblog : Status.unreblog)(status.id) + self.mastodonController?.run(request, completion: { response in + switch response { + case .success(let status, _): + self.mastodonController?.persistentContainer.addOrUpdate(status: status) + case .failure(let error): + if let toastable = self.toastableViewController { + let config = ToastConfiguration(from: error, with: "Error \(reblogged ? "Unr" : "R")eblogging", in: toastable, retryAction: nil) + DispatchQueue.main.async { + toastable.showToast(configuration: config, animated: true) + } + } + } + }) + }), at: 1) + } + + var actionsSection: [UIAction] = [] + if includeReply { actionsSection.insert(createAction(identifier: "reply", title: "Reply", systemImageName: "arrowshape.turn.up.left", handler: { [weak self] (_) in guard let self = self else { return } @@ -172,7 +222,7 @@ extension MenuActionProvider { // only allow muting conversations that either current user posted or is participating in (technically, is mentioned, since that's the best we can do) if status.account.id == account.id || status.mentions.contains(where: { $0.id == account.id }) { let muted = status.muted - actionsSection.append(createAction(identifier: "mute", title: muted ? "Unmute Conversation" : "Mute Conversation", systemImageName: muted ? "speaker" : "speaker.slash", handler: { [weak self] (_) in + toggleableSection.append(createAction(identifier: "mute", title: muted ? "Unmute Conversation" : "Mute Conversation", systemImageName: muted ? "speaker" : "speaker.slash", handler: { [weak self] (_) in guard let self = self else { return } let request = (muted ? Status.unmuteConversation : Status.muteConversation)(status.id) self.mastodonController?.run(request) { (response) in @@ -195,7 +245,7 @@ extension MenuActionProvider { if account.id == status.account.id, mastodonController.instanceFeatures.profilePinnedStatuses { let pinned = status.pinned ?? false - actionsSection.append(createAction(identifier: "pin", title: pinned ? "Unpin from Profile" : "Pin to Profile", systemImageName: pinned ? "pin.slash" : "pin", handler: { [weak self] (_) in + toggleableSection.append(createAction(identifier: "pin", title: pinned ? "Unpin from Profile" : "Pin to Profile", systemImageName: pinned ? "pin.slash" : "pin", handler: { [weak self] (_) in guard let self = self else { return } let request = (pinned ? Status.unpin : Status.pin)(status.id) self.mastodonController?.run(request, completion: { [weak self] (response) in @@ -250,10 +300,18 @@ extension MenuActionProvider { addOpenInNewWindow(actions: &shareSection, activity: UserActivityManager.showConversationActivity(mainStatusID: status.id, accountID: accountID)) - return [ - UIMenu(title: "", image: nil, identifier: nil, options: [.displayInline], children: shareSection), - UIMenu(title: "", image: nil, identifier: nil, options: [.displayInline], children: actionsSection), - ] + if #available(iOS 16.0, *) { + return [ + UIMenu(options: .displayInline, preferredElementSize: .medium, children: toggleableSection + actionsSection), + UIMenu(options: .displayInline, children: shareSection), + ] + } else { + return [ + UIMenu(options: .displayInline, children: shareSection), + UIMenu(options: .displayInline, children: toggleableSection), + UIMenu(options: .displayInline, children: actionsSection), + ] + } } func actionsForTrendingLink(card: Card) -> [UIMenuElement] { @@ -286,6 +344,10 @@ extension MenuActionProvider { } else { image = nil } + return createAction(identifier: identifier, title: title, image: image, handler: handler) + } + + private func createAction(identifier: String, title: String, image: UIImage?, handler: @escaping UIActionHandler) -> UIAction { return UIAction(title: title, image: image, identifier: UIAction.Identifier(identifier), discoverabilityTitle: nil, attributes: [], state: .off, handler: handler) }