From 56fe9d057d954360b948773918c489b5b02958f5 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Tue, 5 Dec 2017 18:09:05 -0500 Subject: [PATCH] Attachment Approval // FREEBIE --- Signal.xcodeproj/project.pbxproj | 4 + .../translations/en.lproj/Localizable.strings | 6 + .../AttachmentApprovalViewController.swift | 171 ++++++++++++++++++ .../attachments/MediaMessageView.swift | 1 + .../SendExternalFileViewController.m | 98 ++++++---- .../attachments/SignalAttachment.swift | 2 +- 6 files changed, 241 insertions(+), 41 deletions(-) create mode 100644 SignalMessaging/attachments/AttachmentApprovalViewController.swift diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index cfd4610a2..0172824dc 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -294,6 +294,7 @@ 45CD81EF1DC030E7004C9430 /* AccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45CD81EE1DC030E7004C9430 /* AccountManager.swift */; }; 45D231771DC7E8F10034FA89 /* SessionResetJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45D231761DC7E8F10034FA89 /* SessionResetJob.swift */; }; 45DF5DF21DDB843F00C936C7 /* CompareSafetyNumbersActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45DF5DF11DDB843F00C936C7 /* CompareSafetyNumbersActivity.swift */; }; + 45E547201FD755E700DFC09E /* AttachmentApprovalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E5471F1FD755E700DFC09E /* AttachmentApprovalViewController.swift */; }; 45E5A6991F61E6DE001E4A8A /* MarqueeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E5A6981F61E6DD001E4A8A /* MarqueeLabel.swift */; }; 45E615161E8C590B0018AD52 /* DisplayableText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E615151E8C590B0018AD52 /* DisplayableText.swift */; }; 45E7A6A81E71CA7E00D44FB5 /* DisplayableTextFilterTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E7A6A61E71CA7E00D44FB5 /* DisplayableTextFilterTest.swift */; }; @@ -820,6 +821,7 @@ 45E282DE1D08E67800ADD4C8 /* gl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = gl; path = translations/gl.lproj/Localizable.strings; sourceTree = ""; }; 45E282DF1D08E6CC00ADD4C8 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = translations/id.lproj/Localizable.strings; sourceTree = ""; }; 45E2E91E1E13EE3500457AA0 /* OWSCallNotificationsAdaptee.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; name = OWSCallNotificationsAdaptee.h; path = UserInterface/OWSCallNotificationsAdaptee.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 45E5471F1FD755E700DFC09E /* AttachmentApprovalViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentApprovalViewController.swift; sourceTree = ""; }; 45E5A6981F61E6DD001E4A8A /* MarqueeLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarqueeLabel.swift; sourceTree = ""; }; 45E615151E8C590B0018AD52 /* DisplayableText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayableText.swift; sourceTree = ""; }; 45E7A6A61E71CA7E00D44FB5 /* DisplayableTextFilterTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayableTextFilterTest.swift; sourceTree = ""; }; @@ -1542,6 +1544,7 @@ 454A96571FD600B4008D2A0E /* attachments */ = { isa = PBXGroup; children = ( + 45E5471F1FD755E700DFC09E /* AttachmentApprovalViewController.swift */, 3400C7901EAF89CD008A8584 /* SendExternalFileViewController.h */, 3400C7911EAF89CD008A8584 /* SendExternalFileViewController.m */, 34533F161EA8D2070006114F /* OWSAudioAttachmentPlayer.h */, @@ -2693,6 +2696,7 @@ 45194F931FD7215C00333B2C /* OWSContactOffersInteraction.m in Sources */, 346129761FD1E0B500532771 /* WeakTimer.swift in Sources */, 346129F81FD5F31400532771 /* OWS100RemoveTSRecipientsMigration.m in Sources */, + 45E547201FD755E700DFC09E /* AttachmentApprovalViewController.swift in Sources */, 346129B51FD1F7E800532771 /* OWSProfileManager.m in Sources */, 346129701FD1D74C00532771 /* Release.m in Sources */, 34480B621FD0A98800BC14EF /* UIColor+OWS.m in Sources */, diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 275c4f72c..d8735bc0c 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -76,12 +76,18 @@ /* No comment provided by engineer. */ "ATTACHMENT" = "Attachment"; +/* Title for the 'attachment approval' dialog. */ +"ATTACHMENT_APPROVAL_DIALOG_TITLE" = "Attachment"; + /* Format string for file extension label in call interstitial view */ "ATTACHMENT_APPROVAL_FILE_EXTENSION_FORMAT" = "File type: %@"; /* Format string for file size label in call interstitial view. Embeds: {{file size as 'N mb' or 'N kb'}}. */ "ATTACHMENT_APPROVAL_FILE_SIZE_FORMAT" = "Size: %@"; +/* Label for 'send' button in the 'attachment approval' dialog. */ +"ATTACHMENT_APPROVAL_SEND_BUTTON" = "Send"; + /* Generic filename for an attachment with no known name */ "ATTACHMENT_DEFAULT_FILENAME" = "Attachment"; diff --git a/SignalMessaging/attachments/AttachmentApprovalViewController.swift b/SignalMessaging/attachments/AttachmentApprovalViewController.swift new file mode 100644 index 000000000..104798edf --- /dev/null +++ b/SignalMessaging/attachments/AttachmentApprovalViewController.swift @@ -0,0 +1,171 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +import Foundation +import MediaPlayer + +@objc +public protocol AttachmentApprovalViewControllerDelegate: class { + func didApproveAttachment() + func didCancelAttachment() +} + +@objc +public class AttachmentApprovalViewController: OWSViewController { + + let TAG = "[AttachmentApprovalViewController]" + weak var delegate: AttachmentApprovalViewControllerDelegate? + + // MARK: Properties + + let attachment: SignalAttachment + + let mediaMessageView: MediaMessageView + + // MARK: Initializers + + @available(*, unavailable, message:"use attachment: constructor instead.") + required public init?(coder aDecoder: NSCoder) { + fatalError("unimplemented") + } + + required public init(attachment: SignalAttachment, delegate: AttachmentApprovalViewControllerDelegate) { + assert(!attachment.hasError) + self.attachment = attachment + self.delegate = delegate + self.mediaMessageView = MediaMessageView(attachment: attachment, mode: .large) + super.init(nibName: nil, bundle: nil) + } + + // MARK: View Lifecycle + + override public func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = UIColor.white + + createViews() + + self.navigationItem.title = dialogTitle() + } + + private func dialogTitle() -> String { + guard let filename = mediaMessageView.formattedFileName() else { + return NSLocalizedString("ATTACHMENT_APPROVAL_DIALOG_TITLE", + comment: "Title for the 'attachment approval' dialog.") + } + return filename + } + + override public func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + mediaMessageView.viewWillAppear(animated) + } + + override public func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + mediaMessageView.viewWillDisappear(animated) + } + + // MARK: - Create Views + + private func createViews() { + let previewTopMargin: CGFloat = 30 + let previewHMargin: CGFloat = 20 + + self.view.addSubview(mediaMessageView) + mediaMessageView.autoPinWidthToSuperview(withMargin:previewHMargin) + mediaMessageView.autoPin(toTopLayoutGuideOf: self, withInset:previewTopMargin) + + createButtonRow(mediaMessageView:mediaMessageView) + } + + private func wrapViewsInVerticalStack(subviews: [UIView]) -> UIView { + assert(subviews.count > 0) + + let stackView = UIView() + + var lastView: UIView? + for subview in subviews { + + stackView.addSubview(subview) + subview.autoHCenterInSuperview() + + if lastView == nil { + subview.autoPinEdge(toSuperviewEdge:.top) + } else { + subview.autoPinEdge(.top, to:.bottom, of:lastView!, withOffset:10) + } + + lastView = subview + } + + lastView?.autoPinEdge(toSuperviewEdge:.bottom) + + return stackView + } + + private func createButtonRow(mediaMessageView: UIView) { + let buttonTopMargin = ScaleFromIPhone5To7Plus(30, 40) + let buttonBottomMargin = ScaleFromIPhone5To7Plus(25, 40) + let buttonHSpacing = ScaleFromIPhone5To7Plus(20, 30) + + let buttonRow = UIView() + self.view.addSubview(buttonRow) + buttonRow.autoPinWidthToSuperview() + buttonRow.autoPinEdge(toSuperviewEdge:.bottom, withInset:buttonBottomMargin) + buttonRow.autoPinEdge(.top, to:.bottom, of:mediaMessageView, withOffset:buttonTopMargin) + + // We use this invisible subview to ensure that the buttons are centered + // horizontally. + let buttonSpacer = UIView() + buttonRow.addSubview(buttonSpacer) + // Vertical positioning of this view doesn't matter. + buttonSpacer.autoPinEdge(toSuperviewEdge:.top) + buttonSpacer.autoSetDimension(.width, toSize:buttonHSpacing) + buttonSpacer.autoHCenterInSuperview() + + let cancelButton = createButton(title: CommonStrings.cancelButton, + color : UIColor.ows_destructiveRed(), + action: #selector(cancelPressed)) + buttonRow.addSubview(cancelButton) + cancelButton.autoPinEdge(toSuperviewEdge:.top) + cancelButton.autoPinEdge(toSuperviewEdge:.bottom) + cancelButton.autoPinEdge(.right, to:.left, of:buttonSpacer) + + let sendButton = createButton(title: NSLocalizedString("ATTACHMENT_APPROVAL_SEND_BUTTON", + comment: "Label for 'send' button in the 'attachment approval' dialog."), + color : UIColor(rgbHex:0x2ecc71), + action: #selector(sendPressed)) + buttonRow.addSubview(sendButton) + sendButton.autoPinEdge(toSuperviewEdge:.top) + sendButton.autoPinEdge(toSuperviewEdge:.bottom) + sendButton.autoPinEdge(.left, to:.right, of:buttonSpacer) + } + + private func createButton(title: String, color: UIColor, action: Selector) -> UIView { + let buttonWidth = ScaleFromIPhone5To7Plus(110, 140) + let buttonHeight = ScaleFromIPhone5To7Plus(35, 45) + + return OWSFlatButton.button(title:title, + titleColor:UIColor.white, + backgroundColor:color, + width:buttonWidth, + height:buttonHeight, + target:target, + selector:action) + } + + // MARK: - Event Handlers + + func cancelPressed(sender: UIButton) { + self.delegate?.didCancelAttachment() + } + + func sendPressed(sender: UIButton) { + self.delegate?.didApproveAttachment() + } +} diff --git a/SignalMessaging/attachments/MediaMessageView.swift b/SignalMessaging/attachments/MediaMessageView.swift index 014138cc4..6857aa08f 100644 --- a/SignalMessaging/attachments/MediaMessageView.swift +++ b/SignalMessaging/attachments/MediaMessageView.swift @@ -426,6 +426,7 @@ public class MediaMessageView: UIView, OWSAudioAttachmentPlayerDelegate { return } + // FIXME SHARINGEXTENSION // let window = UIApplication.shared.keyWindow // let convertedRect = fromView.convert(fromView.bounds, to:window) // let viewController = FullImageViewController(attachment:attachment, from:convertedRect) diff --git a/SignalMessaging/attachments/SendExternalFileViewController.m b/SignalMessaging/attachments/SendExternalFileViewController.m index 3ec63950c..3e60bf09f 100644 --- a/SignalMessaging/attachments/SendExternalFileViewController.m +++ b/SignalMessaging/attachments/SendExternalFileViewController.m @@ -16,10 +16,12 @@ NS_ASSUME_NONNULL_BEGIN -@interface SendExternalFileViewController () +@interface SendExternalFileViewController () @property (nonatomic, readonly) OWSContactsManager *contactsManager; @property (nonatomic, readonly) OWSMessageSender *messageSender; +@property (nonatomic) TSThread *thread; @end @@ -45,45 +47,6 @@ NS_ASSUME_NONNULL_BEGIN self.title = NSLocalizedString(@"SEND_EXTERNAL_FILE_VIEW_TITLE", @"Title for the 'send external file' view."); } -#pragma mark - SelectThreadViewControllerDelegate - -- (void)threadWasSelected:(TSThread *)thread -{ - OWSAssert(self.attachment); - OWSAssert(thread); - - __weak typeof(self) weakSelf = self; - - // FIXME SHARINGEXTENSION - // Handling safety number changes brings in a lot of machinery. - // How do we want to handle this? - // e.g. fingerprint scanning, etc. in the SAE or just redirect the user to the main app? - // BOOL didShowSNAlert = - // [SafetyNumberConfirmationAlert presentAlertIfNecessaryWithRecipientIds:thread.recipientIdentifiers - // confirmationText:[SafetyNumberStrings - // confirmSendButton] - // contactsManager:self.contactsManager - // completion:^(BOOL didConfirm) { - // if (didConfirm) { - // [weakSelf threadWasSelected:thread]; - // } - // }]; - // if (didShowSNAlert) { - // return; - // } - - [ThreadUtil addThreadToProfileWhitelistIfEmptyContactThread:thread]; - [ThreadUtil sendMessageWithAttachment:self.attachment inThread:thread messageSender:self.messageSender]; - - // FIXME SHARINGEXTENSION - // Show loading screen and dismiss when complete - - // TODO Show share annotation UI (add caption, preview, cancel) - // - // This implemenation was for the "import with signal" functionality: - // [SignalApp.sharedApp presentConversationForThread:thread]; -} - - (BOOL)canSelectBlockedContact { return NO; @@ -191,6 +154,61 @@ NS_ASSUME_NONNULL_BEGIN return label; } +#pragma mark - SelectThreadViewControllerDelegate + +- (void)threadWasSelected:(TSThread *)thread +{ + OWSAssert(self.attachment); + OWSAssert(thread); + self.thread = thread; + + __weak typeof(self) weakSelf = self; + + // FIXME SHARINGEXTENSION + // Handling safety number changes brings in a lot of machinery. + // How do we want to handle this? + // e.g. fingerprint scanning, etc. in the SAE or just redirect the user to the main app? + // BOOL didShowSNAlert = + // [SafetyNumberConfirmationAlert presentAlertIfNecessaryWithRecipientIds:thread.recipientIdentifiers + // confirmationText:[SafetyNumberStrings + // confirmSendButton] + // contactsManager:self.contactsManager + // completion:^(BOOL didConfirm) { + // if (didConfirm) { + // [weakSelf threadWasSelected:thread]; + // } + // }]; + // if (didShowSNAlert) { + // return; + // } + + AttachmentApprovalViewController *approvalVC = + [[AttachmentApprovalViewController alloc] initWithAttachment:self.attachment delegate:self]; + + [self.navigationController pushViewController:approvalVC animated:YES]; + + // FIXME This implemenation was for the "import with signal" functionality, + // and is now broken. We need to be sure to remove the "import with signal" functionality. + // [SignalApp.sharedApp presentConversationForThread:thread]; +} + +#pragma mark - AttachmentApprovalViewControllerDelegate + +- (void)didApproveAttachment +{ + [ThreadUtil addThreadToProfileWhitelistIfEmptyContactThread:self.thread]; + [ThreadUtil sendMessageWithAttachment:self.attachment inThread:self.thread messageSender:self.messageSender]; + + // FIXME SHARINGEXTENSION + // Show loading screen and dismiss entire share extnesion once entirely complete + [self.navigationController popViewControllerAnimated:YES]; +} + +- (void)didCancelAttachment +{ + [self.navigationController popViewControllerAnimated:YES]; +} + @end NS_ASSUME_NONNULL_END diff --git a/SignalMessaging/attachments/SignalAttachment.swift b/SignalMessaging/attachments/SignalAttachment.swift index 614e9817c..06deca330 100644 --- a/SignalMessaging/attachments/SignalAttachment.swift +++ b/SignalMessaging/attachments/SignalAttachment.swift @@ -152,7 +152,7 @@ public class SignalAttachment: NSObject { // This method should not be called directly; use the factory // methods instead. @objc - internal init(dataSource: DataSource, dataUTI: String) { + private init(dataSource: DataSource, dataUTI: String) { self.dataSource = dataSource self.dataUTI = dataUTI super.init()