diff --git a/Signal/src/Signal-Bridging-Header.h b/Signal/src/Signal-Bridging-Header.h index 48ad505d8..9b4154f71 100644 --- a/Signal/src/Signal-Bridging-Header.h +++ b/Signal/src/Signal-Bridging-Header.h @@ -7,6 +7,7 @@ #import "Asserts.h" #import "AttachmentSharing.h" #import "Environment.h" +#import "FLAnimatedImage.h" #import "NotificationsManager.h" #import "OWSAnyTouchGestureRecognizer.h" #import "OWSCallNotificationsAdaptee.h" diff --git a/Signal/src/ViewControllers/AttachmentApprovalViewController.swift b/Signal/src/ViewControllers/AttachmentApprovalViewController.swift index b6df468b5..c2b9dca99 100644 --- a/Signal/src/ViewControllers/AttachmentApprovalViewController.swift +++ b/Signal/src/ViewControllers/AttachmentApprovalViewController.swift @@ -3,6 +3,7 @@ // import Foundation +import MediaPlayer class AttachmentApprovalViewController: UIViewController { @@ -14,6 +15,8 @@ class AttachmentApprovalViewController: UIViewController { var successCompletion : (() -> Void)? + var videoPlayer: MPMoviePlayerController? + // MARK: Initializers @available(*, unavailable, message:"use attachment: constructor instead.") @@ -60,29 +63,75 @@ class AttachmentApprovalViewController: UIViewController { createButtonRow(attachmentPreviewView:attachmentPreviewView) - if attachment.isImage { + if attachment.isAnimatedImage { + createAnimatedPreview(attachmentPreviewView:attachmentPreviewView) + } else if attachment.isImage { createImagePreview(attachmentPreviewView:attachmentPreviewView) + } else if attachment.isVideo { + createVideoPreview(attachmentPreviewView:attachmentPreviewView) + } else if attachment.isAudio { + createAudioPreview(attachmentPreviewView:attachmentPreviewView) } else { createGenericPreview(attachmentPreviewView:attachmentPreviewView) } } + private func createAudioPreview(attachmentPreviewView: UIView) { + // TODO: Add audio player. + createGenericPreview(attachmentPreviewView:attachmentPreviewView) + } + + private func createAnimatedPreview(attachmentPreviewView: UIView) { + // Use Flipboard FLAnimatedImage library to display gifs + guard let animatedImage = FLAnimatedImage(gifData:attachment.data) else { + createGenericPreview(attachmentPreviewView:attachmentPreviewView) + return + } + let animatedImageView = FLAnimatedImageView() + animatedImageView.animatedImage = animatedImage + animatedImageView.contentMode = .scaleAspectFit + attachmentPreviewView.addSubview(animatedImageView) + animatedImageView.autoPinWidthToSuperview() + animatedImageView.autoPinHeightToSuperview() + } + private func createImagePreview(attachmentPreviewView: UIView) { var image = attachment.image if image == nil { image = UIImage(data:attachment.data) } - if image != nil { - let imageView = UIImageView(image:image) - imageView.layer.minificationFilter = kCAFilterTrilinear - imageView.layer.magnificationFilter = kCAFilterTrilinear - imageView.contentMode = .scaleAspectFit - attachmentPreviewView.addSubview(imageView) - imageView.autoPinWidthToSuperview() - imageView.autoPinHeightToSuperview() - } else { + guard image != nil else { createGenericPreview(attachmentPreviewView:attachmentPreviewView) + return } + + let imageView = UIImageView(image:image) + imageView.layer.minificationFilter = kCAFilterTrilinear + imageView.layer.magnificationFilter = kCAFilterTrilinear + imageView.contentMode = .scaleAspectFit + attachmentPreviewView.addSubview(imageView) + imageView.autoPinWidthToSuperview() + imageView.autoPinHeightToSuperview() + } + + private func createVideoPreview(attachmentPreviewView: UIView) { + guard let dataUrl = attachment.getTemporaryDataUrl() else { + createGenericPreview(attachmentPreviewView:attachmentPreviewView) + return + } + guard let videoPlayer = MPMoviePlayerController(contentURL:dataUrl) else { + createGenericPreview(attachmentPreviewView:attachmentPreviewView) + return + } + videoPlayer.prepareToPlay() + + videoPlayer.controlStyle = .default + videoPlayer.shouldAutoplay = false + + attachmentPreviewView.addSubview(videoPlayer.view) + self.videoPlayer = videoPlayer + videoPlayer.view.autoPinWidthToSuperview() + videoPlayer.view.autoPinHeightToSuperview() } private func createGenericPreview(attachmentPreviewView: UIView) { @@ -125,9 +174,17 @@ class AttachmentApprovalViewController: UIViewController { let numberFormatter = NumberFormatter() numberFormatter.numberStyle = NumberFormatter.Style.decimal let fileSizeLabel = UILabel() + let fileSize = attachment.data.count + let kOneKilobyte = 1024 + let kOneMegabyte = kOneKilobyte * kOneKilobyte + let fileSizeText = (fileSize > kOneMegabyte + ? numberFormatter.string(from: NSNumber(value: fileSize / kOneMegabyte))! + " mb" + : (fileSize > kOneKilobyte + ? numberFormatter.string(from: NSNumber(value: fileSize / kOneKilobyte))! + " kb" + : numberFormatter.string(from: NSNumber(value: fileSize))!)) fileSizeLabel.text = String(format:NSLocalizedString("ATTACHMENT_APPROVAL_FILE_SIZE_FORMAT", - comment: "Format string for file size label in call interstitial view"), - numberFormatter.string(from: NSNumber(value: attachment.data.count))!) + comment: "Format string for file size label in call interstitial view. Embeds: {{file size as 'N mb' or 'N kb'}}."), + fileSizeText) fileSizeLabel.textColor = UIColor.white fileSizeLabel.font = labelFont diff --git a/Signal/src/ViewControllers/MessagesViewController.m b/Signal/src/ViewControllers/MessagesViewController.m index c35a6ff56..a47d50f7f 100644 --- a/Signal/src/ViewControllers/MessagesViewController.m +++ b/Signal/src/ViewControllers/MessagesViewController.m @@ -110,12 +110,9 @@ typedef enum : NSUInteger { } - (BOOL)pasteBoardHasPossibleAttachment { - if ([SignalAttachment pasteboardHasPossibleAttachment]) { - // We don't want to load/convert images more than once so we - // only do a cursory validation pass at this time. - return YES; - } - return NO; + // We don't want to load/convert images more than once so we + // only do a cursory validation pass at this time. + return [SignalAttachment pasteboardHasPossibleAttachment]; } - (BOOL)canPerformAction:(SEL)action withSender:(id)sender { diff --git a/Signal/src/ViewControllers/SignalAttachment.swift b/Signal/src/ViewControllers/SignalAttachment.swift index 7fd436663..6d48fecbe 100644 --- a/Signal/src/ViewControllers/SignalAttachment.swift +++ b/Signal/src/ViewControllers/SignalAttachment.swift @@ -64,6 +64,8 @@ class SignalAttachment: NSObject { let data: Data + internal var temporaryDataUrl: URL? + // Attachment types are identified using UTIs. // // See: https://developer.apple.com/library/content/documentation/Miscellaneous/Reference/UTIRef/Articles/System-DeclaredUniformTypeIdentifiers.html @@ -109,6 +111,30 @@ class SignalAttachment: NSObject { super.init() } + // MARK: Methods + + public func getTemporaryDataUrl() -> URL? { + if temporaryDataUrl == nil { + let directory = NSTemporaryDirectory() + guard let fileExtension = self.fileExtension else { + return nil + } + let fileName = NSUUID().uuidString + "." + fileExtension + guard let fileUrl = NSURL.fileURL(withPathComponents: [directory, fileName]) else { + return nil + } + do { + try data.write(to: fileUrl) + } catch { + Logger.error("\(SignalAttachment.TAG) Could not write data to disk: \(dataUTI)") + assertionFailure() + return nil + } + temporaryDataUrl = fileUrl + } + return temporaryDataUrl + } + var hasError: Bool { return error != nil } @@ -237,6 +263,10 @@ class SignalAttachment: NSObject { return SignalAttachment.outputImageUTISet.contains(dataUTI) } + public var isAnimatedImage: Bool { + return SignalAttachment.animatedImageUTISet.contains(dataUTI) + } + public var isVideo: Bool { return SignalAttachment.videoUTISet.contains(dataUTI) } diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 2ff444f4b..eac2b6e1d 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -56,7 +56,7 @@ "ATTACHMENT_APPROVAL_FILE_EXTENSION_FORMAT" = "File type: %@"; /* Format string for file size label in call interstitial view */ -"ATTACHMENT_APPROVAL_FILE_SIZE_FORMAT" = "File size: %@"; +"ATTACHMENT_APPROVAL_FILE_SIZE_FORMAT" = "Size: %@"; /* Label for 'send' button in the 'attachment approval' dialog. */ "ATTACHMENT_APPROVAL_SEND_BUTTON" = "Send";