From 1780973e68fd407dfc5157000f750ac5dd233453 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Wed, 11 Apr 2018 11:31:34 -0400 Subject: [PATCH] fix image, video, audio interactions in message details // FREEBIE --- Signal/src/Signal-Bridging-Header.h | 1 + .../MessageDetailViewController.swift | 186 ++++++++++++------ SignalMessaging/SignalMessaging.h | 1 + .../attachments/MediaMessageView.swift | 119 +++++------ 4 files changed, 191 insertions(+), 116 deletions(-) diff --git a/Signal/src/Signal-Bridging-Header.h b/Signal/src/Signal-Bridging-Header.h index a7775a9da..5779fddd5 100644 --- a/Signal/src/Signal-Bridging-Header.h +++ b/Signal/src/Signal-Bridging-Header.h @@ -52,6 +52,7 @@ #import #import #import +#import #import #import #import diff --git a/Signal/src/ViewControllers/MessageDetailViewController.swift b/Signal/src/ViewControllers/MessageDetailViewController.swift index e2e677ea5..6b66dd0bd 100644 --- a/Signal/src/ViewControllers/MessageDetailViewController.swift +++ b/Signal/src/ViewControllers/MessageDetailViewController.swift @@ -12,7 +12,8 @@ enum MessageMetadataViewMode: UInt { case focusOnMetadata } -class MessageDetailViewController: OWSViewController, MediaDetailPresenter, MediaGalleryDataSourceDelegate { +//class MessageDetailViewController: OWSViewController, MediaDetailPresenter, MediaGalleryDataSourceDelegate { +class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDelegate, OWSMessageBubbleViewDelegate { // MARK: Properties @@ -318,30 +319,29 @@ class MessageDetailViewController: OWSViewController, MediaDetailPresenter, Medi rows += addAttachmentRows() } - if true { - let messageBubbleView = OWSMessageBubbleView(frame: CGRect.zero) - self.messageBubbleView = messageBubbleView - messageBubbleView.viewItem = viewItem - messageBubbleView.cellMediaCache = NSCache() - messageBubbleView.contentWidth = contentWidth() - messageBubbleView.alwaysShowBubbleTail = true - messageBubbleView.configureViews() - messageBubbleView.loadContent() + let messageBubbleView = OWSMessageBubbleView(frame: CGRect.zero) + messageBubbleView.delegate = self + self.messageBubbleView = messageBubbleView + messageBubbleView.viewItem = viewItem + messageBubbleView.cellMediaCache = NSCache() + messageBubbleView.contentWidth = contentWidth() + messageBubbleView.alwaysShowBubbleTail = true + messageBubbleView.configureViews() + messageBubbleView.loadContent() - messageBubbleView.isUserInteractionEnabled = true - messageBubbleView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(messageBubbleTapped))) + assert(messageBubbleView.isUserInteractionEnabled) +// messageBubbleView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(messageBubbleTapped))) - let row = UIView() - row.addSubview(messageBubbleView) - messageBubbleView.autoPinHeightToSuperview() + let row = UIView() + row.addSubview(messageBubbleView) + messageBubbleView.autoPinHeightToSuperview() - let isIncoming = self.message as? TSIncomingMessage != nil - messageBubbleView.autoPinEdge(toSuperviewEdge: isIncoming ? .leading : .trailing, withInset: bubbleViewHMargin) + let isIncoming = self.message as? TSIncomingMessage != nil + messageBubbleView.autoPinEdge(toSuperviewEdge: isIncoming ? .leading : .trailing, withInset: bubbleViewHMargin) - self.messageBubbleViewWidthLayoutConstraint = messageBubbleView.autoSetDimension(.width, toSize: 0) - self.messageBubbleViewHeightLayoutConstraint = messageBubbleView.autoSetDimension(.height, toSize: 0) - rows.append(row) - } + self.messageBubbleViewWidthLayoutConstraint = messageBubbleView.autoSetDimension(.width, toSize: 0) + self.messageBubbleViewHeightLayoutConstraint = messageBubbleView.autoSetDimension(.height, toSize: 0) + rows.append(row) if rows.count == 0 { // Neither attachment nor body. @@ -587,40 +587,112 @@ class MessageDetailViewController: OWSViewController, MediaDetailPresenter, Medi messageBubbleViewHeightLayoutConstraint.constant = messageBubbleSize.height } - // MARK: - Event Handlers +// // MARK: - Event Handlers +// +// func messageBubbleTapped(sender: UIGestureRecognizer) { +// guard let messageBubbleView = messageBubbleView else { +// return +// } +// guard sender.state == .recognized else { +// return +// } +// if let outgoingMessage = viewItem.interaction as? TSOutgoingMessage { +// switch outgoingMessage.messageState { +// case .attemptingOut, +// .unsent: +// // Ignore taps on "unsent" and "sending" messages. +// return +// default: +// break +// } +// } +// +// let locationInMessageBubble = sender.location(in: messageBubbleView) +// switch messageBubbleView.gestureLocation(forLocation: locationInMessageBubble) { +// case .default: +// break +// case .oversizeText: +// let viewController = LongTextViewController(viewItem: viewItem) +// self.navigationController?.pushViewController(viewController, animated: true) +// break +// case .media: +// // TODO: We could show MediaGalleryViewController? +// break +// case .quotedReply: +// break +// } +// } + + // MARK: OWSMessageBubbleViewDelegate + + func didTapImageViewItem(_ viewItem: ConversationViewItem, attachmentStream: TSAttachmentStream, imageView: UIView) { + let mediaGalleryViewController = MediaGalleryViewController(thread: self.thread, uiDatabaseConnection: self.uiDatabaseConnection) + + mediaGalleryViewController.addDataSourceDelegate(self) + mediaGalleryViewController.presentDetailView(fromViewController: self, mediaMessage: self.message, replacingView: imageView) + } - func messageBubbleTapped(sender: UIGestureRecognizer) { - guard let messageBubbleView = messageBubbleView else { + func didTapVideoViewItem(_ viewItem: ConversationViewItem, attachmentStream: TSAttachmentStream, imageView: UIView) { + let mediaGalleryViewController = MediaGalleryViewController(thread: self.thread, uiDatabaseConnection: self.uiDatabaseConnection) + + mediaGalleryViewController.addDataSourceDelegate(self) + mediaGalleryViewController.presentDetailView(fromViewController: self, mediaMessage: self.message, replacingView: imageView) + } + + var audioAttachmentPlayer: OWSAudioPlayer? + + func didTapAudioViewItem(_ viewItem: ConversationViewItem, attachmentStream: TSAttachmentStream) { + AssertIsOnMainThread() + + guard let mediaURL = attachmentStream.mediaURL() else { + owsFail("\(logTag) in \(#function) mediaURL was unexpectedly nil for attachment: \(attachmentStream)") return } - guard sender.state == .recognized else { + + guard FileManager.default.fileExists(atPath: mediaURL.path) else { + owsFail("\(logTag) in \(#function) audio file missing at path: \(mediaURL)") return } - if let outgoingMessage = viewItem.interaction as? TSOutgoingMessage { - switch outgoingMessage.messageState { - case .attemptingOut, - .unsent: - // Ignore taps on "unsent" and "sending" messages. + + if let audioAttachmentPlayer = self.audioAttachmentPlayer { + // Is this player associated with this media adapter? + if (audioAttachmentPlayer.owner as? ConversationViewItem == viewItem) { + // Tap to pause & unpause. + audioAttachmentPlayer.togglePlayState() return - default: - break } + audioAttachmentPlayer.stop() + self.audioAttachmentPlayer = nil } - let locationInMessageBubble = sender.location(in: messageBubbleView) - switch messageBubbleView.gestureLocation(forLocation: locationInMessageBubble) { - case .default: - break - case .oversizeText: - let viewController = LongTextViewController(viewItem: viewItem) - self.navigationController?.pushViewController(viewController, animated: true) - break - case .media: - // TODO: We could show MediaGalleryViewController? - break - case .quotedReply: - break + let audioAttachmentPlayer = OWSAudioPlayer(mediaUrl: mediaURL, delegate: viewItem) + self.audioAttachmentPlayer = audioAttachmentPlayer + + // Associate the player with this media adapter. + audioAttachmentPlayer.owner = viewItem + audioAttachmentPlayer.playWithPlaybackAudioCategory() + } + + func didTapTruncatedTextMessage(_ conversationItem: ConversationViewItem) { + guard let navigationController = self.navigationController else { + owsFail("\(logTag) in \(#function) navigationController was unexpectedly nil") + return } + + let viewController = LongTextViewController(viewItem: viewItem) + navigationController.pushViewController(viewController, animated: true) + } + + func didTapFailedIncomingAttachment(_ viewItem: ConversationViewItem, attachmentPointer: TSAttachmentPointer) { + // no - op + } + + func didTapFailedOutgoingMessage(_ message: TSOutgoingMessage) { + // no - op + } + + func didTapQuotedMessage(_ viewItem: ConversationViewItem, quotedMessage: TSQuotedMessage) { + // no - op } // MediaGalleryDataSourceDelegate @@ -643,16 +715,16 @@ class MessageDetailViewController: OWSViewController, MediaDetailPresenter, Medi } } - // MARK: MediaDetailPresenter - - public func presentDetails(mediaMessageView: MediaMessageView, fromView: UIView) { - guard self.attachmentStream != nil else { - owsFail("attachment stream unexpectedly nil") - return - } - - let mediaGalleryViewController = MediaGalleryViewController(thread: self.thread, uiDatabaseConnection: self.uiDatabaseConnection) - mediaGalleryViewController.addDataSourceDelegate(self) - mediaGalleryViewController.presentDetailView(fromViewController: self, mediaMessage: self.message, replacingView: fromView) - } +// // MARK: MediaDetailPresenter +// +// public func presentDetails(mediaMessageView: MediaMessageView, fromView: UIView) { +// guard self.attachmentStream != nil else { +// owsFail("attachment stream unexpectedly nil") +// return +// } +// +// let mediaGalleryViewController = MediaGalleryViewController(thread: self.thread, uiDatabaseConnection: self.uiDatabaseConnection) +// mediaGalleryViewController.addDataSourceDelegate(self) +// mediaGalleryViewController.presentDetailView(fromViewController: self, mediaMessage: self.message, replacingView: fromView) +// } } diff --git a/SignalMessaging/SignalMessaging.h b/SignalMessaging/SignalMessaging.h index f4fbdb45b..1204fdc46 100644 --- a/SignalMessaging/SignalMessaging.h +++ b/SignalMessaging/SignalMessaging.h @@ -30,6 +30,7 @@ FOUNDATION_EXPORT const unsigned char SignalMessagingVersionString[]; #import #import #import +#import #import #import #import diff --git a/SignalMessaging/attachments/MediaMessageView.swift b/SignalMessaging/attachments/MediaMessageView.swift index 6858f8337..7787ee5bb 100644 --- a/SignalMessaging/attachments/MediaMessageView.swift +++ b/SignalMessaging/attachments/MediaMessageView.swift @@ -14,10 +14,10 @@ public enum MediaMessageViewMode: UInt { case attachmentApproval } -@objc -public protocol MediaDetailPresenter: class { - func presentDetails(mediaMessageView: MediaMessageView, fromView: UIView) -} +//@objc +//public protocol MediaDetailPresenter: class { +// func presentDetails(mediaMessageView: MediaMessageView, fromView: UIView) +//} @objc public class MediaMessageView: UIView, OWSAudioPlayerDelegate { @@ -59,7 +59,7 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate { @objc public var contentView: UIView? - private weak var mediaDetailPresenter: MediaDetailPresenter? +// private weak var mediaDetailPresenter: MediaDetailPresenter? // MARK: Initializers @@ -68,16 +68,18 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate { fatalError("\(#function) is unimplemented.") } - @objc - public convenience init(attachment: SignalAttachment, mode: MediaMessageViewMode) { - self.init(attachment: attachment, mode: mode, mediaDetailPresenter: nil) - } +// @objc +// public convenience init(attachment: SignalAttachment, mode: MediaMessageViewMode) { +// self.init(attachment: attachment, mode: mode, mediaDetailPresenter: nil) +// } - public required init(attachment: SignalAttachment, mode: MediaMessageViewMode, mediaDetailPresenter: MediaDetailPresenter?) { + // TODO there's only one mode now, used by the AttachmentApprovalView + @objc + public required init(attachment: SignalAttachment, mode: MediaMessageViewMode) { //}, mediaDetailPresenter: MediaDetailPresenter?) { assert(!attachment.hasError) self.attachment = attachment self.mode = mode - self.mediaDetailPresenter = mediaDetailPresenter +// self.mediaDetailPresenter = mediaDetailPresenter super.init(frame: CGRect.zero) createViews() @@ -203,9 +205,9 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate { let aspectRatio = image.size.width / image.size.height addSubviewWithScaleAspectFitLayout(view: animatedImageView, aspectRatio: aspectRatio) contentView = animatedImageView - - animatedImageView.isUserInteractionEnabled = true - animatedImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(imageTapped))) +// +// animatedImageView.isUserInteractionEnabled = true +// animatedImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(imageTapped))) } private func addSubviewWithScaleAspectFitLayout(view: UIView, aspectRatio: CGFloat) { @@ -237,9 +239,9 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate { let aspectRatio = image.size.width / image.size.height addSubviewWithScaleAspectFitLayout(view: imageView, aspectRatio: aspectRatio) contentView = imageView - - imageView.isUserInteractionEnabled = true - imageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(imageTapped))) +// +// imageView.isUserInteractionEnabled = true +// imageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(imageTapped))) } private func createVideoPreview() { @@ -269,8 +271,8 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate { self.addSubview(videoPlayButton) videoPlayButton.autoCenterInSuperview() - imageView.isUserInteractionEnabled = true - imageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(videoTapped))) +// imageView.isUserInteractionEnabled = true +// imageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(videoTapped))) } } @@ -445,44 +447,43 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate { audioPlayButton?.setImage(image, for: .normal) audioPlayButton?.imageView?.tintColor = controlTintColor } - - // MARK: - Full Screen Image - - @objc - func imageTapped(sender: UIGestureRecognizer) { - // Approval view handles it's own zooming gesture - guard mode != .attachmentApproval else { - return - } - guard sender.state == .recognized else { - return - } - guard let fromView = sender.view else { - return - } - - showMediaDetailViewController(fromView: fromView) - } - - // MARK: - Video Playback - - @objc - func videoTapped(sender: UIGestureRecognizer) { - // Approval view handles it's own play gesture - guard mode != .attachmentApproval else { - return - } - guard sender.state == .recognized else { - return - } - guard let fromView = sender.view else { - return - } - - showMediaDetailViewController(fromView: fromView) - } - - func showMediaDetailViewController(fromView: UIView) { - self.mediaDetailPresenter?.presentDetails(mediaMessageView: self, fromView: fromView) - } +// +// // MARK: - Full Screen Image +// +// @objc +// func imageTapped(sender: UIGestureRecognizer) { +// // Approval view handles it's own zooming gesture +// guard mode != .attachmentApproval else { +// return +// } +// guard sender.state == .recognized else { +// return +// } +// guard let fromView = sender.view else { +// return +// } +// +// showMediaDetailViewController(fromView: fromView) +// } +// +// // MARK: - Video Playback +// +// @objc +// func videoTapped(sender: UIGestureRecognizer) { +// // Approval view handles it's own play gesture +// guard mode != .attachmentApproval else { +// return +// } +// guard sender.state == .recognized else { +// return +// } +// guard let fromView = sender.view else { +// return +// } +// +// showMediaDetailViewController(fromView: fromView) +// } +// func showMediaDetailViewController(fromView: UIView) { +// self.mediaDetailPresenter?.presentDetails(mediaMessageView: self, fromView: fromView) +// } }