Respond to CR.

// FREEBIE
This commit is contained in:
Matthew Chen 2017-05-08 11:13:51 -04:00
parent 7a37de28e5
commit 7f92b5a96b
5 changed files with 105 additions and 69 deletions

View File

@ -10,6 +10,7 @@
#import <AssetsLibrary/AssetsLibrary.h>
#import <JSQMessagesViewController/JSQMessagesMediaViewBubbleImageMasker.h>
#import <MobileCoreServices/MobileCoreServices.h>
#import <SignalServiceKit/MIMETypeUtil.h>
NS_ASSUME_NONNULL_BEGIN
@ -131,8 +132,14 @@ NS_ASSUME_NONNULL_BEGIN
- (void)performEditingAction:(SEL)action
{
if (action == @selector(copy:)) {
NSString *utiType = [MIMETypeUtil utiTypeForMIMEType:self.attachment.contentType];
if (!utiType) {
OWSAssert(0);
utiType = (NSString *)kUTTypeVideo;
}
UIPasteboard *pasteboard = UIPasteboard.generalPasteboard;
[pasteboard setData:self.fileData forPasteboardType:(__bridge NSString *)kUTTypeGIF];
[pasteboard setData:self.fileData forPasteboardType:utiType];
} else if (action == NSSelectorFromString(@"save:")) {
NSData *photoData = self.fileData;
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];

View File

@ -8,6 +8,7 @@
#import "TSAttachmentStream.h"
#import <JSQMessagesViewController/JSQMessagesMediaViewBubbleImageMasker.h>
#import <MobileCoreServices/MobileCoreServices.h>
#import <SignalServiceKit/MimeTypeUtil.h>
NS_ASSUME_NONNULL_BEGIN
@ -116,18 +117,14 @@ NS_ASSUME_NONNULL_BEGIN
if (action == @selector(copy:)) {
// We should always copy to the pasteboard as data, not an UIImage.
// The pasteboard should has as specific as UTI type as possible and
// The pasteboard should have as specific as UTI type as possible and
// data support should be far more general than UIImage support.
OWSAssert(self.attachment.filePath.length > 0);
NSString *fileExtension = [self.attachment.filePath pathExtension];
NSArray *utiTypes = (__bridge_transfer NSArray *)UTTypeCreateAllIdentifiersForTag(
kUTTagClassFilenameExtension, (__bridge CFStringRef)fileExtension, (CFStringRef) @"public.image");
NSString *utiType = (NSString *)kUTTypeImage;
OWSAssert(utiTypes.count > 0);
if (utiTypes.count > 0) {
utiType = utiTypes[0];
}
NSString *utiType = [MIMETypeUtil utiTypeForMIMEType:self.attachment.contentType];
if (!utiType) {
OWSAssert(0);
utiType = (NSString *)kUTTypeImage;
}
NSData *data = [NSData dataWithContentsOfURL:self.attachment.mediaURL];
[UIPasteboard.generalPasteboard setData:data forPasteboardType:utiType];
return;

View File

@ -297,9 +297,13 @@ NS_ASSUME_NONNULL_BEGIN
{
if ([self isVideo]) {
if (action == @selector(copy:)) {
NSString *utiType = [MIMETypeUtil utiTypeForMIMEType:_contentType];
if (!utiType) {
OWSAssert(0);
utiType = (NSString *)kUTTypeVideo;
}
NSData *data = [NSData dataWithContentsOfURL:self.fileURL];
// TODO: This assumes all videos are mp4.
[UIPasteboard.generalPasteboard setData:data forPasteboardType:(NSString *)kUTTypeMPEG4];
[UIPasteboard.generalPasteboard setData:data forPasteboardType:utiType];
return;
} else if (action == NSSelectorFromString(@"save:")) {
if (UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(self.fileURL.path)) {
@ -310,22 +314,30 @@ NS_ASSUME_NONNULL_BEGIN
}
} else if ([self isAudio]) {
if (action == @selector(copy:)) {
NSData *data = [NSData dataWithContentsOfURL:self.fileURL];
NSString *pasteboardType = [MIMETypeUtil getSupportedExtensionFromAudioMIMEType:self.contentType];
if ([pasteboardType isEqualToString:@"mp3"]) {
[UIPasteboard.generalPasteboard setData:data forPasteboardType:(NSString *)kUTTypeMP3];
} else if ([pasteboardType isEqualToString:@"aiff"]) {
[UIPasteboard.generalPasteboard setData:data
forPasteboardType:(NSString *)kUTTypeAudioInterchangeFileFormat];
} else if ([pasteboardType isEqualToString:@"m4a"]) {
[UIPasteboard.generalPasteboard setData:data forPasteboardType:(NSString *)kUTTypeMPEG4Audio];
} else if ([pasteboardType isEqualToString:@"amr"]) {
[UIPasteboard.generalPasteboard setData:data forPasteboardType:@"org.3gpp.adaptive-multi-rate-audio"];
} else {
[UIPasteboard.generalPasteboard setData:data forPasteboardType:(NSString *)kUTTypeAudio];
NSString *utiType = [MIMETypeUtil utiTypeForMIMEType:_contentType];
if (!utiType) {
if ([_contentType isEqualToString:@"audio/amr"]) {
utiType = @"org.3gpp.adaptive-multi-rate-audio";
} else if ([_contentType isEqualToString:@"audio/mp3"] ||
[_contentType isEqualToString:@"audio/x-mpeg"] || [_contentType isEqualToString:@"audio/mpeg"] ||
[_contentType isEqualToString:@"audio/mpeg3"] || [_contentType isEqualToString:@"audio/x-mp3"] ||
[_contentType isEqualToString:@"audio/x-mpeg3"]) {
utiType = (NSString *)kUTTypeMP3;
} else if ([_contentType isEqualToString:@"audio/aac"] ||
[_contentType isEqualToString:@"audio/x-m4a"]) {
utiType = (NSString *)kUTTypeMPEG4Audio;
} else if ([_contentType isEqualToString:@"audio/aiff"] ||
[_contentType isEqualToString:@"audio/x-aiff"]) {
utiType = (NSString *)kUTTypeAudioInterchangeFileFormat;
} else {
OWSAssert(0);
utiType = (NSString *)kUTTypeAudio;
}
}
NSData *data = [NSData dataWithContentsOfURL:self.fileURL];
OWSAssert(data);
[UIPasteboard.generalPasteboard setData:data forPasteboardType:utiType];
}
} else {
// Shouldn't get here, as only supported actions should be exposed via canPerformEditingAction

View File

@ -165,13 +165,13 @@ typedef enum : NSUInteger {
@interface OWSMessagesToolbarContentView ()
@property (nonatomic, weak) id<OWSMessagesToolbarContentDelegate> delegate;
@property (nonatomic, nullable, weak) id<OWSMessagesToolbarContentDelegate> delegate;
@property (nonatomic) BOOL shouldShowVoiceMemoButton;
@property (nonatomic) UIButton *voiceMemoButton;
@property (nonatomic, nullable) UIButton *voiceMemoButton;
@property (nonatomic) UIButton *sendButton;
@property (nonatomic, nullable) UIButton *sendButton;
@property (nonatomic) BOOL isRecordingVoiceMemo;
@ -193,6 +193,26 @@ typedef enum : NSUInteger {
- (void)ensureSubviews
{
if (!self.sendButton) {
OWSAssert(self.rightBarButtonItem);
self.sendButton = self.rightBarButtonItem;
}
if (!self.voiceMemoButton) {
UIImage *icon = [UIImage imageNamed:@"voice-memo-button"];
OWSAssert(icon);
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button setImage:[icon imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]
forState:UIControlStateNormal];
button.imageView.tintColor = [UIColor ows_materialBlueColor];
UILongPressGestureRecognizer *longPressGestureRecognizer =
[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)];
longPressGestureRecognizer.minimumPressDuration = 0;
[button addGestureRecognizer:longPressGestureRecognizer];
self.voiceMemoButton = button;
}
[self ensureShouldShowVoiceMemoButton];
[self ensureVoiceMemoButton];
@ -224,30 +244,7 @@ typedef enum : NSUInteger {
- (void)ensureVoiceMemoButton
{
if (!self.superview) {
return;
}
if (!self.sendButton) {
OWSAssert(self.rightBarButtonItem);
self.sendButton = self.rightBarButtonItem;
}
if (self.shouldShowVoiceMemoButton) {
if (!self.voiceMemoButton) {
UIImage *icon = [UIImage imageNamed:@"voice-memo-button"];
OWSAssert(icon);
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button setImage:[icon imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]
forState:UIControlStateNormal];
button.imageView.tintColor = [UIColor ows_materialBlueColor];
[button
addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self
action:@selector(handleLongPress:)]];
self.voiceMemoButton = button;
}
self.rightBarButtonItem = self.voiceMemoButton;
self.rightBarButtonItemWidth = [self.voiceMemoButton sizeThatFits:CGSizeZero].width;
} else {
@ -338,6 +335,7 @@ typedef enum : NSUInteger {
- (void)toggleSendButtonEnabled
{
// Do nothing; disables JSQ's control over send button enabling.
// Overrides a method in JSQMessagesInputToolbar.
}
- (JSQMessagesToolbarContentView *)loadToolbarContentView {
@ -438,24 +436,24 @@ typedef enum : NSUInteger {
{
OWSAssert([NSThread isMainThread]);
UIView *voiceMemoUI = self.voiceMemoUI;
UIView *oldVoiceMemoUI = self.voiceMemoUI;
self.voiceMemoUI = nil;
NSTimer *voiceMemoUpdateTimer = self.voiceMemoUpdateTimer;
self.voiceMemoUpdateTimer = nil;
[self.voiceMemoUI.layer removeAllAnimations];
[oldVoiceMemoUI.layer removeAllAnimations];
if (animated) {
[UIView animateWithDuration:0.35f
animations:^{
voiceMemoUI.layer.opacity = 0.f;
oldVoiceMemoUI.layer.opacity = 0.f;
}
completion:^(BOOL finished) {
[voiceMemoUI removeFromSuperview];
[oldVoiceMemoUI removeFromSuperview];
[voiceMemoUpdateTimer invalidate];
}];
} else {
[voiceMemoUI removeFromSuperview];
[oldVoiceMemoUI removeFromSuperview];
[voiceMemoUpdateTimer invalidate];
}
}
@ -3049,7 +3047,7 @@ typedef enum : NSUInteger {
[session setCategory:AVAudioSessionCategoryRecord error:&error];
if (error) {
DDLogError(@"%@ Couldn't configure audio session: %@", self.tag, error);
[self resetRecordingVoiceMemo];
[self cancelVoiceMemo];
OWSAssert(0);
return;
}
@ -3065,7 +3063,7 @@ typedef enum : NSUInteger {
error:&error];
if (error) {
DDLogError(@"%@ Couldn't create audioRecorder: %@", self.tag, error);
[self resetRecordingVoiceMemo];
[self cancelVoiceMemo];
OWSAssert(0);
return;
}
@ -3074,17 +3072,28 @@ typedef enum : NSUInteger {
if (![self.audioRecorder prepareToRecord]) {
DDLogError(@"%@ audioRecorder couldn't prepareToRecord.", self.tag);
[self resetRecordingVoiceMemo];
[self cancelVoiceMemo];
OWSAssert(0);
return;
}
if (![self.audioRecorder record]) {
DDLogError(@"%@ audioRecorder couldn't record.", self.tag);
[self resetRecordingVoiceMemo];
[self cancelVoiceMemo];
OWSAssert(0);
return;
}
if (session.recordPermission != AVAudioSessionRecordPermissionGranted) {
DDLogError(@"%@ we do not have recording permission.", self.tag);
[self cancelVoiceMemo];
[ViewControllerUtils
showAlertWithTitle:NSLocalizedString(@"VOICE_MEMO_NEEDS_RECORDING_PERMISSION_ALERT_TITLE",
@"Title of the 'voice memo needs recording permission' alert.")
message:NSLocalizedString(@"VOICE_MEMO_NEEDS_RECORDING_PERMISSION_ALERT_MESSAGE",
@"Message of the 'voice memo needs recording permission' alert.")];
return;
}
}
- (void)endRecordingVoiceMemo
@ -3121,8 +3130,11 @@ typedef enum : NSUInteger {
self.audioRecorder = nil;
NSString *filename = [NSLocalizedString(@"VOICE_MEMO_FILE_NAME", @"Filename for voice memoes.")
stringByAppendingPathExtension:[MIMETypeUtil fileExtensionForUTIType:(NSString *)kUTTypeMPEG4Audio]];
SignalAttachment *attachment =
[SignalAttachment attachmentWithData:audioData dataUTI:(NSString *)kUTTypeMPEG4Audio filename:nil];
[SignalAttachment attachmentWithData:audioData dataUTI:(NSString *)kUTTypeMPEG4Audio filename:filename];
if (!attachment || [attachment hasError]) {
DDLogWarn(@"%@ %s Invalid attachment: %@.",
self.tag,
@ -3492,6 +3504,8 @@ typedef enum : NSUInteger {
- (void)textViewDidChange:(UITextView *)textView
{
// Override.
//
// We want to show the "voice memo" button if the text input is empty
// and the "send" button if it isn't.
[((OWSMessagesToolbarContentView *)self.inputToolbar.contentView)ensureEnabling];

View File

@ -422,7 +422,7 @@
"ERROR_DESCRIPTION_NO_INTERNET" = "Signal was unable to connect to the internet. Please try from another WiFi network or use mobile data.";
/* Error indicating that an outgoing message had no valid recipients. */
"ERROR_DESCRIPTION_NO_VALID_RECIPIENTS" = "ERROR_DESCRIPTION_NO_VALID_RECIPIENTS";
"ERROR_DESCRIPTION_NO_VALID_RECIPIENTS" = "Message send failed due to a lack of valid recipients.";
/* Error message when attempting to send message */
"ERROR_DESCRIPTION_SENDING_UNAUTHORIZED" = "Your device is no longer registered for your phone number. You must remove and reinstall Signal.";
@ -860,9 +860,6 @@
/* Label for 'Pager' phone numbers. */
"PHONE_NUMBER_TYPE_PAGER" = "Pager";
/* Label for 'Radio' phone numbers. */
"PHONE_NUMBER_TYPE_RADIO" = "Radio";
/* Label used when we don't what kind of phone number it is (e.g. mobile/work/home). */
"PHONE_NUMBER_TYPE_UNKNOWN" = "Unknown";
@ -1281,7 +1278,16 @@
"VERIFY_PRIVACY" = "Verify Safety Number";
/* Indicates how to cancel a voice memo. */
"VOICE_MEMO_CANCEL_INSTRUCTIONS" = "Slide To Cancel";
"VOICE_MEMO_CANCEL_INSTRUCTIONS" = "Slide to Cancel";
/* Filename for voice memoes. */
"VOICE_MEMO_FILE_NAME" = "Voice Memo";
/* Message of the 'voice memo needs recording permission' alert. */
"VOICE_MEMO_NEEDS_RECORDING_PERMISSION_ALERT_MESSAGE" = "You can grant Signal access to your microphone in the Settings App >> Privacy >> Microphone >> Signal.";
/* Title of the 'voice memo needs recording permission' alert. */
"VOICE_MEMO_NEEDS_RECORDING_PERMISSION_ALERT_TITLE" = "Can't Record";
/* Activity indicator title, shown upon returning to the device manager, until you complete the provisioning process on desktop */
"WAITING_TO_COMPLETE_DEVICE_LINK_TEXT" = "Complete setup on Signal Desktop.";