1
0
mirror of https://github.com/mastodon/mastodon-ios.git synced 2025-01-26 15:24:54 +01:00

Add “Copy,” “Share,” and “Share Link in Post” actions to cards

This commit is contained in:
Jed Fox 2022-12-03 13:38:32 -05:00
parent 3661b5ce90
commit 17b39da316
No known key found for this signature in database
GPG Key ID: 0B61D18EA54B47E1
10 changed files with 169 additions and 8 deletions

View File

@ -78,6 +78,7 @@
"sign_up": "Create account",
"see_more": "See More",
"preview": "Preview",
"copy": "Copy",
"share": "Share",
"share_user": "Share %s",
"share_post": "Share Post",
@ -133,6 +134,7 @@
"media_content_warning": "Tap anywhere to reveal",
"tap_to_reveal": "Tap to reveal",
"load_embed": "Load Embed",
"link_via_user": "%s via %s",
"poll": {
"vote": "Vote",
"closed": "Closed"
@ -154,6 +156,7 @@
"show_image": "Show image",
"show_gif": "Show GIF",
"show_video_player": "Show video player",
"share_link_in_post": "Share Link in Post",
"tap_then_hold_to_show_menu": "Tap then hold to show menu"
},
"tag": {

View File

@ -78,6 +78,7 @@
"sign_up": "Create account",
"see_more": "See More",
"preview": "Preview",
"copy": "Copy",
"share": "Share",
"share_user": "Share %s",
"share_post": "Share Post",
@ -133,6 +134,7 @@
"media_content_warning": "Tap anywhere to reveal",
"tap_to_reveal": "Tap to reveal",
"load_embed": "Load Embed",
"link_via_user": "%s via %s",
"poll": {
"vote": "Vote",
"closed": "Closed"
@ -154,6 +156,7 @@
"show_image": "Show image",
"show_gif": "Show GIF",
"show_video_player": "Show video player",
"share_link_in_post": "Share Link in Post",
"tap_then_hold_to_show_menu": "Tap then hold to show menu"
},
"tag": {

View File

@ -10,6 +10,9 @@ import CoreDataStack
import MetaTextKit
import MastodonCore
import MastodonUI
import MastodonLocalization
import MastodonAsset
import LinkPresentation
// MARK: - header
extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider {
@ -150,6 +153,99 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthConte
}
}
func tableViewCell(
_ cell: UITableViewCell,
statusView: StatusView,
cardControl: StatusCardControl,
didTapURL url: URL
) {
Task {
let source = DataSourceItem.Source(tableViewCell: cell, indexPath: nil)
guard let item = await item(from: source) else {
assertionFailure()
return
}
guard case let .status(status) = item else {
assertionFailure("only works for status data provider")
return
}
await DataSourceFacade.responseToURLAction(
provider: self,
status: status,
url: url
)
}
}
func tableViewCell(
_ cell: UITableViewCell,
statusView: StatusView,
cardControlMenu statusCardControl: StatusCardControl
) -> UIMenu? {
guard let card = statusView.viewModel.card,
let url = card.url else {
return nil
}
return UIMenu(children: [
UIAction(
title: L10n.Common.Controls.Actions.copy,
image: UIImage(systemName: "doc.on.doc")
) { _ in
UIPasteboard.general.url = url
},
UIAction(
title: L10n.Common.Controls.Actions.share,
image: Asset.Arrow.squareAndArrowUp.image.withRenderingMode(.alwaysTemplate)
) { _ in
Task {
await MainActor.run {
let activityViewController = UIActivityViewController(
activityItems: [
URLActivityItemWithMetadata(url: url) { metadata in
metadata.title = card.title
if let image = card.imageURL {
metadata.iconProvider = ImageProvider(url: image, filter: nil).itemProvider
}
}
],
applicationActivities: []
)
self.coordinator.present(
scene: .activityViewController(
activityViewController: activityViewController,
sourceView: statusCardControl, barButtonItem: nil
),
from: self,
transition: .activityViewControllerPresent(animated: true)
)
}
}
},
UIAction(
title: L10n.Common.Controls.Status.Actions.shareLinkInPost,
image: Asset.ObjectsAndTools.squareAndPencil.image.withRenderingMode(.alwaysTemplate)
) { _ in
Task {
await MainActor.run {
self.coordinator.present(
scene: .compose(viewModel: ComposeViewModel(
context: self.context,
authContext: self.authContext,
destination: .topLevel,
initialContent: L10n.Common.Controls.Status.linkViaUser(url.absoluteString, "@" + (statusView.viewModel.authorUsername ?? ""))
)),
from: self,
transition: .modal(animated: true)
)
}
}
}
])
}
}
// MARK: - media

View File

