parent
7a37de28e5
commit
7f92b5a96b
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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.";
|
||||
|
|
Loading…
Reference in New Issue