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:
parent
3661b5ce90
commit
17b39da316
@ -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": {
|
||||
|
@ -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": {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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: %@";
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user