Respond to CR.

// FREEBIE
This commit is contained in:
Matthew Chen 2017-03-11 22:19:59 -03:00
parent 164db41c29
commit bdc729ad29
5 changed files with 113 additions and 132 deletions

View File

@ -174,4 +174,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 48dbf2fb380d626bb799a782dd41b6bf1e466506
COCOAPODS: 1.2.0
COCOAPODS: 1.1.1

View File

@ -32,6 +32,7 @@
#import <SignalServiceKit/Contact.h>
#import <SignalServiceKit/ContactsUpdater.h>
#import <SignalServiceKit/Cryptography.h>
#import <SignalServiceKit/MIMETypeUtil.h>
#import <SignalServiceKit/NSData+Base64.h>
#import <SignalServiceKit/NSDate+millisecondTimeStamp.h>
#import <SignalServiceKit/OWSAcknowledgeMessageDeliveryRequest.h>

View File

@ -10,22 +10,22 @@ class AttachmentApprovalViewController: UIViewController {
// MARK: Properties
let attachment: SignalAttachment!
let attachment: SignalAttachment
var successCompletion : (() -> Void)?
// MARK: Initializers
@available(*, deprecated, message:"use attachment: constructor instead.")
@available(*, unavailable, message:"use attachment: constructor instead.")
required init?(coder aDecoder: NSCoder) {
self.attachment = SignalAttachment.genericAttachment(withData: nil,
self.attachment = SignalAttachment.genericAttachment(data: nil,
dataUTI: kUTTypeContent as String)
super.init(coder: aDecoder)
assert(false)
}
required init(attachment: SignalAttachment!, successCompletion : @escaping () -> Void) {
assert(!attachment.hasError())
assert(!attachment.hasError)
self.attachment = attachment
self.successCompletion = successCompletion
super.init(nibName: nil, bundle: nil)
@ -50,8 +50,8 @@ class AttachmentApprovalViewController: UIViewController {
// MARK: - Create Views
private func createViews() {
let previewTopMargin = 30 as CGFloat
let previewHMargin = 20 as CGFloat
let previewTopMargin: CGFloat = 30
let previewHMargin: CGFloat = 20
let attachmentPreviewView = UIView()
self.view.addSubview(attachmentPreviewView)
@ -60,7 +60,7 @@ class AttachmentApprovalViewController: UIViewController {
createButtonRow(attachmentPreviewView:attachmentPreviewView)
if attachment.isImage() {
if attachment.isImage {
createImagePreview(attachmentPreviewView:attachmentPreviewView)
} else {
createGenericPreview(attachmentPreviewView:attachmentPreviewView)
@ -74,8 +74,8 @@ class AttachmentApprovalViewController: UIViewController {
}
if image != nil {
let imageView = UIImageView(image:image)
imageView.layer.minificationFilter = kCAFilterTrilinear;
imageView.layer.magnificationFilter = kCAFilterTrilinear;
imageView.layer.minificationFilter = kCAFilterTrilinear
imageView.layer.magnificationFilter = kCAFilterTrilinear
imageView.contentMode = .scaleAspectFit
attachmentPreviewView.addSubview(imageView)
imageView.autoPinWidthToSuperview()
@ -94,8 +94,8 @@ class AttachmentApprovalViewController: UIViewController {
let image = UIImage(named:"file-icon-large")
assert(image != nil)
let imageView = UIImageView(image:image)
imageView.layer.minificationFilter = kCAFilterTrilinear;
imageView.layer.magnificationFilter = kCAFilterTrilinear;
imageView.layer.minificationFilter = kCAFilterTrilinear
imageView.layer.magnificationFilter = kCAFilterTrilinear
stackView.addSubview(imageView)
imageView.autoHCenterInSuperview()
imageView.autoPinEdge(toSuperviewEdge:.top)
@ -106,11 +106,11 @@ class AttachmentApprovalViewController: UIViewController {
let labelFont = UIFont.ows_regularFont(withSize:ScaleFromIPhone5To7Plus(18, 24))
if attachment.fileExtension() != nil {
if let fileExtension = attachment.fileExtension {
let fileExtensionLabel = UILabel()
fileExtensionLabel.text = String(format:NSLocalizedString("ATTACHMENT_APPROVAL_FILE_EXTENSION_FORMAT",
comment: "Format string for file extension label in call interstitial view"),
attachment.fileExtension()!.capitalized)
fileExtension.capitalized)
fileExtensionLabel.textColor = UIColor.white
fileExtensionLabel.font = labelFont
@ -158,8 +158,8 @@ class AttachmentApprovalViewController: UIViewController {
buttonSpacer.autoSetDimension(.width, toSize:buttonHSpacing)
buttonSpacer.autoHCenterInSuperview()
let cancelButton = createButton(title: NSLocalizedString("ATTACHMENT_APPROVAL_CANCEL_BUTTON",
comment: "Label for 'cancel' button in the 'attachment approval' dialog."),
let cancelButton = createButton(title: NSLocalizedString("TXT_CANCEL_TITLE",
comment: ""),
color : UIColor(rgbHex:0xff3B30),
action: #selector(cancelPressed))
buttonRow.addSubview(cancelButton)
@ -209,9 +209,7 @@ class AttachmentApprovalViewController: UIViewController {
func sendPressed(sender: UIButton) {
let successCompletion = self.successCompletion
dismiss(animated: true, completion: {
if successCompletion != nil {
successCompletion!()
}
successCompletion?()
})
}
}

View File

@ -1831,10 +1831,6 @@ typedef enum : NSUInteger {
NSString *contactName = [self.contactsManager displayNameForPhoneIdentifier:fingerprint.theirStableId];
[vc configureWithThread:self.thread fingerprint:fingerprint contactName:contactName];
} else if ([segue.destinationViewController isKindOfClass:[OWSConversationSettingsTableViewController class]]) {
OWSConversationSettingsTableViewController *controller
= (OWSConversationSettingsTableViewController *)segue.destinationViewController;
[controller configureWithThread:self.thread];
} else {
DDLogDebug(@"%@ Received segue: %@", self.tag, segue.identifier);
}

View File

@ -6,6 +6,7 @@ import Foundation
import MobileCoreServices
enum SignalAttachmentError: String {
case missingData
case fileSizeTooLarge
case invalidData
case couldNotParseImage
@ -42,12 +43,12 @@ class SignalAttachment: NSObject {
// MARK: Properties
let data: Data!
let data: Data
// Attachment types are identified using UTIs.
//
// See: https://developer.apple.com/library/content/documentation/Miscellaneous/Reference/UTIRef/Articles/System-DeclaredUniformTypeIdentifiers.html
let dataUTI: String!
let dataUTI: String
var error: SignalAttachmentError? {
didSet {
@ -67,32 +68,32 @@ class SignalAttachment: NSObject {
/**
* Media Size constraints from Signal-Android
* (org/thoughtcrime/securesms/mms/PushMediaConstraints.java)
*
* https://github.com/WhisperSystems/Signal-Android/blob/master/src/org/thoughtcrime/securesms/mms/PushMediaConstraints.java
*/
static let kMaxFileSizeGif = 5 * 1024 * 1024
static let kMaxFileSizeImage = 420 * 1024
static let kMaxFileSizeAnimatedImage = 6 * 1024 * 1024
static let kMaxFileSizeImage = 6 * 1024 * 1024
static let kMaxFileSizeVideo = 100 * 1024 * 1024
static let kMaxFileSizeAudio = 100 * 1024 * 1024
// TODO: What should the max file size on "other" attachments be?
static let kMaxFileSizeGeneric = 100 * 1024 * 1024
// MARK: Constructor
// This method should not be called directly; use the factory
// methods instead.
internal required init(data: Data!, dataUTI: String!) {
internal required init(data: Data, dataUTI: String) {
self.data = data
self.dataUTI = dataUTI
super.init()
}
public func hasError() -> Bool {
var hasError: Bool {
return error != nil
}
// Returns the MIME type for this attachment or nil if no MIME type
// can be identified.
public func mimeType() -> String? {
var mimeType: String? {
let mimeType = UTTypeCopyPreferredTagWithClass(dataUTI as CFString, kUTTagClassMIMEType)
guard mimeType != nil else {
return nil
@ -102,12 +103,12 @@ class SignalAttachment: NSObject {
// Returns the file extension for this attachment or nil if no file extension
// can be identified.
public func fileExtension() -> String? {
let fileExtension = UTTypeCopyPreferredTagWithClass(dataUTI as CFString, kUTTagClassFilenameExtension)
guard fileExtension != nil else {
var fileExtension: String? {
guard let fileExtension = UTTypeCopyPreferredTagWithClass(dataUTI as CFString,
kUTTagClassFilenameExtension) else {
return nil
}
return fileExtension?.takeRetainedValue() as? String
return fileExtension.takeRetainedValue() as String
}
// Returns the set of UTIs that correspond to valid _input_ image formats
@ -115,65 +116,50 @@ class SignalAttachment: NSObject {
//
// Image attachments may be converted to another image format before
// being uploaded.
//
// TODO: We need to finalize which formats we support.
private class func inputImageUTISet() -> Set<String>! {
return [
kUTTypeJPEG as String,
kUTTypeGIF as String,
kUTTypePNG as String
]
private class var inputImageUTISet: Set<String> {
return MIMETypeUtil.supportedImageUTITypes()
}
// Returns the set of UTIs that correspond to valid _output_ image formats
// for Signal attachments.
//
// TODO: We need to finalize which formats we support.
private class func outputImageUTISet() -> Set<String>! {
return [
kUTTypeJPEG as String,
kUTTypeGIF as String,
kUTTypePNG as String
]
private class var outputImageUTISet: Set<String> {
return MIMETypeUtil.supportedImageUTITypes()
}
// Returns the set of UTIs that correspond to valid animated image formats
// for Signal attachments.
private class var animatedImageUTISet: Set<String> {
return MIMETypeUtil.supportedAnimatedImageUTITypes()
}
// Returns the set of UTIs that correspond to valid video formats
// for Signal attachments.
//
// TODO: We need to finalize which formats we support.
private class func videoUTISet() -> Set<String>! {
return [
kUTTypeMPEG4 as String
]
private class var videoUTISet: Set<String> {
return MIMETypeUtil.supportedVideoUTITypes()
}
// Returns the set of UTIs that correspond to valid audio formats
// for Signal attachments.
//
// TODO: We need to finalize which formats we support.
private class func audioUTISet() -> Set<String>! {
return [
kUTTypeMP3 as String,
kUTTypeMPEG4Audio as String
]
private class var audioUTISet: Set<String> {
return MIMETypeUtil.supportedAudioUTITypes()
}
// Returns the set of UTIs that correspond to valid input formats
// for Signal attachments.
public class func validInputUTISet() -> Set<String>! {
return inputImageUTISet().union(videoUTISet().union(audioUTISet()))
public class var validInputUTISet: Set<String> {
return inputImageUTISet.union(videoUTISet.union(audioUTISet))
}
public func isImage() -> Bool {
return SignalAttachment.outputImageUTISet().contains(dataUTI)
public var isImage: Bool {
return SignalAttachment.outputImageUTISet.contains(dataUTI)
}
public func isVideo() -> Bool {
return SignalAttachment.videoUTISet().contains(dataUTI)
public var isVideo: Bool {
return SignalAttachment.videoUTISet.contains(dataUTI)
}
public func isAudio() -> Bool {
return SignalAttachment.audioUTISet().contains(dataUTI)
public var isAudio: Bool {
return SignalAttachment.audioUTISet.contains(dataUTI)
}
// Returns an attachment from the pasteboard, or nil if no attachment
@ -182,29 +168,31 @@ class SignalAttachment: NSObject {
// NOTE: The attachment returned by this method may not be valid.
// Check the attachment's error property.
public class func attachmentFromPasteboard() -> SignalAttachment? {
guard UIPasteboard.general.numberOfItems == 1 else {
// Ignore pasteboard if it contains multiple items.
//
// TODO: Should we try to use the first?
guard UIPasteboard.general.numberOfItems >= 1 else {
return nil
}
let pasteboardUTISet = Set(UIPasteboard.general.types)
for dataUTI in inputImageUTISet() {
// If pasteboard contains multiple items, use only the first.
let itemSet = IndexSet(integer:0)
guard let pasteboardUTITypes = UIPasteboard.general.types(forItemSet:itemSet) else {
return nil
}
let pasteboardUTISet = Set<String>(pasteboardUTITypes[0])
for dataUTI in inputImageUTISet {
if pasteboardUTISet.contains(dataUTI) {
let imageData = UIPasteboard.general.data(forPasteboardType:dataUTI)
return imageAttachment(withData : imageData, dataUTI : dataUTI)
let data = UIPasteboard.general.data(forPasteboardType:dataUTI, inItemSet:itemSet)![0] as! Data
return imageAttachment(data : data, dataUTI : dataUTI)
}
}
for dataUTI in videoUTISet() {
for dataUTI in videoUTISet {
if pasteboardUTISet.contains(dataUTI) {
let imageData = UIPasteboard.general.data(forPasteboardType:dataUTI)
return videoAttachment(withData : imageData, dataUTI : dataUTI)
let data = UIPasteboard.general.data(forPasteboardType:dataUTI, inItemSet:itemSet)![0] as! Data
return videoAttachment(data : data, dataUTI : dataUTI)
}
}
for dataUTI in audioUTISet() {
for dataUTI in audioUTISet {
if pasteboardUTISet.contains(dataUTI) {
let imageData = UIPasteboard.general.data(forPasteboardType:dataUTI)
return audioAttachment(withData : imageData, dataUTI : dataUTI)
let data = UIPasteboard.general.data(forPasteboardType:dataUTI, inItemSet:itemSet)![0] as! Data
return audioAttachment(data : data, dataUTI : dataUTI)
}
}
// TODO: We could handle generic attachments at this point.
@ -218,17 +206,19 @@ class SignalAttachment: NSObject {
//
// NOTE: The attachment returned by this method may not be valid.
// Check the attachment's error property.
public class func imageAttachment(withData imageData: Data?, dataUTI: String!) -> SignalAttachment! {
public class func imageAttachment(data imageData: Data?, dataUTI: String) -> SignalAttachment {
assert(dataUTI.characters.count > 0)
assert(imageData != nil)
guard let imageData = imageData else {
return nil
let attachment = SignalAttachment(data : Data(), dataUTI: dataUTI)
attachment.error = .missingData
return attachment
}
let attachment = SignalAttachment(data : imageData, dataUTI: dataUTI)
guard inputImageUTISet().contains(dataUTI) else {
guard inputImageUTISet.contains(dataUTI) else {
attachment.error = .invalidFileFormat
return attachment
}
@ -239,16 +229,13 @@ class SignalAttachment: NSObject {
return attachment
}
if dataUTI == kUTTypeGIF as String {
guard imageData.count <= kMaxFileSizeGif else {
if animatedImageUTISet.contains(dataUTI) {
guard imageData.count <= kMaxFileSizeAnimatedImage else {
attachment.error = .fileSizeTooLarge
return attachment
}
// We don't re-encode GIFs as JPEGs, presumably in case they are
// animated.
//
// TODO: Consider re-encoding non-animated GIFs as JPEG?
Logger.verbose("\(TAG) Sending raw \(attachment.mimeType()) to retain any animation")
// Never re-encode animated images (i.e. GIFs) as JPEGs.
Logger.verbose("\(TAG) Sending raw \(attachment.mimeType) to retain any animation")
return attachment
} else {
guard let image = UIImage(data:imageData) else {
@ -258,7 +245,7 @@ class SignalAttachment: NSObject {
attachment.image = image
if isInputImageValidOutputImage(image: image, imageData: imageData, dataUTI: dataUTI) {
Logger.verbose("\(TAG) Sending raw \(attachment.mimeType())")
Logger.verbose("\(TAG) Sending raw \(attachment.mimeType)")
return attachment
}
@ -268,31 +255,26 @@ class SignalAttachment: NSObject {
}
private class func defaultImageUploadQuality() -> TSImageQuality {
// always return average image quality
//
// TODO: Why are we using this value? Why not default to .uncropped?
return .medium
// Always default to the highest possible image quality and
// not converting the image if possible.
return .uncropped
}
// If the proposed attachment already is a JPEG, and already conforms to the
// If the proposed attachment already conforms to the
// file size and content size limits, don't recompress it.
//
// TODO: Should non-JPEGs always be converted to JPEG?
private class func isInputImageValidOutputImage(image: UIImage?, imageData: Data?, dataUTI: String!) -> Bool {
private class func isInputImageValidOutputImage(image: UIImage?, imageData: Data?, dataUTI: String) -> Bool {
guard let image = image else {
return false
}
guard let imageData = imageData else {
return false
}
if dataUTI == kUTTypeJPEG as String {
let maxSize = maxSizeForImage(image: image,
imageUploadQuality:defaultImageUploadQuality())
if image.size.width <= maxSize &&
image.size.height <= maxSize &&
imageData.count <= kMaxFileSizeImage {
return true
}
let maxSize = maxSizeForImage(image: image,
imageUploadQuality:defaultImageUploadQuality())
if image.size.width <= maxSize &&
image.size.height <= maxSize &&
imageData.count <= kMaxFileSizeImage {
return true
}
return false
}
@ -301,22 +283,24 @@ class SignalAttachment: NSObject {
//
// NOTE: The attachment returned by this method may nil or not be valid.
// Check the attachment's error property.
public class func imageAttachment(withImage image: UIImage?, dataUTI: String!) -> SignalAttachment! {
public class func imageAttachment(image: UIImage?, dataUTI: String) -> SignalAttachment {
assert(dataUTI.characters.count > 0)
guard let image = image else {
return nil
let attachment = SignalAttachment(data : Data(), dataUTI: dataUTI)
attachment.error = .missingData
return attachment
}
// Make a placeholder attachment on which to hang errors if necessary.
let attachment = SignalAttachment(data : Data(), dataUTI: dataUTI)
attachment.image = image
Logger.verbose("\(TAG) Writing \(attachment.mimeType()) as image/jpeg")
Logger.verbose("\(TAG) Writing \(attachment.mimeType) as image/jpeg")
return compressImageAsJPEG(image : image, attachment : attachment)
}
private class func compressImageAsJPEG(image: UIImage!, attachment: SignalAttachment!) -> SignalAttachment! {
private class func compressImageAsJPEG(image: UIImage, attachment: SignalAttachment) -> SignalAttachment {
assert(attachment.error == nil)
var imageUploadQuality = defaultImageUploadQuality()
@ -405,10 +389,10 @@ class SignalAttachment: NSObject {
//
// NOTE: The attachment returned by this method may not be valid.
// Check the attachment's error property.
public class func videoAttachment(withData data: Data?, dataUTI: String!) -> SignalAttachment! {
return newAttachment(withData : data,
public class func videoAttachment(data: Data?, dataUTI: String) -> SignalAttachment {
return newAttachment(data : data,
dataUTI : dataUTI,
validUTISet : videoUTISet(),
validUTISet : videoUTISet,
maxFileSize : kMaxFileSizeVideo)
}
@ -418,10 +402,10 @@ class SignalAttachment: NSObject {
//
// NOTE: The attachment returned by this method may not be valid.
// Check the attachment's error property.
public class func audioAttachment(withData data: Data?, dataUTI: String!) -> SignalAttachment! {
return newAttachment(withData : data,
public class func audioAttachment(data: Data?, dataUTI: String) -> SignalAttachment {
return newAttachment(data : data,
dataUTI : dataUTI,
validUTISet : audioUTISet(),
validUTISet : audioUTISet,
maxFileSize : kMaxFileSizeAudio)
}
@ -431,8 +415,8 @@ class SignalAttachment: NSObject {
//
// NOTE: The attachment returned by this method may not be valid.
// Check the attachment's error property.
public class func genericAttachment(withData data: Data?, dataUTI: String!) -> SignalAttachment! {
return newAttachment(withData : data,
public class func genericAttachment(data: Data?, dataUTI: String) -> SignalAttachment {
return newAttachment(data : data,
dataUTI : dataUTI,
validUTISet : nil,
maxFileSize : kMaxFileSizeGeneric)
@ -440,21 +424,23 @@ class SignalAttachment: NSObject {
// MARK: Helper Methods
private class func newAttachment(withData data: Data?,
dataUTI: String!,
private class func newAttachment(data: Data?,
dataUTI: String,
validUTISet: Set<String>?,
maxFileSize: Int) -> SignalAttachment! {
maxFileSize: Int) -> SignalAttachment {
assert(dataUTI.characters.count > 0)
assert(data != nil)
guard let data = data else {
return nil
let attachment = SignalAttachment(data : Data(), dataUTI: dataUTI)
attachment.error = .missingData
return attachment
}
let attachment = SignalAttachment(data : data, dataUTI: dataUTI)
if validUTISet != nil {
guard validUTISet!.contains(dataUTI) else {
if let validUTISet = validUTISet {
guard validUTISet.contains(dataUTI) else {
attachment.error = .invalidFileFormat
return attachment
}