@ -37,6 +37,8 @@ protocol StatusTableViewCellDelegate: AnyObject, AutoGenerateProtocolDelegate {
func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, mediaGridContainerView: MediaGridContainerView, mediaSensitiveButtonDidPressed button: UIButton)
func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, statusMetricView: StatusMetricView, reblogButtonDidPressed button: UIButton)
func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, statusMetricView: StatusMetricView, favoriteButtonDidPressed button: UIButton)
func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, cardControl: StatusCardControl, didTapURL url: URL)
func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, cardControlMenu: StatusCardControl) -> UIMenu?
func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, accessibilityActivate: Void)
// sourcery:end
}
@ -102,6 +104,14 @@ extension StatusViewDelegate where Self: StatusViewContainerTableViewCell {
delegate?.tableViewCell(self, statusView: statusView, statusMetricView: statusMetricView, favoriteButtonDidPressed: button)
}
func statusView(_ statusView: StatusView, cardControl: StatusCardControl, didTapURL url: URL) {
delegate?.tableViewCell(self, statusView: statusView, cardControl: cardControl, didTapURL: url)
}
func statusView(_ statusView: StatusView, cardControlMenu: StatusCardControl) -> UIMenu? {
return delegate?.tableViewCell(self, statusView: statusView, cardControlMenu: cardControlMenu)
}
func statusView(_ statusView: StatusView, accessibilityActivate: Void) {
delegate?.tableViewCell(self, statusView: statusView, accessibilityActivate: accessibilityActivate)
}

View File

