From ba7955bdb5cf49cdfde49cc108544991c5c74535 Mon Sep 17 00:00:00 2001 From: Kyle Bashour Date: Thu, 24 Nov 2022 07:48:07 -0800 Subject: [PATCH] Handle taps --- .../Provider/DataSourceFacade+Meta.swift | 18 ++---- .../Provider/DataSourceFacade+URL.swift | 32 +++++++++++ ...er+NotificationTableViewCellDelegate.swift | 55 +++++++++++++++++++ ...Provider+StatusTableViewCellDelegate.swift | 31 ++++++++++- .../NotificationTableViewCellDelegate.swift | 10 ++++ .../StatusTableViewCellDelegate.swift | 5 ++ .../View/Content/LinkPreviewButton.swift | 20 ++++++- .../View/Content/NotificationView.swift | 14 ++++- .../MastodonUI/View/Content/StatusView.swift | 15 ++++- 9 files changed, 184 insertions(+), 16 deletions(-) create mode 100644 Mastodon/Protocol/Provider/DataSourceFacade+URL.swift diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Meta.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Meta.swift index dd3c4903a..ca3bbd474 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Meta.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Meta.swift @@ -48,18 +48,12 @@ extension DataSourceFacade { assertionFailure() return } - let domain = provider.authContext.mastodonAuthenticationBox.domain - if url.host == domain, - url.pathComponents.count >= 4, - url.pathComponents[0] == "/", - url.pathComponents[1] == "web", - url.pathComponents[2] == "statuses" { - let statusID = url.pathComponents[3] - let threadViewModel = RemoteThreadViewModel(context: provider.context, authContext: provider.authContext, statusID: statusID) - _ = await provider.coordinator.present(scene: .thread(viewModel: threadViewModel), from: nil, transition: .show) - } else { - _ = await provider.coordinator.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil)) - } + + await responseToURLAction( + provider: provider, + status: status, + url: url + ) case .hashtag(_, let hashtag, _): let hashtagTimelineViewModel = HashtagTimelineViewModel(context: provider.context, authContext: provider.authContext, hashtag: hashtag) _ = await provider.coordinator.present(scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel), from: provider, transition: .show) diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+URL.swift b/Mastodon/Protocol/Provider/DataSourceFacade+URL.swift new file mode 100644 index 000000000..a65de9537 --- /dev/null +++ b/Mastodon/Protocol/Provider/DataSourceFacade+URL.swift @@ -0,0 +1,32 @@ +// +// DataSourceFacade+URL.swift +// Mastodon +// +// Created by Kyle Bashour on 11/24/22. +// + +import Foundation +import CoreDataStack +import MetaTextKit +import MastodonCore + +extension DataSourceFacade { + static func responseToURLAction( + provider: DataSourceProvider & AuthContextProvider, + status: ManagedObjectRecord, + url: URL + ) async { + let domain = provider.authContext.mastodonAuthenticationBox.domain + if url.host == domain, + url.pathComponents.count >= 4, + url.pathComponents[0] == "/", + url.pathComponents[1] == "web", + url.pathComponents[2] == "statuses" { + let statusID = url.pathComponents[3] + let threadViewModel = RemoteThreadViewModel(context: provider.context, authContext: provider.authContext, statusID: statusID) + _ = await provider.coordinator.present(scene: .thread(viewModel: threadViewModel), from: nil, transition: .show) + } else { + _ = await provider.coordinator.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil)) + } + } +} diff --git a/Mastodon/Protocol/Provider/DataSourceProvider+NotificationTableViewCellDelegate.swift b/Mastodon/Protocol/Provider/DataSourceProvider+NotificationTableViewCellDelegate.swift index e868f418f..279cb562e 100644 --- a/Mastodon/Protocol/Provider/DataSourceProvider+NotificationTableViewCellDelegate.swift +++ b/Mastodon/Protocol/Provider/DataSourceProvider+NotificationTableViewCellDelegate.swift @@ -516,6 +516,61 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider & Aut } +// MARK: - card +extension NotificationTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider { + + func tableViewCell( + _ cell: UITableViewCell, + notificationView: NotificationView, + statusView: StatusView, + didTapCardWithURL 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, + notificationView: NotificationView, + quoteStatusView: StatusView, + didTapCardWithURL 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 + ) + } + } + +} + // MARK: a11y extension NotificationTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider { func tableViewCell(_ cell: UITableViewCell, notificationView: NotificationView, accessibilityActivate: Void) { diff --git a/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift b/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift index c157b7086..721263f30 100644 --- a/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift +++ b/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift @@ -120,7 +120,36 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthConte ) } } - + +} + +// MARK: - card +extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider { + + func tableViewCell( + _ cell: UITableViewCell, + statusView: StatusView, + didTapCardWithURL 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 + ) + } + } + } // MARK: - media diff --git a/Mastodon/Scene/Notification/Cell/NotificationTableViewCellDelegate.swift b/Mastodon/Scene/Notification/Cell/NotificationTableViewCellDelegate.swift index 7a603d5f0..45ad59334 100644 --- a/Mastodon/Scene/Notification/Cell/NotificationTableViewCellDelegate.swift +++ b/Mastodon/Scene/Notification/Cell/NotificationTableViewCellDelegate.swift @@ -28,11 +28,13 @@ protocol NotificationTableViewCellDelegate: AnyObject, AutoGenerateProtocolDeleg func tableViewCell(_ cell: UITableViewCell, notificationView: NotificationView, acceptFollowRequestButtonDidPressed button: UIButton) func tableViewCell(_ cell: UITableViewCell, notificationView: NotificationView, rejectFollowRequestButtonDidPressed button: UIButton) func tableViewCell(_ cell: UITableViewCell, notificationView: NotificationView, statusView: StatusView, metaText: MetaText, didSelectMeta meta: Meta) + func tableViewCell(_ cell: UITableViewCell, notificationView: NotificationView, statusView: StatusView, didTapCardWithURL url: URL) func tableViewCell(_ cell: UITableViewCell, notificationView: NotificationView, statusView: StatusView, spoilerOverlayViewDidPressed overlayView: SpoilerOverlayView) func tableViewCell(_ cell: UITableViewCell, notificationView: NotificationView, statusView: StatusView, mediaGridContainerView: MediaGridContainerView, mediaView: MediaView, didSelectMediaViewAt index: Int) func tableViewCell(_ cell: UITableViewCell, notificationView: NotificationView, statusView: StatusView, actionToolbarContainer: ActionToolbarContainer, buttonDidPressed button: UIButton, action: ActionToolbarContainer.Action) func tableViewCell(_ cell: UITableViewCell, notificationView: NotificationView, quoteStatusView: StatusView, authorAvatarButtonDidPressed button: AvatarButton) func tableViewCell(_ cell: UITableViewCell, notificationView: NotificationView, quoteStatusView: StatusView, metaText: MetaText, didSelectMeta meta: Meta) + func tableViewCell(_ cell: UITableViewCell, notificationView: NotificationView, quoteStatusView: StatusView, didTapCardWithURL url: URL) func tableViewCell(_ cell: UITableViewCell, notificationView: NotificationView, quoteStatusView: StatusView, spoilerOverlayViewDidPressed overlayView: SpoilerOverlayView) func tableViewCell(_ cell: UITableViewCell, notificationView: NotificationView, quoteStatusView: StatusView, mediaGridContainerView: MediaGridContainerView, mediaView: MediaView, didSelectMediaViewAt index: Int) func tableViewCell(_ cell: UITableViewCell, notificationView: NotificationView, accessibilityActivate: Void) @@ -63,6 +65,10 @@ extension NotificationViewDelegate where Self: NotificationViewContainerTableVie delegate?.tableViewCell(self, notificationView: notificationView, statusView: statusView, metaText: metaText, didSelectMeta: meta) } + func notificationView(_ notificationView: NotificationView, statusView: StatusView, didTapCardWithURL url: URL) { + delegate?.tableViewCell(self, notificationView: notificationView, statusView: statusView, didTapCardWithURL: url) + } + func notificationView(_ notificationView: NotificationView, statusView: StatusView, spoilerOverlayViewDidPressed overlayView: SpoilerOverlayView) { delegate?.tableViewCell(self, notificationView: notificationView, statusView: statusView, spoilerOverlayViewDidPressed: overlayView) } @@ -83,6 +89,10 @@ extension NotificationViewDelegate where Self: NotificationViewContainerTableVie delegate?.tableViewCell(self, notificationView: notificationView, quoteStatusView: quoteStatusView, metaText: metaText, didSelectMeta: meta) } + func notificationView(_ notificationView: NotificationView, quoteStatusView: StatusView, didTapCardWithURL url: URL) { + delegate?.tableViewCell(self, notificationView: notificationView, quoteStatusView: quoteStatusView, didTapCardWithURL: url) + } + func notificationView(_ notificationView: NotificationView, quoteStatusView: StatusView, spoilerOverlayViewDidPressed overlayView: SpoilerOverlayView) { delegate?.tableViewCell(self, notificationView: notificationView, quoteStatusView: quoteStatusView, spoilerOverlayViewDidPressed: overlayView) } diff --git a/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCellDelegate.swift b/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCellDelegate.swift index 034985c03..e76ba5006 100644 --- a/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCellDelegate.swift +++ b/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCellDelegate.swift @@ -27,6 +27,7 @@ protocol StatusTableViewCellDelegate: AnyObject, AutoGenerateProtocolDelegate { func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, authorAvatarButtonDidPressed button: AvatarButton) func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, contentSensitiveeToggleButtonDidPressed button: UIButton) func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, metaText: MetaText, didSelectMeta meta: Meta) + func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, didTapCardWithURL url: URL) func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, mediaGridContainerView: MediaGridContainerView, mediaView: MediaView, didSelectMediaViewAt index: Int) func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, pollTableView tableView: UITableView, didSelectRowAt indexPath: IndexPath) func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, pollVoteButtonPressed button: UIButton) @@ -61,6 +62,10 @@ extension StatusViewDelegate where Self: StatusViewContainerTableViewCell { delegate?.tableViewCell(self, statusView: statusView, metaText: metaText, didSelectMeta: meta) } + func statusView(_ statusView: StatusView, didTapCardWithURL url: URL) { + delegate?.tableViewCell(self, statusView: statusView, didTapCardWithURL: url) + } + func statusView(_ statusView: StatusView, mediaGridContainerView: MediaGridContainerView, mediaView: MediaView, didSelectMediaViewAt index: Int) { delegate?.tableViewCell(self, statusView: statusView, mediaGridContainerView: mediaGridContainerView, mediaView: mediaView, didSelectMediaViewAt: index) } diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/LinkPreviewButton.swift b/MastodonSDK/Sources/MastodonUI/View/Content/LinkPreviewButton.swift index 4cb2dca6d..fc5d8b247 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/LinkPreviewButton.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/LinkPreviewButton.swift @@ -16,6 +16,7 @@ public final class LinkPreviewButton: UIControl { private let containerStackView = UIStackView() private let labelStackView = UIStackView() + private let highlightView = UIView() private let imageView = UIImageView() private let titleLabel = UILabel() private let linkLabel = UILabel() @@ -30,6 +31,12 @@ public final class LinkPreviewButton: UIControl { imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: 21 / 40), ] + public override var isHighlighted: Bool { + didSet { + highlightView.isHidden = !isHighlighted + } + } + public override init(frame: CGRect) { super.init(frame: frame) @@ -38,6 +45,9 @@ public final class LinkPreviewButton: UIControl { layer.cornerRadius = 10 layer.borderColor = ThemeService.shared.currentTheme.value.separator.cgColor + highlightView.backgroundColor = UIColor.black.withAlphaComponent(0.1) + highlightView.isHidden = true + titleLabel.numberOfLines = 2 titleLabel.setContentCompressionResistancePriority(.defaultLow - 1, for: .horizontal) titleLabel.text = "This is where I'd put a title... if I had one" @@ -61,17 +71,23 @@ public final class LinkPreviewButton: UIControl { containerStackView.addArrangedSubview(imageView) containerStackView.addArrangedSubview(labelStackView) - containerStackView.distribution = .fill addSubview(containerStackView) + addSubview(highlightView) + containerStackView.isUserInteractionEnabled = false containerStackView.translatesAutoresizingMaskIntoConstraints = false + highlightView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ containerStackView.topAnchor.constraint(equalTo: topAnchor), containerStackView.bottomAnchor.constraint(equalTo: bottomAnchor), containerStackView.leadingAnchor.constraint(equalTo: leadingAnchor), containerStackView.trailingAnchor.constraint(equalTo: trailingAnchor), + highlightView.topAnchor.constraint(equalTo: topAnchor), + highlightView.bottomAnchor.constraint(equalTo: bottomAnchor), + highlightView.leadingAnchor.constraint(equalTo: leadingAnchor), + highlightView.trailingAnchor.constraint(equalTo: trailingAnchor), ]) } @@ -100,10 +116,12 @@ public final class LinkPreviewButton: UIControl { if isCompact { containerStackView.alignment = .center containerStackView.axis = .horizontal + containerStackView.distribution = .fill NSLayoutConstraint.activate(compactImageConstraints) } else { containerStackView.alignment = .fill containerStackView.axis = .vertical + containerStackView.distribution = .equalSpacing NSLayoutConstraint.activate(largeImageConstraints) } } diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView.swift index ddc1add5c..eb077d6db 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView.swift @@ -22,6 +22,7 @@ public protocol NotificationViewDelegate: AnyObject { func notificationView(_ notificationView: NotificationView, rejectFollowRequestButtonDidPressed button: UIButton) func notificationView(_ notificationView: NotificationView, statusView: StatusView, metaText: MetaText, didSelectMeta meta: Meta) + func notificationView(_ notificationView: NotificationView, statusView: StatusView, didTapCardWithURL url: URL) func notificationView(_ notificationView: NotificationView, statusView: StatusView, spoilerOverlayViewDidPressed overlayView: SpoilerOverlayView) func notificationView(_ notificationView: NotificationView, statusView: StatusView, mediaGridContainerView: MediaGridContainerView, mediaView: MediaView, didSelectMediaViewAt index: Int) @@ -29,6 +30,7 @@ public protocol NotificationViewDelegate: AnyObject { func notificationView(_ notificationView: NotificationView, quoteStatusView: StatusView, authorAvatarButtonDidPressed button: AvatarButton) func notificationView(_ notificationView: NotificationView, quoteStatusView: StatusView, metaText: MetaText, didSelectMeta meta: Meta) + func notificationView(_ notificationView: NotificationView, quoteStatusView: StatusView, didTapCardWithURL url: URL) func notificationView(_ notificationView: NotificationView, quoteStatusView: StatusView, spoilerOverlayViewDidPressed overlayView: SpoilerOverlayView) func notificationView(_ notificationView: NotificationView, quoteStatusView: StatusView, mediaGridContainerView: MediaGridContainerView, mediaView: MediaView, didSelectMediaViewAt index: Int) @@ -496,7 +498,17 @@ extension NotificationView { // MARK: - StatusViewDelegate extension NotificationView: StatusViewDelegate { - + public func statusView(_ statusView: StatusView, didTapCardWithURL url: URL) { + switch statusView { + case self.statusView: + delegate?.notificationView(self, statusView: statusView, didTapCardWithURL: url) + case quoteStatusView: + delegate?.notificationView(self, quoteStatusView: statusView, didTapCardWithURL: url) + default: + assertionFailure() + } + } + public func statusView(_ statusView: StatusView, headerDidPressed header: UIView) { // do nothing } diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView.swift index 3e90e03d4..a2621badd 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView.swift @@ -23,6 +23,7 @@ public protocol StatusViewDelegate: AnyObject { func statusView(_ statusView: StatusView, authorAvatarButtonDidPressed button: AvatarButton) func statusView(_ statusView: StatusView, contentSensitiveeToggleButtonDidPressed button: UIButton) func statusView(_ statusView: StatusView, metaText: MetaText, didSelectMeta meta: Meta) + func statusView(_ statusView: StatusView, didTapCardWithURL url: URL) func statusView(_ statusView: StatusView, mediaGridContainerView: MediaGridContainerView, mediaView: MediaView, didSelectMediaViewAt index: Int) func statusView(_ statusView: StatusView, pollTableView tableView: UITableView, didSelectRowAt indexPath: IndexPath) func statusView(_ statusView: StatusView, pollVoteButtonPressed button: UIButton) @@ -263,7 +264,13 @@ extension StatusView { // media mediaGridContainerView.delegate = self - + + linkPreviewButton.addTarget( + self, + action: #selector(linkPreviewButtonPressed), + for: .touchUpInside + ) + // poll pollTableView.translatesAutoresizingMaskIntoConstraints = false pollTableViewHeightLayoutConstraint = pollTableView.heightAnchor.constraint(equalToConstant: 44.0).priority(.required - 1) @@ -298,6 +305,12 @@ extension StatusView { logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") delegate?.statusView(self, spoilerOverlayViewDidPressed: spoilerOverlayView) } + + @objc private func linkPreviewButtonPressed(_ sender: LinkPreviewButton) { + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") + guard let url = viewModel.card?.url else { return } + delegate?.statusView(self, didTapCardWithURL: url) + } }