From 8d3cb2beb3c247938c47b208fdff65534662d647 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Sat, 18 May 2024 14:30:51 +0200 Subject: [PATCH] Update Post Menu (IOS-229) (#1292) * Add missing actions, hack way to inject options into menu (IOS-229) * Implement new menu structure (IOS-229) This doesn't work-work yet, as functionality isn't implemented yet. Also: Special options and traits. * Add preferredElementSize to submenus (IOS-229) * Copy Link (IOS-229) * Open In Browser (IOS-229) * Boost status from menu (IOS-229) * Favorite status from menu (IOS-229) * Fix following-status in menu and don't cache menu (IOS-229) * Add some destruction, add localization and use "Boost" instead of "Reblog" (IOS-229) * Use struct instead of tuple for those menu-parts (IOS-229) --- .../input/Base.lproj/app.json | 37 ++++---- Localization/app.json | 37 ++++---- .../Provider/DataSourceFacade+Status.swift | 32 +++++++ ...er+NotificationTableViewCellDelegate.swift | 3 +- .../NotificationView/NotificationView.swift | 27 ++---- .../Scene/Profile/ProfileViewController.swift | 66 ++++++------- .../Generated/Strings.swift | 70 +++++++------- .../Resources/Base.lproj/Localizable.strings | 35 +++---- .../View/Content/StatusAuthorView.swift | 79 ++++++++-------- .../View/Content/StatusView+ViewModel.swift | 27 ++++-- .../MastodonUI/View/Menu/MastodonMenu.swift | 94 +++++++++++++++++-- 11 files changed, 304 insertions(+), 203 deletions(-) diff --git a/Localization/StringsConvertor/input/Base.lproj/app.json b/Localization/StringsConvertor/input/Base.lproj/app.json index 4be5fb01b..d36af7781 100644 --- a/Localization/StringsConvertor/input/Base.lproj/app.json +++ b/Localization/StringsConvertor/input/Base.lproj/app.json @@ -143,9 +143,9 @@ "next_status": "Next Post", "open_status": "Open Post", "open_author_profile": "Open Author's Profile", - "open_reblogger_profile": "Open Reblogger's Profile", + "open_reblogger_profile": "Open Booster's Profile", "reply_status": "Reply to Post", - "toggle_reblog": "Toggle Reblog on Post", + "toggle_reblog": "Toggle Boost on Post", "toggle_favorite": "Toggle Favorite on Post", "toggle_content_warning": "Toggle Content Warning", "preview_image": "Preview Image" @@ -156,7 +156,7 @@ } }, "status": { - "user_reblogged": "%s reblogged", + "user_reblogged": "%s boosted", "user_replied_to": "Replied to %s", "show_post": "Show Post", "show_user_profile": "Show user profile", @@ -178,8 +178,8 @@ }, "actions": { "reply": "Reply", - "reblog": "Reblog", - "unreblog": "Undo reblog", + "reblog": "Boost", + "unreblog": "Undo boost", "favorite": "Favorite", "unfavorite": "Unfavorite", "menu": "Menu", @@ -190,9 +190,10 @@ "share_link_in_post": "Share Link in Post", "tap_then_hold_to_show_menu": "Tap then hold to show menu", "a11y_labels": { - "reblog": "Re-blog", - "unreblog": "Undo re-blog" - } + "reblog": "Boost", + "unreblog": "Undo boost" + }, + "copy_link": "Copy Link" }, "tag": { "url": "URL", @@ -222,7 +223,7 @@ }, "posted_via_application": "%s via %s", "buttons": { - "reblogs_title": "Reblogs", + "reblogs_title": "Boosts", "favorites_title": "Favorites", "edit_history_title": "Edit History", "edit_history_detail": "Last edit %s" @@ -251,8 +252,8 @@ "unmute_user": "Unmute %s", "muted": "Muted", "edit_info": "Edit Info", - "show_reblogs": "Show Reblogs", - "hide_reblogs": "Hide Reblogs" + "show_reblogs": "Show Boosts", + "hide_reblogs": "Hide Boosts" }, "timeline": { "filtered": "Filtered", @@ -616,12 +617,12 @@ "message": "Confirm to unblock %s" }, "confirm_show_reblogs": { - "title": "Show Reblogs", - "message": "Confirm to show reblogs" + "title": "Show Boosts", + "message": "Confirm to show boosts" }, "confirm_hide_reblogs": { - "title": "Hide Reblogs", - "message": "Confirm to hide reblogs" + "title": "Hide Boosts", + "message": "Confirm to hide boosts" }, "confirm_block_domain": { "title": "Block domain", @@ -655,7 +656,7 @@ "title": "Favorited By" }, "reblogged_by": { - "title": "Reblogged By" + "title": "Boosted By" }, "search": { "title": "Search", @@ -715,7 +716,7 @@ "notification_description": { "followed_you": "followed you", "favorited_your_post": "favorited your post", - "reblogged_your_post": "reblogged your post", + "reblogged_your_post": "boosted your post", "mentioned_you": "mentioned you", "request_to_follow_you": "request to follow you", "poll_has_ended": "poll has ended" @@ -871,7 +872,7 @@ "unfollowed": "Unfollowed", "unfollow_user": "Unfollow %s", "mute_user": "Mute %s", - "you_wont_see_their_posts_or_reblogs_in_your_home_feed_they_wont_know_they_ve_been_muted": "You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted.", + "you_wont_see_their_posts_or_reblogs_in_your_home_feed_they_wont_know_they_ve_been_muted": "You won’t see their posts or boosts in your home feed. They won’t know they’ve been muted.", "block_user": "Block %s", "they_will_no_longer_be_able_to_follow_or_see_your_posts_but_they_can_see_if_theyve_been_blocked": "They will no longer be able to follow or see your posts, but they can see if they’ve been blocked.", "while_we_review_this_you_can_take_action_against_user": "While we review this, you can take action against %s" diff --git a/Localization/app.json b/Localization/app.json index 4be5fb01b..d36af7781 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -143,9 +143,9 @@ "next_status": "Next Post", "open_status": "Open Post", "open_author_profile": "Open Author's Profile", - "open_reblogger_profile": "Open Reblogger's Profile", + "open_reblogger_profile": "Open Booster's Profile", "reply_status": "Reply to Post", - "toggle_reblog": "Toggle Reblog on Post", + "toggle_reblog": "Toggle Boost on Post", "toggle_favorite": "Toggle Favorite on Post", "toggle_content_warning": "Toggle Content Warning", "preview_image": "Preview Image" @@ -156,7 +156,7 @@ } }, "status": { - "user_reblogged": "%s reblogged", + "user_reblogged": "%s boosted", "user_replied_to": "Replied to %s", "show_post": "Show Post", "show_user_profile": "Show user profile", @@ -178,8 +178,8 @@ }, "actions": { "reply": "Reply", - "reblog": "Reblog", - "unreblog": "Undo reblog", + "reblog": "Boost", + "unreblog": "Undo boost", "favorite": "Favorite", "unfavorite": "Unfavorite", "menu": "Menu", @@ -190,9 +190,10 @@ "share_link_in_post": "Share Link in Post", "tap_then_hold_to_show_menu": "Tap then hold to show menu", "a11y_labels": { - "reblog": "Re-blog", - "unreblog": "Undo re-blog" - } + "reblog": "Boost", + "unreblog": "Undo boost" + }, + "copy_link": "Copy Link" }, "tag": { "url": "URL", @@ -222,7 +223,7 @@ }, "posted_via_application": "%s via %s", "buttons": { - "reblogs_title": "Reblogs", + "reblogs_title": "Boosts", "favorites_title": "Favorites", "edit_history_title": "Edit History", "edit_history_detail": "Last edit %s" @@ -251,8 +252,8 @@ "unmute_user": "Unmute %s", "muted": "Muted", "edit_info": "Edit Info", - "show_reblogs": "Show Reblogs", - "hide_reblogs": "Hide Reblogs" + "show_reblogs": "Show Boosts", + "hide_reblogs": "Hide Boosts" }, "timeline": { "filtered": "Filtered", @@ -616,12 +617,12 @@ "message": "Confirm to unblock %s" }, "confirm_show_reblogs": { - "title": "Show Reblogs", - "message": "Confirm to show reblogs" + "title": "Show Boosts", + "message": "Confirm to show boosts" }, "confirm_hide_reblogs": { - "title": "Hide Reblogs", - "message": "Confirm to hide reblogs" + "title": "Hide Boosts", + "message": "Confirm to hide boosts" }, "confirm_block_domain": { "title": "Block domain", @@ -655,7 +656,7 @@ "title": "Favorited By" }, "reblogged_by": { - "title": "Reblogged By" + "title": "Boosted By" }, "search": { "title": "Search", @@ -715,7 +716,7 @@ "notification_description": { "followed_you": "followed you", "favorited_your_post": "favorited your post", - "reblogged_your_post": "reblogged your post", + "reblogged_your_post": "boosted your post", "mentioned_you": "mentioned you", "request_to_follow_you": "request to follow you", "poll_has_ended": "poll has ended" @@ -871,7 +872,7 @@ "unfollowed": "Unfollowed", "unfollow_user": "Unfollow %s", "mute_user": "Mute %s", - "you_wont_see_their_posts_or_reblogs_in_your_home_feed_they_wont_know_they_ve_been_muted": "You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted.", + "you_wont_see_their_posts_or_reblogs_in_your_home_feed_they_wont_know_they_ve_been_muted": "You won’t see their posts or boosts in your home feed. They won’t know they’ve been muted.", "block_user": "Block %s", "they_will_no_longer_be_able_to_follow_or_see_your_posts_but_they_can_see_if_theyve_been_blocked": "They will no longer be able to follow or see your posts, but they can see if they’ve been blocked.", "while_we_review_this_you_can_take_action_against_user": "While we review this, you can take action against %s" diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift index ddac7f14d..7fe8fe639 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift @@ -384,6 +384,38 @@ extension DataSourceFacade { let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .cancel) alertController.addAction(cancelAction) dependency.present(alertController, animated: true) + case .boostStatus(_): + guard let status: MastodonStatus = menuContext.statusViewModel?.originalStatus?.reblog ?? menuContext.statusViewModel?.originalStatus else { + assertionFailure() + return + } + + try await responseToStatusReblogAction(provider: dependency, status: status) + case .favoriteStatus(_): + guard let status: MastodonStatus = menuContext.statusViewModel?.originalStatus?.reblog ?? menuContext.statusViewModel?.originalStatus else { + assertionFailure() + return + } + + try await responseToStatusFavoriteAction(provider: dependency, status: status) + case .copyLink: + guard let status: MastodonStatus = menuContext.statusViewModel?.originalStatus?.reblog ?? menuContext.statusViewModel?.originalStatus else { + assertionFailure() + return + } + + UIPasteboard.general.string = status.entity.url + case .openInBrowser: + guard + let status: MastodonStatus = menuContext.statusViewModel?.originalStatus?.reblog ?? menuContext.statusViewModel?.originalStatus, + let urlString = status.entity.url, + let url = URL(string: urlString) + else { + assertionFailure() + return + } + + dependency.coordinator.present(scene: .safari(url: url), transition: .safariPresent(animated: true)) } } } diff --git a/Mastodon/Protocol/Provider/DataSourceProvider+NotificationTableViewCellDelegate.swift b/Mastodon/Protocol/Provider/DataSourceProvider+NotificationTableViewCellDelegate.swift index d468962e1..9e7f6e047 100644 --- a/Mastodon/Protocol/Provider/DataSourceProvider+NotificationTableViewCellDelegate.swift +++ b/Mastodon/Protocol/Provider/DataSourceProvider+NotificationTableViewCellDelegate.swift @@ -61,9 +61,10 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider & Aut barButtonItem: nil ) ) - case .translateStatus(_), .showOriginal, .shareUser(_), .blockDomain(_), .bookmarkStatus(_), .hideReblogs(_), .shareStatus, .deleteStatus, .editStatus, .followUser(_): + case .translateStatus(_), .showOriginal, .shareUser(_), .blockDomain(_), .bookmarkStatus(_), .hideReblogs(_), .shareStatus, .deleteStatus, .editStatus, .followUser(_), .boostStatus(_), .favoriteStatus(_), .copyLink, .openInBrowser: // Do Nothing break + } } } diff --git a/Mastodon/Scene/Notification/NotificationView/NotificationView.swift b/Mastodon/Scene/Notification/NotificationView/NotificationView.swift index 3d1d7605a..a947db916 100644 --- a/Mastodon/Scene/Notification/NotificationView/NotificationView.swift +++ b/Mastodon/Scene/Notification/NotificationView/NotificationView.swift @@ -461,37 +461,26 @@ extension NotificationView { } public func setupAuthorMenu(menuContext: AuthorMenuContext) -> (UIMenu, [UIAccessibilityCustomAction]) { - var actions: [[MastodonMenu.Action]] = [] - var upperActions: [MastodonMenu.Action] = [] - - upperActions = [ - .muteUser(.init( - name: menuContext.name, - isMuting: menuContext.isMuting - )), - .blockUser(.init( - name: menuContext.name, - isBlocking: menuContext.isBlocking - )), - .reportUser( - .init(name: menuContext.name) + var items = [ + MastodonMenu.Submenu(actions: [ + .muteUser(.init(name: menuContext.name,isMuting: menuContext.isMuting)), + .blockUser(.init(name: menuContext.name,isBlocking: menuContext.isBlocking)), + .reportUser(.init(name: menuContext.name))] ) ] - actions.append(upperActions) - if menuContext.isMyself { - actions.append([.deleteStatus]) + items.append(MastodonMenu.Submenu(actions: [.deleteStatus])) } let menu = MastodonMenu.setupMenu( - actions: actions, + submenus: items, delegate: self ) let accessibilityActions = MastodonMenu.setupAccessibilityActions( - actions: actions, + actions: items.compactMap { $0.actions } , delegate: self ) diff --git a/Mastodon/Scene/Profile/ProfileViewController.swift b/Mastodon/Scene/Profile/ProfileViewController.swift index 84f0cf5ae..4d35a361c 100644 --- a/Mastodon/Scene/Profile/ProfileViewController.swift +++ b/Mastodon/Scene/Profile/ProfileViewController.swift @@ -451,7 +451,7 @@ extension ProfileViewController { } let menu = MastodonMenu.setupMenu( - actions: [menuActions], + submenus: [MastodonMenu.Submenu(actions: menuActions)], delegate: self ) return menu @@ -929,42 +929,36 @@ extension ProfileViewController: ProfileAboutViewControllerDelegate { extension ProfileViewController: MastodonMenuDelegate { func menuAction(_ action: MastodonMenu.Action) { switch action { - case .muteUser(_), - .blockUser(_), - .blockDomain(_), - .hideReblogs(_): - Task { - try await DataSourceFacade.responseToMenuAction( - dependency: self, - action: action, - menuContext: DataSourceFacade.MenuContext( - author: viewModel.account, - statusViewModel: nil, - button: nil, - barButtonItem: self.moreMenuBarButtonItem - )) - } - case .reportUser(_), .shareUser(_): - Task { - try await DataSourceFacade.responseToMenuAction( - dependency: self, - action: action, - menuContext: DataSourceFacade.MenuContext( - author: viewModel.account, - statusViewModel: nil, - button: nil, - barButtonItem: self.moreMenuBarButtonItem - )) - } + case .muteUser(_), + .blockUser(_), + .blockDomain(_), + .hideReblogs(_): + Task { + try await DataSourceFacade.responseToMenuAction( + dependency: self, + action: action, + menuContext: DataSourceFacade.MenuContext( + author: viewModel.account, + statusViewModel: nil, + button: nil, + barButtonItem: self.moreMenuBarButtonItem + )) + } + case .reportUser(_), .shareUser(_): + Task { + try await DataSourceFacade.responseToMenuAction( + dependency: self, + action: action, + menuContext: DataSourceFacade.MenuContext( + author: viewModel.account, + statusViewModel: nil, + button: nil, + barButtonItem: self.moreMenuBarButtonItem + )) + } - case .translateStatus(_), - .showOriginal, - .bookmarkStatus(_), - .shareStatus, - .deleteStatus, - .editStatus, - .followUser(_): - break + case .translateStatus(_), .showOriginal, .bookmarkStatus(_), .shareStatus, .deleteStatus, .editStatus, .followUser(_), .boostStatus(_), .favoriteStatus(_), .copyLink, .openInBrowser: + break } } } diff --git a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift index ccf92024e..0f67b89dc 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift +++ b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift @@ -259,8 +259,8 @@ public enum L10n { public static let follow = L10n.tr("Localizable", "Common.Controls.Friendship.Follow", fallback: "Follow") /// Following public static let following = L10n.tr("Localizable", "Common.Controls.Friendship.Following", fallback: "Following") - /// Hide Reblogs - public static let hideReblogs = L10n.tr("Localizable", "Common.Controls.Friendship.HideReblogs", fallback: "Hide Reblogs") + /// Hide Boosts + public static let hideReblogs = L10n.tr("Localizable", "Common.Controls.Friendship.HideReblogs", fallback: "Hide Boosts") /// Mute public static let mute = L10n.tr("Localizable", "Common.Controls.Friendship.Mute", fallback: "Mute") /// Muted @@ -273,8 +273,8 @@ public enum L10n { public static let pending = L10n.tr("Localizable", "Common.Controls.Friendship.Pending", fallback: "Pending") /// Request public static let request = L10n.tr("Localizable", "Common.Controls.Friendship.Request", fallback: "Request") - /// Show Reblogs - public static let showReblogs = L10n.tr("Localizable", "Common.Controls.Friendship.ShowReblogs", fallback: "Show Reblogs") + /// Show Boosts + public static let showReblogs = L10n.tr("Localizable", "Common.Controls.Friendship.ShowReblogs", fallback: "Show Boosts") /// Unblock public static let unblock = L10n.tr("Localizable", "Common.Controls.Friendship.Unblock", fallback: "Unblock") /// Unblock %@ @@ -312,8 +312,8 @@ public enum L10n { public static let nextStatus = L10n.tr("Localizable", "Common.Controls.Keyboard.Timeline.NextStatus", fallback: "Next Post") /// Open Author's Profile public static let openAuthorProfile = L10n.tr("Localizable", "Common.Controls.Keyboard.Timeline.OpenAuthorProfile", fallback: "Open Author's Profile") - /// Open Reblogger's Profile - public static let openRebloggerProfile = L10n.tr("Localizable", "Common.Controls.Keyboard.Timeline.OpenRebloggerProfile", fallback: "Open Reblogger's Profile") + /// Open Booster's Profile + public static let openRebloggerProfile = L10n.tr("Localizable", "Common.Controls.Keyboard.Timeline.OpenRebloggerProfile", fallback: "Open Booster's Profile") /// Open Post public static let openStatus = L10n.tr("Localizable", "Common.Controls.Keyboard.Timeline.OpenStatus", fallback: "Open Post") /// Preview Image @@ -326,8 +326,8 @@ public enum L10n { public static let toggleContentWarning = L10n.tr("Localizable", "Common.Controls.Keyboard.Timeline.ToggleContentWarning", fallback: "Toggle Content Warning") /// Toggle Favorite on Post public static let toggleFavorite = L10n.tr("Localizable", "Common.Controls.Keyboard.Timeline.ToggleFavorite", fallback: "Toggle Favorite on Post") - /// Toggle Reblog on Post - public static let toggleReblog = L10n.tr("Localizable", "Common.Controls.Keyboard.Timeline.ToggleReblog", fallback: "Toggle Reblog on Post") + /// Toggle Boost on Post + public static let toggleReblog = L10n.tr("Localizable", "Common.Controls.Keyboard.Timeline.ToggleReblog", fallback: "Toggle Boost on Post") } } public enum Status { @@ -357,23 +357,25 @@ public enum L10n { public static let showUserProfile = L10n.tr("Localizable", "Common.Controls.Status.ShowUserProfile", fallback: "Show user profile") /// Tap to reveal public static let tapToReveal = L10n.tr("Localizable", "Common.Controls.Status.TapToReveal", fallback: "Tap to reveal") - /// %@ reblogged + /// %@ boosted public static func userReblogged(_ p1: Any) -> String { - return L10n.tr("Localizable", "Common.Controls.Status.UserReblogged", String(describing: p1), fallback: "%@ reblogged") + return L10n.tr("Localizable", "Common.Controls.Status.UserReblogged", String(describing: p1), fallback: "%@ boosted") } /// Replied to %@ public static func userRepliedTo(_ p1: Any) -> String { return L10n.tr("Localizable", "Common.Controls.Status.UserRepliedTo", String(describing: p1), fallback: "Replied to %@") } public enum Actions { + /// Copy Link + public static let copyLink = L10n.tr("Localizable", "Common.Controls.Status.Actions.CopyLink", fallback: "Copy Link") /// Favorite public static let favorite = L10n.tr("Localizable", "Common.Controls.Status.Actions.Favorite", fallback: "Favorite") /// Hide public static let hide = L10n.tr("Localizable", "Common.Controls.Status.Actions.Hide", fallback: "Hide") /// Menu public static let menu = L10n.tr("Localizable", "Common.Controls.Status.Actions.Menu", fallback: "Menu") - /// Reblog - public static let reblog = L10n.tr("Localizable", "Common.Controls.Status.Actions.Reblog", fallback: "Reblog") + /// Boost + public static let reblog = L10n.tr("Localizable", "Common.Controls.Status.Actions.Reblog", fallback: "Boost") /// Reply public static let reply = L10n.tr("Localizable", "Common.Controls.Status.Actions.Reply", fallback: "Reply") /// Share Link in Post @@ -388,13 +390,13 @@ public enum L10n { public static let tapThenHoldToShowMenu = L10n.tr("Localizable", "Common.Controls.Status.Actions.TapThenHoldToShowMenu", fallback: "Tap then hold to show menu") /// Unfavorite public static let unfavorite = L10n.tr("Localizable", "Common.Controls.Status.Actions.Unfavorite", fallback: "Unfavorite") - /// Undo reblog - public static let unreblog = L10n.tr("Localizable", "Common.Controls.Status.Actions.Unreblog", fallback: "Undo reblog") + /// Undo boost + public static let unreblog = L10n.tr("Localizable", "Common.Controls.Status.Actions.Unreblog", fallback: "Undo boost") public enum A11YLabels { - /// Re-blog - public static let reblog = L10n.tr("Localizable", "Common.Controls.Status.Actions.A11YLabels.Reblog", fallback: "Re-blog") - /// Undo re-blog - public static let unreblog = L10n.tr("Localizable", "Common.Controls.Status.Actions.A11YLabels.Unreblog", fallback: "Undo re-blog") + /// Boost + public static let reblog = L10n.tr("Localizable", "Common.Controls.Status.Actions.A11YLabels.Reblog", fallback: "Boost") + /// Undo boost + public static let unreblog = L10n.tr("Localizable", "Common.Controls.Status.Actions.A11YLabels.Unreblog", fallback: "Undo boost") } } public enum Buttons { @@ -406,8 +408,8 @@ public enum L10n { public static let editHistoryTitle = L10n.tr("Localizable", "Common.Controls.Status.Buttons.EditHistoryTitle", fallback: "Edit History") /// Favorites public static let favoritesTitle = L10n.tr("Localizable", "Common.Controls.Status.Buttons.FavoritesTitle", fallback: "Favorites") - /// Reblogs - public static let reblogsTitle = L10n.tr("Localizable", "Common.Controls.Status.Buttons.ReblogsTitle", fallback: "Reblogs") + /// Boosts + public static let reblogsTitle = L10n.tr("Localizable", "Common.Controls.Status.Buttons.ReblogsTitle", fallback: "Boosts") } public enum EditHistory { /// Original Post · %@ @@ -898,8 +900,8 @@ public enum L10n { public static let mentionedYou = L10n.tr("Localizable", "Scene.Notification.NotificationDescription.MentionedYou", fallback: "mentioned you") /// poll has ended public static let pollHasEnded = L10n.tr("Localizable", "Scene.Notification.NotificationDescription.PollHasEnded", fallback: "poll has ended") - /// reblogged your post - public static let rebloggedYourPost = L10n.tr("Localizable", "Scene.Notification.NotificationDescription.RebloggedYourPost", fallback: "reblogged your post") + /// boosted your post + public static let rebloggedYourPost = L10n.tr("Localizable", "Scene.Notification.NotificationDescription.RebloggedYourPost", fallback: "boosted your post") /// request to follow you public static let requestToFollowYou = L10n.tr("Localizable", "Scene.Notification.NotificationDescription.RequestToFollowYou", fallback: "request to follow you") } @@ -1027,10 +1029,10 @@ public enum L10n { public static let title = L10n.tr("Localizable", "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title", fallback: "Block Account") } public enum ConfirmHideReblogs { - /// Confirm to hide reblogs - public static let message = L10n.tr("Localizable", "Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Message", fallback: "Confirm to hide reblogs") - /// Hide Reblogs - public static let title = L10n.tr("Localizable", "Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Title", fallback: "Hide Reblogs") + /// Confirm to hide boosts + public static let message = L10n.tr("Localizable", "Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Message", fallback: "Confirm to hide boosts") + /// Hide Boosts + public static let title = L10n.tr("Localizable", "Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Title", fallback: "Hide Boosts") } public enum ConfirmMuteUser { /// Confirm to mute %@ @@ -1041,10 +1043,10 @@ public enum L10n { public static let title = L10n.tr("Localizable", "Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Title", fallback: "Mute Account") } public enum ConfirmShowReblogs { - /// Confirm to show reblogs - public static let message = L10n.tr("Localizable", "Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Message", fallback: "Confirm to show reblogs") - /// Show Reblogs - public static let title = L10n.tr("Localizable", "Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Title", fallback: "Show Reblogs") + /// Confirm to show boosts + public static let message = L10n.tr("Localizable", "Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Message", fallback: "Confirm to show boosts") + /// Show Boosts + public static let title = L10n.tr("Localizable", "Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Title", fallback: "Show Boosts") } public enum ConfirmUnblockDomain { /// Confirm to unblock domain %@ @@ -1085,8 +1087,8 @@ public enum L10n { } } public enum RebloggedBy { - /// Reblogged By - public static let title = L10n.tr("Localizable", "Scene.RebloggedBy.Title", fallback: "Reblogged By") + /// Boosted By + public static let title = L10n.tr("Localizable", "Scene.RebloggedBy.Title", fallback: "Boosted By") } public enum Register { /// Create Account @@ -1258,8 +1260,8 @@ public enum L10n { public static func whileWeReviewThisYouCanTakeActionAgainstUser(_ p1: Any) -> String { return L10n.tr("Localizable", "Scene.Report.StepFinal.WhileWeReviewThisYouCanTakeActionAgainstUser", String(describing: p1), fallback: "While we review this, you can take action against %@") } - /// You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted. - public static let youWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted = L10n.tr("Localizable", "Scene.Report.StepFinal.YouWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted", fallback: "You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted.") + /// You won’t see their posts or boosts in your home feed. They won’t know they’ve been muted. + public static let youWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted = L10n.tr("Localizable", "Scene.Report.StepFinal.YouWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted", fallback: "You won’t see their posts or boosts in your home feed. They won’t know they’ve been muted.") } public enum StepFour { /// Is there anything else we should know? diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings index 7ed8427ee..0e613d62d 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings @@ -90,13 +90,13 @@ Please check your internet connection."; "Common.Controls.Friendship.EditInfo" = "Edit Info"; "Common.Controls.Friendship.Follow" = "Follow"; "Common.Controls.Friendship.Following" = "Following"; -"Common.Controls.Friendship.HideReblogs" = "Hide Reblogs"; +"Common.Controls.Friendship.HideReblogs" = "Hide Boosts"; "Common.Controls.Friendship.Mute" = "Mute"; "Common.Controls.Friendship.MuteUser" = "Mute %@"; "Common.Controls.Friendship.Muted" = "Muted"; "Common.Controls.Friendship.Pending" = "Pending"; "Common.Controls.Friendship.Request" = "Request"; -"Common.Controls.Friendship.ShowReblogs" = "Show Reblogs"; +"Common.Controls.Friendship.ShowReblogs" = "Show Boosts"; "Common.Controls.Friendship.Unblock" = "Unblock"; "Common.Controls.Friendship.UnblockUser" = "Unblock %@"; "Common.Controls.Friendship.Unmute" = "Unmute"; @@ -109,20 +109,21 @@ Please check your internet connection."; "Common.Controls.Keyboard.SegmentedControl.PreviousSection" = "Previous Section"; "Common.Controls.Keyboard.Timeline.NextStatus" = "Next Post"; "Common.Controls.Keyboard.Timeline.OpenAuthorProfile" = "Open Author's Profile"; -"Common.Controls.Keyboard.Timeline.OpenRebloggerProfile" = "Open Reblogger's Profile"; +"Common.Controls.Keyboard.Timeline.OpenRebloggerProfile" = "Open Booster's Profile"; "Common.Controls.Keyboard.Timeline.OpenStatus" = "Open Post"; "Common.Controls.Keyboard.Timeline.PreviewImage" = "Preview Image"; "Common.Controls.Keyboard.Timeline.PreviousStatus" = "Previous Post"; "Common.Controls.Keyboard.Timeline.ReplyStatus" = "Reply to Post"; "Common.Controls.Keyboard.Timeline.ToggleContentWarning" = "Toggle Content Warning"; "Common.Controls.Keyboard.Timeline.ToggleFavorite" = "Toggle Favorite on Post"; -"Common.Controls.Keyboard.Timeline.ToggleReblog" = "Toggle Reblog on Post"; -"Common.Controls.Status.Actions.A11YLabels.Reblog" = "Re-blog"; -"Common.Controls.Status.Actions.A11YLabels.Unreblog" = "Undo re-blog"; +"Common.Controls.Keyboard.Timeline.ToggleReblog" = "Toggle Boost on Post"; +"Common.Controls.Status.Actions.A11YLabels.Reblog" = "Boost"; +"Common.Controls.Status.Actions.A11YLabels.Unreblog" = "Undo boost"; +"Common.Controls.Status.Actions.CopyLink" = "Copy Link"; "Common.Controls.Status.Actions.Favorite" = "Favorite"; "Common.Controls.Status.Actions.Hide" = "Hide"; "Common.Controls.Status.Actions.Menu" = "Menu"; -"Common.Controls.Status.Actions.Reblog" = "Reblog"; +"Common.Controls.Status.Actions.Reblog" = "Boost"; "Common.Controls.Status.Actions.Reply" = "Reply"; "Common.Controls.Status.Actions.ShareLinkInPost" = "Share Link in Post"; "Common.Controls.Status.Actions.ShowGif" = "Show GIF"; @@ -130,11 +131,11 @@ Please check your internet connection."; "Common.Controls.Status.Actions.ShowVideoPlayer" = "Show video player"; "Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Tap then hold to show menu"; "Common.Controls.Status.Actions.Unfavorite" = "Unfavorite"; -"Common.Controls.Status.Actions.Unreblog" = "Undo reblog"; +"Common.Controls.Status.Actions.Unreblog" = "Undo boost"; "Common.Controls.Status.Buttons.EditHistoryDetail" = "Last edit %@"; "Common.Controls.Status.Buttons.EditHistoryTitle" = "Edit History"; "Common.Controls.Status.Buttons.FavoritesTitle" = "Favorites"; -"Common.Controls.Status.Buttons.ReblogsTitle" = "Reblogs"; +"Common.Controls.Status.Buttons.ReblogsTitle" = "Boosts"; "Common.Controls.Status.ContentWarning" = "Content Warning"; "Common.Controls.Status.EditHistory.OriginalPost" = "Original Post · %@"; "Common.Controls.Status.EditHistory.Title" = "Edit History"; @@ -167,7 +168,7 @@ Please check your internet connection."; "Common.Controls.Status.Translation.TranslatedFrom" = "Translated from %@ using %@"; "Common.Controls.Status.Translation.UnknownLanguage" = "Unknown"; "Common.Controls.Status.Translation.UnknownProvider" = "Unknown"; -"Common.Controls.Status.UserReblogged" = "%@ reblogged"; +"Common.Controls.Status.UserReblogged" = "%@ boosted"; "Common.Controls.Status.UserRepliedTo" = "Replied to %@"; "Common.Controls.Status.Visibility.Direct" = "Only mentioned user can see this post."; "Common.Controls.Status.Visibility.Private" = "Only their followers can see this post."; @@ -319,7 +320,7 @@ uploaded to Mastodon."; "Scene.Notification.NotificationDescription.FollowedYou" = "followed you"; "Scene.Notification.NotificationDescription.MentionedYou" = "mentioned you"; "Scene.Notification.NotificationDescription.PollHasEnded" = "poll has ended"; -"Scene.Notification.NotificationDescription.RebloggedYourPost" = "reblogged your post"; +"Scene.Notification.NotificationDescription.RebloggedYourPost" = "boosted your post"; "Scene.Notification.NotificationDescription.RequestToFollowYou" = "request to follow you"; "Scene.Notification.Title.Everything" = "Everything"; "Scene.Notification.Title.Mentions" = "Mentions"; @@ -361,12 +362,12 @@ uploaded to Mastodon."; "Scene.Profile.RelationshipActionAlert.ConfirmBlockDomain.Title" = "Block domain"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Message" = "Confirm to block %@"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title" = "Block Account"; -"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Message" = "Confirm to hide reblogs"; -"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Title" = "Hide Reblogs"; +"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Message" = "Confirm to hide boosts"; +"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Title" = "Hide Boosts"; "Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Message" = "Confirm to mute %@"; "Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Title" = "Mute Account"; -"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Message" = "Confirm to show reblogs"; -"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Title" = "Show Reblogs"; +"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Message" = "Confirm to show boosts"; +"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Title" = "Show Boosts"; "Scene.Profile.RelationshipActionAlert.ConfirmUnblockDomain.Message" = "Confirm to unblock domain %@"; "Scene.Profile.RelationshipActionAlert.ConfirmUnblockDomain.Title" = "Unblock domain"; "Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Message" = "Confirm to unblock %@"; @@ -378,7 +379,7 @@ uploaded to Mastodon."; "Scene.Profile.SegmentedControl.Posts" = "Posts"; "Scene.Profile.SegmentedControl.PostsAndReplies" = "Posts and Replies"; "Scene.Profile.SegmentedControl.Replies" = "Replies"; -"Scene.RebloggedBy.Title" = "Reblogged By"; +"Scene.RebloggedBy.Title" = "Boosted By"; "Scene.Register.Error.Item.Agreement" = "Agreement"; "Scene.Register.Error.Item.Email" = "Email"; "Scene.Register.Error.Item.Locale" = "Locale"; @@ -431,7 +432,7 @@ uploaded to Mastodon."; "Scene.Report.StepFinal.Unfollowed" = "Unfollowed"; "Scene.Report.StepFinal.WhenYouSeeSomethingYouDontLikeOnMastodonYouCanRemoveThePersonFromYourExperience." = "When you see something you don’t like on Mastodon, you can remove the person from your experience."; "Scene.Report.StepFinal.WhileWeReviewThisYouCanTakeActionAgainstUser" = "While we review this, you can take action against %@"; -"Scene.Report.StepFinal.YouWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted" = "You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted."; +"Scene.Report.StepFinal.YouWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted" = "You won’t see their posts or boosts in your home feed. They won’t know they’ve been muted."; "Scene.Report.StepFour.IsThereAnythingElseWeShouldKnow" = "Is there anything else we should know?"; "Scene.Report.StepFour.Step4Of4" = "Step 4 of 4"; "Scene.Report.StepOne.IDontLikeIt" = "I don’t like it"; diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/StatusAuthorView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/StatusAuthorView.swift index e11dd242d..ee85e3fa9 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/StatusAuthorView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/StatusAuthorView.swift @@ -147,73 +147,68 @@ extension StatusAuthorView { public let isMuting: Bool public let isBlocking: Bool public let isMyself: Bool - public let isBookmarking: Bool + public let isBookmarked: Bool public let isFollowed: Bool public let isTranslationEnabled: Bool public let isTranslated: Bool public let statusLanguage: String? + public let isFavorited: Bool + public let isBoosted: Bool } public func setupAuthorMenu(menuContext: AuthorMenuContext) -> (UIMenu, [UIAccessibilityCustomAction]) { - var actions: [[MastodonMenu.Action]] = [] - var postActions: [MastodonMenu.Action] = [] - var userActions: [MastodonMenu.Action] = [] + var items: [MastodonMenu.Submenu] = [] + + items.append(MastodonMenu.Submenu( + actions: [ + .boostStatus(.init(isBoosted: menuContext.isBoosted)), + .favoriteStatus(.init(isFavorited: menuContext.isFavorited)), + .bookmarkStatus(.init(isBookmarked: menuContext.isBookmarked)), + ], + preferredElementSize: .medium + )) if menuContext.isMyself { - postActions.append(.editStatus) - } + items.append(MastodonMenu.Submenu(actions: [.editStatus])) + } else if menuContext.isTranslationEnabled, + let statusLanguage = menuContext.statusLanguage, + let deviceLanguage = Bundle.main.preferredLocalizations.first, + deviceLanguage != statusLanguage { + let action: MastodonMenu.Action - if menuContext.isTranslationEnabled, - let statusLanguage = menuContext.statusLanguage, - let deviceLanguage = Bundle.main.preferredLocalizations.first, - deviceLanguage != statusLanguage { if menuContext.isTranslated == false { - postActions.append(.translateStatus(.init(language: statusLanguage))) + action = .translateStatus(.init(language: statusLanguage)) } else { - postActions.append(.showOriginal) + action = .showOriginal } + + items.append(MastodonMenu.Submenu(actions: [action])) } - postActions.append(.bookmarkStatus(.init(isBookmarking: menuContext.isBookmarking))) - postActions.append(.shareStatus) - - if menuContext.isMyself == false { - - userActions.append(.followUser(.init( - name: menuContext.name, - isFollowing: menuContext.isFollowed - ))) - - userActions.append(.muteUser(.init( - name: menuContext.name, - isMuting: menuContext.isMuting - ))) - - userActions.append(.blockUser(.init( - name: menuContext.name, - isBlocking: menuContext.isBlocking - ))) - - userActions.append(.reportUser( - .init(name: menuContext.name) - )) - } - - actions.append(postActions) - actions.append(userActions) + items.append(MastodonMenu.Submenu(actions: [.shareStatus, .openInBrowser, .copyLink])) if menuContext.isMyself { - actions.append([.deleteStatus]) + items.append(MastodonMenu.Submenu(actions: [.deleteStatus])) + } else { + items.append(MastodonMenu.Submenu(actions: [ + .followUser(.init(name: menuContext.name, isFollowing: menuContext.isFollowed)), + .muteUser(.init( name: menuContext.name, isMuting: menuContext.isMuting)) + ])) + + items.append(MastodonMenu.Submenu(actions: [ + .blockUser(.init(name: menuContext.name, isBlocking: menuContext.isBlocking)), + .reportUser(.init(name: menuContext.name)) + ])) } let menu = MastodonMenu.setupMenu( - actions: actions, + submenus: items, delegate: self.statusView! ) let accessibilityActions = MastodonMenu.setupAccessibilityActions( - actions: actions, + actions: items.compactMap { $0.actions } , delegate: self.statusView! ) diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift index 6ebc265de..cce4f0f60 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift @@ -686,13 +686,18 @@ extension StatusView.ViewModel { $language ) + let publisherTwo = Publishers.CombineLatest3( + $isBookmark, $isFavorite, $isReblog + ) + Publishers.CombineLatest3( publisherOne.eraseToAnyPublisher(), - $isBookmark, + publisherTwo.eraseToAnyPublisher(), publishersThree.eraseToAnyPublisher() ).eraseToAnyPublisher() - .sink { tupleOne, isBookmark, tupleThree in + .sink { tupleOne, tupleTwo, tupleThree in let (authorName, authorId, isMyself) = tupleOne + let (isBookmark, isFavorite, isBoosted) = tupleTwo let (translatedFromLanguage, language) = tupleThree guard let name = authorName?.string, let authorId = authorId, let context = self.context, let authContext = self.authContext else { @@ -705,8 +710,8 @@ extension StatusView.ViewModel { let isTranslationEnabled = instance?.isTranslationEnabled ?? false authorView.menuButton.menu = UIMenu(children: [ - UIDeferredMenuElement({ menuElement in - + UIDeferredMenuElement.uncached({ menuElement in + let domain = authContext.mastodonAuthenticationBox.domain Task { @MainActor in @@ -724,11 +729,13 @@ extension StatusView.ViewModel { isMuting: rel.muting, isBlocking: rel.blocking, isMyself: isMyself, - isBookmarking: isBookmark, - isFollowed: rel.followedBy, + isBookmarked: isBookmark, + isFollowed: rel.following, isTranslationEnabled: isTranslationEnabled, isTranslated: translatedFromLanguage != nil, - statusLanguage: language + statusLanguage: language, + isFavorited: isFavorite, + isBoosted: isBoosted ) let (menu, actions) = authorView.setupAuthorMenu(menuContext: menuContext) @@ -737,7 +744,11 @@ extension StatusView.ViewModel { menuElement(menu.children) } } else { - menuElement(MastodonMenu.setupMenu(actions: [[.shareStatus]], delegate: statusView).children) + menuElement( + MastodonMenu.setupMenu( + submenus: [MastodonMenu.Submenu(actions: [.shareStatus])], + delegate: statusView).children + ) } } }) diff --git a/MastodonSDK/Sources/MastodonUI/View/Menu/MastodonMenu.swift b/MastodonSDK/Sources/MastodonUI/View/Menu/MastodonMenu.swift index 72f2877a9..40d13c787 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Menu/MastodonMenu.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Menu/MastodonMenu.swift @@ -13,19 +13,33 @@ public protocol MastodonMenuDelegate: AnyObject { } public enum MastodonMenu { + + public struct Submenu { + public let actions: [Action] + public let options: UIMenu.Options + public let preferredElementSize: UIMenu.ElementSize + + public init(actions: [Action], options: UIMenu.Options = .displayInline, preferredElementSize: UIMenu.ElementSize = .large) { + self.actions = actions + self.options = options + self.preferredElementSize = preferredElementSize + } + } + public static func setupMenu( - actions: [[Action]], + submenus: [Submenu], delegate: MastodonMenuDelegate ) -> UIMenu { var children: [UIMenuElement] = [] - for actionGroup in actions { + for item in submenus { var submenuChildren: [UIMenuElement] = [] - for action in actionGroup { + for action in item.actions { let element = action.build(delegate: delegate).menuElement submenuChildren.append(element) } - let submenu = UIMenu(options: .displayInline, children: submenuChildren) + let submenu = UIMenu(options: item.options, children: submenuChildren) + submenu.preferredElementSize = item.preferredElementSize children.append(submenu) } @@ -60,6 +74,10 @@ extension MastodonMenu { case editStatus case followUser(FollowUserActionContext) case blockDomain(BlockDomainActionContext) + case boostStatus(BoostStatusActionContext) + case favoriteStatus(FavoriteStatusActionContext) + case copyLink + case openInBrowser func build(delegate: MastodonMenuDelegate) -> LabeledAction { switch self { @@ -96,7 +114,7 @@ extension MastodonMenu { title = L10n.Common.Controls.Friendship.blockUser(context.name) image = UIImage(systemName: "hand.raised") } - let blockAction = LabeledAction(title: title, image: image) { [weak delegate] in + let blockAction = LabeledAction(title: title, image: image, attributes: .destructive) { [weak delegate] in guard let delegate = delegate else { return } delegate.menuAction(self) } @@ -104,7 +122,8 @@ extension MastodonMenu { case .reportUser(let context): let reportAction = LabeledAction( title: L10n.Common.Controls.Actions.reportUser(context.name), - image: UIImage(systemName: "flag") + image: UIImage(systemName: "flag"), + attributes: .destructive ) { [weak delegate] in guard let delegate = delegate else { return } delegate.menuAction(self) @@ -122,7 +141,7 @@ extension MastodonMenu { case .bookmarkStatus(let context): let title: String let image: UIImage? - if context.isBookmarking { + if context.isBookmarked { title = L10n.Common.Controls.Actions.removeBookmark image = UIImage(systemName: "bookmark.slash.fill") } else { @@ -215,6 +234,44 @@ extension MastodonMenu { delegate.menuAction(self) } return action + + case .boostStatus(let context): + let title: String + + if context.isBoosted { + title = L10n.Common.Controls.Status.Actions.unreblog + } else { + title = L10n.Common.Controls.Status.Actions.reblog + } + + return LabeledAction(title: title, image: UIImage(systemName: "arrow.2.squarepath")) { [weak delegate] in + delegate?.menuAction(self) + } + case .favoriteStatus(let context): + let title: String + let image: UIImage? + + if context.isFavorited { + title = L10n.Common.Controls.Status.Actions.unfavorite + image = UIImage(systemName: "star.slash") + } else { + title = L10n.Common.Controls.Status.Actions.favorite + image = UIImage(systemName: "star") + } + + return LabeledAction(title: title, image: image) { [weak delegate] in + delegate?.menuAction(self) + } + + case .copyLink: + return LabeledAction(title: L10n.Common.Controls.Status.Actions.copyLink, image: UIImage(systemName: "doc.on.doc")) { [weak delegate] in + delegate?.menuAction(self) + } + + case .openInBrowser: + return LabeledAction(title: L10n.Common.Controls.Actions.openInBrowser, image: UIImage(systemName: "safari")) { [weak delegate] in + delegate?.menuAction(self) + } } } } @@ -242,10 +299,10 @@ extension MastodonMenu { } public struct BookmarkStatusActionContext { - public let isBookmarking: Bool + public let isBookmarked: Bool - public init(isBookmarking: Bool) { - self.isBookmarking = isBookmarking + public init(isBookmarked: Bool) { + self.isBookmarked = isBookmarked } } @@ -301,4 +358,21 @@ extension MastodonMenu { self.isBlocking = isBlocking } } + + public struct BoostStatusActionContext { + public let isBoosted: Bool + + public init(isBoosted: Bool) { + self.isBoosted = isBoosted + } + } + + public struct FavoriteStatusActionContext { + public let isFavorited: Bool + + public init(isFavorited: Bool) { + self.isFavorited = isFavorited + } + } + }