@ -112,6 +112,8 @@ public enum L10n {
public static let confirm = L10n.tr("Localizable", "Common.Controls.Actions.Confirm", fallback: "Confirm")
/// Continue
public static let `continue` = L10n.tr("Localizable", "Common.Controls.Actions.Continue", fallback: "Continue")
/// Copy
public static let copy = L10n.tr("Localizable", "Common.Controls.Actions.Copy", fallback: "Copy")
/// Copy Photo
public static let copyPhoto = L10n.tr("Localizable", "Common.Controls.Actions.CopyPhoto", fallback: "Copy Photo")
/// Delete
@ -272,6 +274,10 @@ public enum L10n {
public enum Status {
/// Content Warning
public static let contentWarning = L10n.tr("Localizable", "Common.Controls.Status.ContentWarning", fallback: "Content Warning")
/// %@ via %@
public static func linkViaUser(_ p1: Any, _ p2: Any) -> String {
return L10n.tr("Localizable", "Common.Controls.Status.LinkViaUser", String(describing: p1), String(describing: p2), fallback: "%@ via %@")
}
/// Load Embed
public static let loadEmbed = L10n.tr("Localizable", "Common.Controls.Status.LoadEmbed", fallback: "Load Embed")
/// Tap anywhere to reveal
@ -303,6 +309,8 @@ public enum L10n {
public static let reblog = L10n.tr("Localizable", "Common.Controls.Status.Actions.Reblog", fallback: "Reblog")
/// Reply
public static let reply = L10n.tr("Localizable", "Common.Controls.Status.Actions.Reply", fallback: "Reply")
/// Share Link in Post
public static let shareLinkInPost = L10n.tr("Localizable", "Common.Controls.Status.Actions.ShareLinkInPost", fallback: "Share Link in Post")
/// Show GIF
public static let showGif = L10n.tr("Localizable", "Common.Controls.Status.Actions.ShowGif", fallback: "Show GIF")
/// Show image

View File

@ -31,6 +31,7 @@ Please check your internet connection.";
"Common.Controls.Actions.Compose" = "Compose";
"Common.Controls.Actions.Confirm" = "Confirm";
"Common.Controls.Actions.Continue" = "Continue";
"Common.Controls.Actions.Copy" = "Copy";
"Common.Controls.Actions.CopyPhoto" = "Copy Photo";
"Common.Controls.Actions.Delete" = "Delete";
"Common.Controls.Actions.Discard" = "Discard";
@ -100,6 +101,7 @@ Please check your internet connection.";
"Common.Controls.Status.Actions.Menu" = "Menu";
"Common.Controls.Status.Actions.Reblog" = "Reblog";
"Common.Controls.Status.Actions.Reply" = "Reply";
"Common.Controls.Status.Actions.ShareLinkInPost" = "Share Link in Post";
"Common.Controls.Status.Actions.ShowGif" = "Show GIF";
"Common.Controls.Status.Actions.ShowImage" = "Show image";
"Common.Controls.Status.Actions.ShowVideoPlayer" = "Show video player";
@ -107,6 +109,7 @@ Please check your internet connection.";
"Common.Controls.Status.Actions.Unfavorite" = "Unfavorite";
"Common.Controls.Status.Actions.Unreblog" = "Undo reblog";
"Common.Controls.Status.ContentWarning" = "Content Warning";
"Common.Controls.Status.LinkViaUser" = "%@ via %@";
"Common.Controls.Status.LoadEmbed" = "Load Embed";
"Common.Controls.Status.MediaContentWarning" = "Tap anywhere to reveal";
"Common.Controls.Status.MetaEntity.Email" = "Email address: %@";

View File

@ -611,6 +611,15 @@ extension NotificationView: StatusViewDelegate {
assertionFailure()
}
public func statusView(_ statusView: StatusView, cardControl: StatusCardControl, didTapURL url: URL) {
assertionFailure()
}
public func statusView(_ statusView: StatusView, cardControlMenu: StatusCardControl) -> UIMenu? {
assertionFailure()
return nil
}
}
// MARK: - MastodonMenuDelegate

View File

@ -14,8 +14,13 @@ import CoreDataStack
import UIKit
import WebKit
public protocol StatusCardControlDelegate: AnyObject {
func statusCardControl(_ statusCardControl: StatusCardControl, didTapURL url: URL)
func statusCardControlMenu(_ statusCardControl: StatusCardControl) -> UIMenu?
}
public final class StatusCardControl: UIControl {
public var urlToOpen = PassthroughSubject<URL, Never>()
public weak var delegate: StatusCardControlDelegate?
private var disposeBag = Set<AnyCancellable>()
@ -130,6 +135,8 @@ public final class StatusCardControl: UIControl {
showEmbedButton.centerXAnchor.constraint(equalTo: imageView.centerXAnchor),
showEmbedButton.centerYAnchor.constraint(equalTo: imageView.centerYAnchor),
])
addInteraction(UIContextMenuInteraction(delegate: self))
}
required init?(coder: NSCoder) {
@ -238,6 +245,7 @@ public final class StatusCardControl: UIControl {
}
}
// MARK: WKWebView delegates
extension StatusCardControl: WKNavigationDelegate, WKUIDelegate {
fileprivate func showWebView() {
let webView = setupWebView()
@ -279,7 +287,7 @@ extension StatusCardControl: WKNavigationDelegate, WKUIDelegate {
navigationAction.navigationType == .linkActivated || navigationAction.navigationType == .other,
let url = navigationAction.request.url,
url.absoluteString != "about:blank" {
urlToOpen.send(url)
delegate?.statusCardControl(self, didTapURL: url)
return .cancel
}
return .allow
@ -291,6 +299,19 @@ extension StatusCardControl: WKNavigationDelegate, WKUIDelegate {
}
}
// MARK: UIContextMenuInteractionDelegate
extension StatusCardControl {
public override func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { elements in
self.delegate?.statusCardControlMenu(self)
}
}
public override func contextMenuInteraction(_ interaction: UIContextMenuInteraction, previewForDismissingMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
UITargetedPreview(view: self)
}
}
private extension StatusCardControl {
enum Layout: Equatable {
case compact

View File

@ -494,12 +494,6 @@ extension StatusView.ViewModel {
statusView.setStatusCardControlDisplay()
}
.store(in: &disposeBag)
statusView.statusCardControl.urlToOpen
.sink { url in
statusView.delegate?.statusView(statusView, didTapCardWithURL: url)
}
.store(in: &disposeBag)
}
private func bindToolbar(statusView: StatusView) {

View File

@ -33,6 +33,8 @@ public protocol StatusViewDelegate: AnyObject {
func statusView(_ statusView: StatusView, mediaGridContainerView: MediaGridContainerView, mediaSensitiveButtonDidPressed button: UIButton)
func statusView(_ statusView: StatusView, statusMetricView: StatusMetricView, reblogButtonDidPressed button: UIButton)
func statusView(_ statusView: StatusView, statusMetricView: StatusMetricView, favoriteButtonDidPressed button: UIButton)
func statusView(_ statusView: StatusView, cardControl: StatusCardControl, didTapURL url: URL)
func statusView(_ statusView: StatusView, cardControlMenu: StatusCardControl) -> UIMenu?
// a11y
func statusView(_ statusView: StatusView, accessibilityActivate: Void)
@ -264,6 +266,7 @@ extension StatusView {
// card
statusCardControl.addTarget(self, action: #selector(statusCardControlPressed), for: .touchUpInside)
statusCardControl.delegate = self
// media
mediaGridContainerView.delegate = self
@ -667,6 +670,17 @@ extension StatusView: MastodonMenuDelegate {
}
}
// MARK: StatusCardControlDelegate
extension StatusView: StatusCardControlDelegate {
public func statusCardControl(_ statusCardControl: StatusCardControl, didTapURL url: URL) {
delegate?.statusView(self, cardControl: statusCardControl, didTapURL: url)
}
public func statusCardControlMenu(_ statusCardControl: StatusCardControl) -> UIMenu? {
delegate?.statusView(self, cardControlMenu: statusCardControl)
}
}
#if DEBUG
import SwiftUI