Merge pull request #1105 from michaelkirk/fix-cannot-delete-media-messages#704
consistent Delete/Copy/Save UX across media types
This commit is contained in:
commit
bd1e99026f
17
BUILDING.md
17
BUILDING.md
@ -17,8 +17,21 @@ occasionally, CocoaPods itself will need to be updated. Do this with
|
||||
sudo gem update
|
||||
```
|
||||
|
||||
3) Open the `Signal.xcworkspace` in Xcode. Build and Run and you are ready to go!
|
||||
3) Open the `Signal.xcworkspace` in Xcode.
|
||||
|
||||
```
|
||||
open Signal.xcworkspace
|
||||
```
|
||||
|
||||
4) Some of our build scripts, like running tests, expect your Derived
|
||||
Data directory to be `$(PROJECT_DIR)/build`. In Xcode, go to `Preferences-> Locations`,
|
||||
and set the "Derived Data" dropdown to "Relative" and the text field
|
||||
value to "build".
|
||||
|
||||
5) Build and Run and you are ready to go!
|
||||
|
||||
## Known issues
|
||||
|
||||
Features related to push notifications are known to be not working for third-party contributors since Apple's Push Notification service pushs will only work with Open Whisper Systems production code signing certificate.
|
||||
Features related to push notifications are known to be not working for third-party contributors since Apple's Push Notification service pushs will only work with Open Whisper Systems production code signing certificate.
|
||||
|
||||
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
45843D1F1D2236B30013E85A /* OWSContactsSearcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 45843D1E1D2236B30013E85A /* OWSContactsSearcher.m */; };
|
||||
45843D201D2236B30013E85A /* OWSContactsSearcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 45843D1E1D2236B30013E85A /* OWSContactsSearcher.m */; };
|
||||
45843D221D223BA10013E85A /* OWSContactsSearcherTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 45843D211D223BA10013E85A /* OWSContactsSearcherTest.m */; };
|
||||
459C3F0D1C9B3A1B003ACF51 /* TSMessageAdapterTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 459C3F0C1C9B3A1B003ACF51 /* TSMessageAdapterTest.m */; };
|
||||
45C681B71D305A580050903A /* OWSCall.m in Sources */ = {isa = PBXBuildFile; fileRef = 45C681B61D305A580050903A /* OWSCall.m */; };
|
||||
45C681B81D305A580050903A /* OWSCall.m in Sources */ = {isa = PBXBuildFile; fileRef = 45C681B61D305A580050903A /* OWSCall.m */; };
|
||||
45C681BC1D305C080050903A /* OWSCallCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 45C681BA1D305C080050903A /* OWSCallCollectionViewCell.m */; };
|
||||
@ -503,6 +504,7 @@
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
4526BD481CA61C8D00166BC8 /* OWSMessageEditing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageEditing.h; sourceTree = "<group>"; };
|
||||
453CC0361D08E1A60040EBA3 /* sn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sn; path = translations/sn.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
453D28AF1D32B87100D523F0 /* OWSErrorMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSErrorMessage.h; sourceTree = "<group>"; };
|
||||
453D28B01D32B87100D523F0 /* OWSErrorMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSErrorMessage.m; sourceTree = "<group>"; };
|
||||
@ -516,6 +518,7 @@
|
||||
45843D1D1D2236B30013E85A /* OWSContactsSearcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSContactsSearcher.h; sourceTree = "<group>"; };
|
||||
45843D1E1D2236B30013E85A /* OWSContactsSearcher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactsSearcher.m; sourceTree = "<group>"; };
|
||||
45843D211D223BA10013E85A /* OWSContactsSearcherTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactsSearcherTest.m; sourceTree = "<group>"; };
|
||||
459C3F0C1C9B3A1B003ACF51 /* TSMessageAdapterTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TSMessageAdapterTest.m; path = "view controllers/Signals/TSMessageAdapters/TSMessageAdapterTest.m"; sourceTree = "<group>"; };
|
||||
45C681B51D305A580050903A /* OWSCall.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSCall.h; sourceTree = "<group>"; };
|
||||
45C681B61D305A580050903A /* OWSCall.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSCall.m; sourceTree = "<group>"; };
|
||||
45C681B91D305C080050903A /* OWSCallCollectionViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSCallCollectionViewCell.h; sourceTree = "<group>"; };
|
||||
@ -1135,6 +1138,14 @@
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
459C3F0E1C9B3A20003ACF51 /* TSMessageAdapters */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
459C3F0C1C9B3A1B003ACF51 /* TSMessageAdapterTest.m */,
|
||||
);
|
||||
name = TSMessageAdapters;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
70B8009F190C529C0042E3F0 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -1736,6 +1747,7 @@
|
||||
B62D53F51A23CCAD009AAF82 /* TSMessageAdapter.h */,
|
||||
B62D53F61A23CCAD009AAF82 /* TSMessageAdapter.m */,
|
||||
B6D3CBCE1C1376BE00C039DF /* TSContentAdapters.h */,
|
||||
4526BD481CA61C8D00166BC8 /* OWSMessageEditing.h */,
|
||||
);
|
||||
name = TSMessageAdapters;
|
||||
path = TSMessageAdapaters;
|
||||
@ -1794,6 +1806,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
457F3AB01D1470CF00C51351 /* view controllers */,
|
||||
459C3F0E1C9B3A20003ACF51 /* TSMessageAdapters */,
|
||||
B660F66D1C29867F00687D6E /* audio */,
|
||||
B660F6731C29867F00687D6E /* call */,
|
||||
B660F6751C29867F00687D6E /* contact */,
|
||||
@ -2789,6 +2802,7 @@
|
||||
B660F7051C29988E00687D6E /* EncodedAudioFrame.m in Sources */,
|
||||
B660F7061C29988E00687D6E /* EncodedAudioPacket.m in Sources */,
|
||||
B660F7071C29988E00687D6E /* AudioProcessor.m in Sources */,
|
||||
459C3F0D1C9B3A1B003ACF51 /* TSMessageAdapterTest.m in Sources */,
|
||||
B660F7081C29988E00687D6E /* AudioStretcher.m in Sources */,
|
||||
B660F7091C29988E00687D6E /* DesiredBufferDepthController.m in Sources */,
|
||||
B660F70A1C29988E00687D6E /* DropoutTracker.m in Sources */,
|
||||
@ -3298,7 +3312,6 @@
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(SRCROOT)",
|
||||
"$(PROJECT_DIR)/build/Debug-iphoneos",
|
||||
);
|
||||
OTHER_LDFLAGS = (
|
||||
"-all_load",
|
||||
@ -3351,7 +3364,6 @@
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(SRCROOT)",
|
||||
"$(PROJECT_DIR)/build/Debug-iphoneos",
|
||||
);
|
||||
OTHER_LDFLAGS = (
|
||||
"-all_load",
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.3.6</string>
|
||||
<string>2.4.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
@ -38,7 +38,7 @@
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>2.3.6.0</string>
|
||||
<string>2.4.0</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LOGS_EMAIL</key>
|
||||
|
||||
8
Signal/src/Models/TSMessageAdapaters/OWSMessageEditing.h
Normal file
8
Signal/src/Models/TSMessageAdapaters/OWSMessageEditing.h
Normal file
@ -0,0 +1,8 @@
|
||||
// Copyright © 2016 Open Whisper Systems. All rights reserved.
|
||||
|
||||
@protocol OWSMessageEditing <NSObject>
|
||||
|
||||
- (BOOL)canPerformEditingAction:(SEL)action;
|
||||
- (void)performEditingAction:(SEL)action;
|
||||
|
||||
@end
|
||||
@ -6,11 +6,12 @@
|
||||
// Copyright (c) 2015 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "OWSMessageEditing.h"
|
||||
#import <JSQMessagesViewController/JSQPhotoMediaItem.h>
|
||||
#import "TSAttachmentStream.h"
|
||||
|
||||
@interface TSAnimatedAdapter : JSQMediaItem
|
||||
@class TSAttachmentStream;
|
||||
|
||||
@interface TSAnimatedAdapter : JSQMediaItem <OWSMessageEditing>
|
||||
|
||||
- (instancetype)initWithAttachment:(TSAttachmentStream *)attachment;
|
||||
|
||||
@ -19,5 +20,6 @@
|
||||
- (BOOL)isVideo;
|
||||
|
||||
@property NSString *attachmentId;
|
||||
@property NSData *fileData;
|
||||
|
||||
@end
|
||||
|
||||
@ -8,13 +8,15 @@
|
||||
|
||||
#import "TSAnimatedAdapter.h"
|
||||
#import "FLAnimatedImage.h"
|
||||
#import "TSAttachmentStream.h"
|
||||
#import "UIDevice+TSHardwareVersion.h"
|
||||
#import <AssetsLibrary/AssetsLibrary.h>
|
||||
#import <JSQMessagesViewController/JSQMessagesMediaViewBubbleImageMasker.h>
|
||||
#import <MobileCoreServices/MobileCoreServices.h>
|
||||
|
||||
@interface TSAnimatedAdapter ()
|
||||
|
||||
@property (strong, nonatomic) UIImageView *cachedImageView;
|
||||
@property (strong, nonatomic) NSData *fileData;
|
||||
@property (strong, nonatomic) UIImage *image;
|
||||
@property (strong, nonatomic) TSAttachmentStream *attachment;
|
||||
|
||||
@ -92,6 +94,36 @@
|
||||
return NO;
|
||||
}
|
||||
|
||||
#pragma mark - OWSMessageEditing Protocol
|
||||
|
||||
- (BOOL)canPerformEditingAction:(SEL)action
|
||||
{
|
||||
return (action == @selector(copy:) || action == NSSelectorFromString(@"save:"));
|
||||
}
|
||||
|
||||
- (void)performEditingAction:(SEL)action
|
||||
{
|
||||
if (action == @selector(copy:)) {
|
||||
UIPasteboard *pasteBoard = UIPasteboard.generalPasteboard;
|
||||
[pasteBoard setData:self.fileData forPasteboardType:(__bridge NSString *)kUTTypeGIF];
|
||||
} else if (action == NSSelectorFromString(@"save:")) {
|
||||
NSData *photoData = self.fileData;
|
||||
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
|
||||
[library writeImageDataToSavedPhotosAlbum:photoData
|
||||
metadata:nil
|
||||
completionBlock:^(NSURL *assetURL, NSError *error) {
|
||||
if (error) {
|
||||
DDLogWarn(@"Error Saving image to photo album: %@", error);
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
// Shouldn't get here, as only supported actions should be exposed via canPerformEditingAction
|
||||
NSString *actionString = NSStringFromSelector(action);
|
||||
DDLogError(@"'%@' action unsupported for %@: attachmentId=%@", actionString, [self class], self.attachmentId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Utility
|
||||
|
||||
- (CGSize)getBubbleSizeForImage:(UIImage *)image {
|
||||
|
||||
@ -6,11 +6,11 @@
|
||||
// Copyright (c) 2014 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "OWSMessageEditing.h"
|
||||
#import <JSQMessagesViewController/JSQMessageData.h>
|
||||
#import "TSInteraction.h"
|
||||
#import "TSMessageAdapter.h"
|
||||
#import "TSThread.h"
|
||||
|
||||
@class TSInteraction;
|
||||
@class TSThread;
|
||||
|
||||
#define ME_MESSAGE_IDENTIFIER @"Me";
|
||||
|
||||
@ -24,10 +24,11 @@ typedef NS_ENUM(NSInteger, TSMessageAdapterType) {
|
||||
TSGenericTextMessageAdapter, // Used when message direction is unknown (outgoing or incoming)
|
||||
};
|
||||
|
||||
@interface TSMessageAdapter : NSObject <JSQMessageData>
|
||||
@interface TSMessageAdapter : NSObject <JSQMessageData, OWSMessageEditing>
|
||||
|
||||
+ (id<JSQMessageData>)messageViewDataWithInteraction:(TSInteraction *)interaction inThread:(TSThread *)thread;
|
||||
|
||||
@property TSInteraction *interaction;
|
||||
@property TSMessageAdapterType messageType;
|
||||
|
||||
@end
|
||||
|
||||
@ -1,19 +1,21 @@
|
||||
//
|
||||
// TSMessageAdapter.m
|
||||
//
|
||||
// Signal
|
||||
//
|
||||
// Created by Frederic Jacobs on 24/11/14.
|
||||
// Copyright (c) 2014 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TSAttachmentPointer.h"
|
||||
#import "TSCall.h"
|
||||
#import "OWSCall.h"
|
||||
#import "TSAttachmentPointer.h"
|
||||
#import "TSAttachmentStream.h"
|
||||
#import "TSCall.h"
|
||||
#import "TSContentAdapters.h"
|
||||
#import "TSErrorMessage.h"
|
||||
#import "TSIncomingMessage.h"
|
||||
#import "TSInfoMessage.h"
|
||||
#import "TSOutgoingMessage.h"
|
||||
#import <MobileCoreServices/MobileCoreServices.h>
|
||||
|
||||
|
||||
@interface TSMessageAdapter ()
|
||||
@ -41,7 +43,7 @@
|
||||
|
||||
// for MediaMessages
|
||||
|
||||
@property JSQMediaItem *mediaItem;
|
||||
@property JSQMediaItem<OWSMessageEditing> *mediaItem;
|
||||
|
||||
// ---
|
||||
|
||||
@ -57,6 +59,7 @@
|
||||
|
||||
+ (id<JSQMessageData>)messageViewDataWithInteraction:(TSInteraction *)interaction inThread:(TSThread *)thread {
|
||||
TSMessageAdapter *adapter = [[TSMessageAdapter alloc] init];
|
||||
adapter.interaction = interaction;
|
||||
adapter.messageDate = interaction.date;
|
||||
// TODO casting a string to an integer? At least need a comment here explaining why we are doing this.
|
||||
adapter.identifier = (NSUInteger)interaction.uniqueId;
|
||||
@ -236,6 +239,57 @@
|
||||
return self.messageDate;
|
||||
}
|
||||
|
||||
#pragma mark - OWSMessageEditing Protocol
|
||||
|
||||
- (BOOL)canPerformEditingAction:(SEL)action
|
||||
{
|
||||
|
||||
// Deletes are always handled by TSMessageAdapter
|
||||
if (action == @selector(delete:)) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// Delegate other actions for media items
|
||||
if (self.isMediaMessage) {
|
||||
return [self.mediaItem canPerformEditingAction:action];
|
||||
} else {
|
||||
// Text message - no media attachment
|
||||
if (action == @selector(copy:)) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)performEditingAction:(SEL)action
|
||||
{
|
||||
// Deletes are always handled by TSMessageAdapter
|
||||
if (action == @selector(delete:)) {
|
||||
DDLogDebug(@"Deleting interaction with uniqueId: %@", self.interaction.uniqueId);
|
||||
[self.interaction remove];
|
||||
return;
|
||||
}
|
||||
|
||||
// Delegate other actions for media items
|
||||
if (self.isMediaMessage) {
|
||||
[self.mediaItem performEditingAction:action];
|
||||
return;
|
||||
} else {
|
||||
// Text message - no media attachment
|
||||
if (action == @selector(copy:)) {
|
||||
UIPasteboard.generalPasteboard.string = self.messageBody;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Shouldn't get here, as only supported actions should be exposed via canPerformEditingAction
|
||||
NSString *actionString = NSStringFromSelector(action);
|
||||
DDLogError(@"'%@' action unsupported for TSInteraction: uniqueId=%@, mediaType=%@",
|
||||
actionString,
|
||||
self.interaction.uniqueId,
|
||||
[self.mediaItem class]);
|
||||
}
|
||||
|
||||
- (BOOL)isMediaMessage {
|
||||
return _mediaItem ? YES : NO;
|
||||
}
|
||||
|
||||
@ -1,17 +1,20 @@
|
||||
// Created by Frederic Jacobs on 17/12/14.
|
||||
// Copyright (c) 2014 Open Whisper Systems. All rights reserved.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "OWSMessageEditing.h"
|
||||
#import <JSQMessagesViewController/JSQPhotoMediaItem.h>
|
||||
#import "TSAttachmentStream.h"
|
||||
|
||||
@interface TSPhotoAdapter : JSQPhotoMediaItem
|
||||
@class TSAttachmentStream;
|
||||
|
||||
@interface TSPhotoAdapter : JSQPhotoMediaItem <OWSMessageEditing>
|
||||
|
||||
- (instancetype)initWithAttachment:(TSAttachmentStream *)attachment;
|
||||
|
||||
- (BOOL)isImage;
|
||||
- (BOOL)isAudio;
|
||||
- (BOOL)isVideo;
|
||||
|
||||
@property TSAttachmentStream *attachment;
|
||||
@property NSString *attachmentId;
|
||||
|
||||
@end
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
// Copyright (c) 2014 Open Whisper Systems. All rights reserved.
|
||||
|
||||
#import "TSPhotoAdapter.h"
|
||||
#import "TSAttachmentStream.h"
|
||||
#import "UIDevice+TSHardwareVersion.h"
|
||||
#import <JSQMessagesViewController/JSQMessagesMediaViewBubbleImageMasker.h>
|
||||
|
||||
@ -15,10 +16,14 @@
|
||||
- (instancetype)initWithAttachment:(TSAttachmentStream *)attachment {
|
||||
self = [super initWithImage:attachment.image];
|
||||
|
||||
if (self) {
|
||||
_cachedImageView = nil;
|
||||
_attachmentId = attachment.uniqueId;
|
||||
if (!self) {
|
||||
return self;
|
||||
}
|
||||
|
||||
_cachedImageView = nil;
|
||||
_attachment = attachment;
|
||||
_attachmentId = attachment.uniqueId;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@ -71,6 +76,28 @@
|
||||
return NO;
|
||||
}
|
||||
|
||||
#pragma mark - OWSMessageEditing Protocol
|
||||
|
||||
- (BOOL)canPerformEditingAction:(SEL)action
|
||||
{
|
||||
return (action == @selector(copy:) || action == NSSelectorFromString(@"save:"));
|
||||
}
|
||||
|
||||
- (void)performEditingAction:(SEL)action
|
||||
{
|
||||
if (action == @selector(copy:)) {
|
||||
UIPasteboard.generalPasteboard.image = self.image;
|
||||
return;
|
||||
} else if (action == NSSelectorFromString(@"save:")) {
|
||||
UIImageWriteToSavedPhotosAlbum(self.image, nil, nil, nil);
|
||||
return;
|
||||
}
|
||||
|
||||
// Shouldn't get here, as only supported actions should be exposed via canPerformEditingAction
|
||||
NSString *actionString = NSStringFromSelector(action);
|
||||
DDLogError(@"'%@' action unsupported for %@: attachmentId=%@", actionString, self.class, self.attachmentId);
|
||||
}
|
||||
|
||||
#pragma mark - Utility
|
||||
|
||||
- (CGSize)getBubbleSizeForImage:(UIImage *)image {
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
// Created by Frederic Jacobs on 17/12/14.
|
||||
// Copyright (c) 2014 Open Whisper Systems. All rights reserved.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "OWSMessageEditing.h"
|
||||
#import <JSQMessagesViewController/JSQVideoMediaItem.h>
|
||||
#import "TSAttachmentStream.h"
|
||||
|
||||
@interface TSVideoAttachmentAdapter : JSQVideoMediaItem
|
||||
@class TSAttachmentStream;
|
||||
|
||||
@interface TSVideoAttachmentAdapter : JSQVideoMediaItem <OWSMessageEditing>
|
||||
|
||||
@property NSString *attachmentId;
|
||||
@property (nonatomic, strong) NSString *contentType;
|
||||
|
||||
@ -3,10 +3,12 @@
|
||||
|
||||
#import "TSVideoAttachmentAdapter.h"
|
||||
#import "MIMETypeUtil.h"
|
||||
#import "TSAttachmentStream.h"
|
||||
#import "TSMessagesManager.h"
|
||||
#import "TSStorageManager+keyingMaterial.h"
|
||||
#import <FFCircularProgressView.h>
|
||||
#import <JSQMessagesViewController/JSQMessagesMediaViewBubbleImageMasker.h>
|
||||
#import <MobileCoreServices/MobileCoreServices.h>
|
||||
#import <SCWaveformView.h>
|
||||
#define AUDIO_BAR_HEIGHT 36
|
||||
|
||||
@ -242,4 +244,62 @@
|
||||
_cachedImageView = nil;
|
||||
}
|
||||
|
||||
#pragma mark - OWSMessageEditing Protocol
|
||||
|
||||
- (BOOL)canPerformEditingAction:(SEL)action
|
||||
{
|
||||
if ([self isVideo]) {
|
||||
return (action == @selector(copy:) || action == NSSelectorFromString(@"save:"));
|
||||
} else if ([self isAudio]) {
|
||||
return (action == @selector(copy:));
|
||||
}
|
||||
|
||||
NSString *actionString = NSStringFromSelector(action);
|
||||
DDLogError(
|
||||
@"Unexpected action: %@ for VideoAttachmentAdapter with contentType: %@", actionString, self.contentType);
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)performEditingAction:(SEL)action
|
||||
{
|
||||
if ([self isVideo]) {
|
||||
if (action == @selector(copy:)) {
|
||||
NSData *data = [NSData dataWithContentsOfURL:self.fileURL];
|
||||
[UIPasteboard.generalPasteboard setData:data forPasteboardType:(NSString *)kUTTypeMPEG4];
|
||||
return;
|
||||
} else if (action == NSSelectorFromString(@"save:")) {
|
||||
if (UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(self.fileURL.path)) {
|
||||
UISaveVideoAtPathToSavedPhotosAlbum(self.fileURL.path, self, nil, nil);
|
||||
} else {
|
||||
DDLogWarn(@"cowardly refusing to save incompatible video attachment");
|
||||
}
|
||||
}
|
||||
} else if ([self isAudio]) {
|
||||
if (action == @selector(copy:)) {
|
||||
NSData *data = [NSData dataWithContentsOfURL:self.fileURL];
|
||||
|
||||
NSString *pasteboardType = [MIMETypeUtil getSupportedExtensionFromAudioMIMEType:self.contentType];
|
||||
[UIPasteboard.generalPasteboard setData:data forPasteboardType:(NSString *)UIPasteboardNameGeneral];
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Shouldn't get here, as only supported actions should be exposed via canPerformEditingAction
|
||||
NSString *actionString = NSStringFromSelector(action);
|
||||
DDLogError(
|
||||
@"Unexpected action: %@ for VideoAttachmentAdapter with contentType: %@", actionString, self.contentType);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@ -7,7 +7,6 @@
|
||||
// Copyright (c) 2014 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <AssetsLibrary/AssetsLibrary.h>
|
||||
#import "DJWActionSheet+OWS.h"
|
||||
#import "FLAnimatedImage.h"
|
||||
#import "FullImageViewController.h"
|
||||
@ -143,18 +142,6 @@
|
||||
[self.view addGestureRecognizer:self.doubleTap];
|
||||
}
|
||||
|
||||
- (void)initializeShareButton {
|
||||
CGFloat buttonRadius = 50.0f;
|
||||
CGFloat x = 14.0f;
|
||||
CGFloat y = self.view.bounds.size.height - buttonRadius - 10.0f;
|
||||
|
||||
self.shareButton = [[UIButton alloc] initWithFrame:CGRectMake(x, y, buttonRadius, buttonRadius)];
|
||||
[self.shareButton addTarget:self action:@selector(shareButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
|
||||
[self.shareButton setImage:[UIImage imageNamed:@"savephoto"] forState:UIControlStateNormal];
|
||||
|
||||
[self.view addSubview:self.shareButton];
|
||||
}
|
||||
|
||||
#pragma mark - Gesture Recognizers
|
||||
|
||||
- (void)imageDoubleTapped:(UITapGestureRecognizer *)doubleTap {
|
||||
@ -220,7 +207,6 @@
|
||||
self.scrollView.frame = self.view.bounds;
|
||||
[self.scrollView addSubview:self.imageView];
|
||||
[self updateLayouts];
|
||||
[self initializeShareButton];
|
||||
self.view.userInteractionEnabled = YES;
|
||||
_isPresenting = NO;
|
||||
}];
|
||||
@ -365,52 +351,6 @@
|
||||
return size.height / size.width;
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (void)shareButtonTapped:(UIButton *)sender {
|
||||
[DJWActionSheet showInView:self.view
|
||||
withTitle:nil
|
||||
cancelButtonTitle:NSLocalizedString(@"TXT_CANCEL_TITLE", @"")
|
||||
destructiveButtonTitle:NSLocalizedString(@"TXT_DELETE_TITLE", @"")
|
||||
otherButtonTitles:@[
|
||||
NSLocalizedString(@"CAMERA_ROLL_SAVE_BUTTON", @""),
|
||||
NSLocalizedString(@"CAMERA_ROLL_COPY_BUTTON", @"")
|
||||
]
|
||||
tapBlock:^(DJWActionSheet *actionSheet, NSInteger tappedButtonIndex) {
|
||||
if (tappedButtonIndex == actionSheet.cancelButtonIndex) {
|
||||
} else if (tappedButtonIndex == actionSheet.destructiveButtonIndex) {
|
||||
__block TSInteraction *interaction = [self interaction];
|
||||
[self dismissViewControllerAnimated:YES
|
||||
completion:^{
|
||||
[interaction remove];
|
||||
}];
|
||||
|
||||
} else {
|
||||
switch (tappedButtonIndex) {
|
||||
case 0: {
|
||||
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
|
||||
[library writeImageDataToSavedPhotosAlbum:self.fileData
|
||||
metadata:nil
|
||||
completionBlock:^(NSURL *assetURL, NSError *error) {
|
||||
if (error) {
|
||||
DDLogWarn(@"Error Saving image to photo album: %@",
|
||||
error);
|
||||
}
|
||||
}];
|
||||
break;
|
||||
}
|
||||
case 1:
|
||||
[[UIPasteboard generalPasteboard] setImage:self.image];
|
||||
break;
|
||||
default:
|
||||
DDLogWarn(@"Illegal Action sheet field #%ld <%s>",
|
||||
(long)tappedButtonIndex,
|
||||
__PRETTY_FUNCTION__);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Saving images to Camera Roll
|
||||
|
||||
|
||||
@ -198,7 +198,10 @@ typedef enum : NSUInteger {
|
||||
[self initializeTextView];
|
||||
|
||||
[JSQMessagesCollectionViewCell registerMenuAction:@selector(delete:)];
|
||||
self.collectionView.collectionViewLayout.bubbleSizeCalculator = [[OWSMessagesBubblesSizeCalculator alloc] init];
|
||||
SEL saveSelector = NSSelectorFromString(@"save:");
|
||||
[JSQMessagesCollectionViewCell registerMenuAction:saveSelector];
|
||||
[UIMenuController sharedMenuController].menuItems = @[ [[UIMenuItem alloc] initWithTitle:NSLocalizedString(@"EDIT_ITEM_SAVE_ACTION", @"Short name for edit menu item to save contents of media message.")
|
||||
action:saveSelector] ];
|
||||
|
||||
[self initializeCollectionViewLayout];
|
||||
[self registerCustomMessageNibs];
|
||||
@ -531,6 +534,7 @@ typedef enum : NSUInteger {
|
||||
|
||||
- (void)initializeBubbles
|
||||
{
|
||||
self.collectionView.collectionViewLayout.bubbleSizeCalculator = [[OWSMessagesBubblesSizeCalculator alloc] init];
|
||||
JSQMessagesBubbleImageFactory *bubbleFactory = [[JSQMessagesBubbleImageFactory alloc] init];
|
||||
self.incomingBubbleImageData = [bubbleFactory incomingMessagesBubbleImageWithColor:[UIColor jsq_messageBubbleLightGrayColor]];
|
||||
self.outgoingBubbleImageData = [bubbleFactory outgoingMessagesBubbleImageWithColor:[UIColor ows_materialBlueColor]];
|
||||
@ -701,6 +705,21 @@ typedef enum : NSUInteger {
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)collectionView:(JSQMessagesCollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if (indexPath == nil) {
|
||||
DDLogError(@"Aborting shouldShowMenuForItemAtIndexPath because indexPath is nil");
|
||||
// Not sure why this is nil, but occasionally it is, which crashes.
|
||||
return NO;
|
||||
}
|
||||
|
||||
// JSQM does some setup in super method
|
||||
[super collectionView:collectionView shouldShowMenuForItemAtIndexPath:indexPath];
|
||||
|
||||
// Super method returns false for media methods. We want menu for *all* items
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - JSQMessages CollectionView DataSource
|
||||
|
||||
- (id<JSQMessageData>)collectionView:(JSQMessagesCollectionView *)collectionView
|
||||
@ -1350,13 +1369,6 @@ typedef enum : NSUInteger {
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)deleteMessageAtIndexPath:(NSIndexPath *)indexPath {
|
||||
[self.editingDatabaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||
TSInteraction *interaction = [self interactionAtIndexPath:indexPath];
|
||||
[interaction removeWithTransaction:transaction];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)handleErrorMessageTap:(TSErrorMessage *)message {
|
||||
if ([message isKindOfClass:[TSInvalidIdentityKeyErrorMessage class]]) {
|
||||
TSInvalidIdentityKeyErrorMessage *errorMessage = (TSInvalidIdentityKeyErrorMessage *)message;
|
||||
@ -1813,6 +1825,7 @@ typedef enum : NSUInteger {
|
||||
return message;
|
||||
}
|
||||
|
||||
// FIXME DANGER this method doesn't always return TSMessageAdapters - it can also return JSQCall!
|
||||
- (TSMessageAdapter *)messageAtIndexPath:(NSIndexPath *)indexPath {
|
||||
TSInteraction *interaction = [self interactionAtIndexPath:indexPath];
|
||||
|
||||
@ -1923,22 +1936,24 @@ typedef enum : NSUInteger {
|
||||
canPerformAction:(SEL)action
|
||||
forItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
withSender:(id)sender {
|
||||
if (action == @selector(delete:)) {
|
||||
return YES;
|
||||
|
||||
TSMessageAdapter *messageAdapter = [self messageAtIndexPath:indexPath];
|
||||
// HACK make sure method exists before calling since messageAtIndexPath doesn't
|
||||
// always return TSMessageAdapters - it can also return JSQCall!
|
||||
if ([messageAdapter respondsToSelector:@selector(canPerformEditingAction:)]) {
|
||||
return [messageAdapter canPerformEditingAction:action];
|
||||
}
|
||||
else {
|
||||
return NO;
|
||||
}
|
||||
|
||||
return [super collectionView:collectionView canPerformAction:action forItemAtIndexPath:indexPath withSender:sender];
|
||||
}
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView
|
||||
performAction:(SEL)action
|
||||
forItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
withSender:(id)sender {
|
||||
if (action == @selector(delete:)) {
|
||||
[self deleteMessageAtIndexPath:indexPath];
|
||||
} else {
|
||||
[super collectionView:collectionView performAction:action forItemAtIndexPath:indexPath withSender:sender];
|
||||
}
|
||||
[[self messageAtIndexPath:indexPath] performEditingAction:action];
|
||||
}
|
||||
|
||||
- (void)updateGroup {
|
||||
|
||||
@ -0,0 +1,276 @@
|
||||
// Copyright © 2016 Open Whisper Systems. All rights reserved.
|
||||
|
||||
#import "TSAttachmentStream.h"
|
||||
#import "TSContentAdapters.h"
|
||||
#import "TSInteraction.h"
|
||||
#import <MobileCoreServices/MobileCoreServices.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
static NSString * const kTestingInteractionId = @"some-fake-testing-id";
|
||||
|
||||
@interface TSMessageAdapter (Testing)
|
||||
|
||||
// expose some private setters for ease of testing setup
|
||||
@property (nonatomic, retain) NSString *messageBody;
|
||||
@property JSQMediaItem *mediaItem;
|
||||
|
||||
@end
|
||||
|
||||
@interface TSMessageAdapterTest : XCTestCase
|
||||
|
||||
@property TSMessageAdapter *messageAdapter;
|
||||
@property TSInteraction *interaction;
|
||||
@property (readonly) NSData *fakeAudioData;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TSMessageAdapterTest
|
||||
|
||||
- (NSData *)fakeAudioData
|
||||
{
|
||||
NSString *fakeAudioString = @"QmxhY2tiaXJkIFJhdW0gRG90IE1QMw==";
|
||||
return [[NSData alloc] initWithBase64EncodedString:fakeAudioString options:0];
|
||||
}
|
||||
|
||||
- (NSData *)fakeVideoData
|
||||
{
|
||||
NSString *fakeVideoString = @"RmFrZSBWaWRlbyBEYXRh";
|
||||
return [[NSData alloc] initWithBase64EncodedString:fakeVideoString options:0];
|
||||
}
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
[super setUp];
|
||||
|
||||
self.messageAdapter = [[TSMessageAdapter alloc] init];
|
||||
|
||||
self.interaction = [[TSInteraction alloc] initWithUniqueId:kTestingInteractionId];
|
||||
[self.interaction save];
|
||||
self.messageAdapter.interaction = self.interaction;
|
||||
}
|
||||
|
||||
- (void)tearDown
|
||||
{
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
[super tearDown];
|
||||
}
|
||||
|
||||
// Test canPerformAction
|
||||
|
||||
- (void)testCanPerformEditingActionWithNonMediaMessage
|
||||
{
|
||||
XCTAssertTrue([self.messageAdapter canPerformEditingAction:@selector(delete:)]);
|
||||
XCTAssertTrue([self.messageAdapter canPerformEditingAction:@selector(copy:)]);
|
||||
|
||||
XCTAssertFalse([self.messageAdapter canPerformEditingAction:NSSelectorFromString(@"save:")]);
|
||||
|
||||
//e.g. any other unsupported action
|
||||
XCTAssertFalse([self.messageAdapter canPerformEditingAction:@selector(paste:)]);
|
||||
}
|
||||
|
||||
- (void)testCanPerformEditingActionWithPhotoMessage
|
||||
{
|
||||
self.messageAdapter.mediaItem = [[TSPhotoAdapter alloc] init];
|
||||
|
||||
XCTAssertTrue([self.messageAdapter canPerformEditingAction:@selector(delete:)]);
|
||||
XCTAssertTrue([self.messageAdapter canPerformEditingAction:@selector(copy:)]);
|
||||
XCTAssertTrue([self.messageAdapter canPerformEditingAction:NSSelectorFromString(@"save:")]);
|
||||
|
||||
// e.g. any other unsupported action
|
||||
XCTAssertFalse([self.messageAdapter canPerformEditingAction:@selector(paste:)]);
|
||||
}
|
||||
|
||||
- (void)testCanPerformEditingActionWithAnimatedMessage
|
||||
{
|
||||
self.messageAdapter.mediaItem = [[TSAnimatedAdapter alloc] init];
|
||||
|
||||
XCTAssertTrue([self.messageAdapter canPerformEditingAction:@selector(delete:)]);
|
||||
XCTAssertTrue([self.messageAdapter canPerformEditingAction:@selector(copy:)]);
|
||||
XCTAssertTrue([self.messageAdapter canPerformEditingAction:NSSelectorFromString(@"save:")]);
|
||||
|
||||
// e.g. any other unsupported action
|
||||
XCTAssertFalse([self.messageAdapter canPerformEditingAction:@selector(paste:)]);
|
||||
}
|
||||
|
||||
- (void)testCanPerformEditingActionWithVideoMessage
|
||||
{
|
||||
TSAttachmentStream *videoAttachment = [[TSAttachmentStream alloc] initWithIdentifier:@"fake-video-message" encryptionKey:nil contentType:@"video/mp4"];
|
||||
self.messageAdapter.mediaItem = [[TSVideoAttachmentAdapter alloc] initWithAttachment:videoAttachment incoming:NO];
|
||||
|
||||
XCTAssertTrue([self.messageAdapter canPerformEditingAction:@selector(delete:)]);
|
||||
XCTAssertTrue([self.messageAdapter canPerformEditingAction:@selector(copy:)]);
|
||||
XCTAssertTrue([self.messageAdapter canPerformEditingAction:NSSelectorFromString(@"save:")]);
|
||||
|
||||
// e.g. any other unsupported action
|
||||
XCTAssertFalse([self.messageAdapter canPerformEditingAction:@selector(paste:)]);
|
||||
}
|
||||
|
||||
- (void)testCanPerformEditingActionWithAudioMessage
|
||||
{
|
||||
TSAttachmentStream *audioAttachment = [[TSAttachmentStream alloc] initWithIdentifier:@"fake-audio-message" encryptionKey:nil contentType:@"audio/mp3"];
|
||||
self.messageAdapter.mediaItem = [[TSVideoAttachmentAdapter alloc] initWithAttachment:audioAttachment incoming:NO];
|
||||
|
||||
XCTAssertTrue([self.messageAdapter canPerformEditingAction:@selector(delete:)]);
|
||||
XCTAssertTrue([self.messageAdapter canPerformEditingAction:@selector(copy:)]);
|
||||
|
||||
//e.g. Can't save an audio attachment at this time.
|
||||
XCTAssertFalse([self.messageAdapter canPerformEditingAction:NSSelectorFromString(@"save:")]);
|
||||
|
||||
//e.g. any other unsupported action
|
||||
XCTAssertFalse([self.messageAdapter canPerformEditingAction:@selector(paste:)]);
|
||||
}
|
||||
|
||||
// Test Delete
|
||||
|
||||
- (void)testPerformDeleteEditingActionWithNonMediaMessage
|
||||
{
|
||||
XCTAssertNotNil([TSInteraction fetchObjectWithUniqueID:kTestingInteractionId]);
|
||||
[self.messageAdapter performEditingAction:@selector(delete:)];
|
||||
XCTAssertNil([TSInteraction fetchObjectWithUniqueID:kTestingInteractionId]);
|
||||
}
|
||||
|
||||
- (void)testPerformDeleteActionWithPhotoMessage
|
||||
{
|
||||
XCTAssertNotNil([TSInteraction fetchObjectWithUniqueID:kTestingInteractionId]);
|
||||
|
||||
self.messageAdapter.mediaItem = [[TSPhotoAdapter alloc] init];
|
||||
[self.messageAdapter performEditingAction:@selector(delete:)];
|
||||
XCTAssertNil([TSInteraction fetchObjectWithUniqueID:kTestingInteractionId]);
|
||||
// TODO assert files are deleted
|
||||
}
|
||||
|
||||
- (void)testPerformDeleteEditingActionWithAnimatedMessage
|
||||
{
|
||||
XCTAssertNotNil([TSInteraction fetchObjectWithUniqueID:kTestingInteractionId]);
|
||||
|
||||
self.messageAdapter.mediaItem = [[TSAnimatedAdapter alloc] init];
|
||||
[self.messageAdapter performEditingAction:@selector(delete:)];
|
||||
XCTAssertNil([TSInteraction fetchObjectWithUniqueID:kTestingInteractionId]);
|
||||
// TODO assert files are deleted
|
||||
}
|
||||
|
||||
- (void)testPerformDeleteEditingActionWithVideoMessage
|
||||
{
|
||||
XCTAssertNotNil([TSInteraction fetchObjectWithUniqueID:kTestingInteractionId]);
|
||||
|
||||
TSAttachmentStream *videoAttachment = [[TSAttachmentStream alloc] initWithIdentifier:@"fake-video-message" encryptionKey:nil contentType:@"video/mp4"];
|
||||
self.messageAdapter.mediaItem = [[TSVideoAttachmentAdapter alloc] initWithAttachment:videoAttachment incoming:NO];
|
||||
|
||||
[self.messageAdapter performEditingAction:@selector(delete:)];
|
||||
XCTAssertNil([TSInteraction fetchObjectWithUniqueID:kTestingInteractionId]);
|
||||
// TODO assert files are deleted
|
||||
}
|
||||
|
||||
- (void)testPerformDeleteEditingActionWithAudioMessage
|
||||
{
|
||||
XCTAssertNotNil([TSInteraction fetchObjectWithUniqueID:kTestingInteractionId]);
|
||||
|
||||
TSAttachmentStream *audioAttachment = [[TSAttachmentStream alloc] initWithIdentifier:@"fake-audio-message" encryptionKey:nil contentType:@"audio/mp3"];
|
||||
self.messageAdapter.mediaItem = [[TSVideoAttachmentAdapter alloc] initWithAttachment:audioAttachment incoming:NO];
|
||||
|
||||
[self.messageAdapter performEditingAction:@selector(delete:)];
|
||||
XCTAssertNil([TSInteraction fetchObjectWithUniqueID:kTestingInteractionId]);
|
||||
// TODO assert files are deleted
|
||||
}
|
||||
|
||||
// Test Copy
|
||||
|
||||
- (void)testPerformCopyEditingActionWithNonMediaMessage
|
||||
{
|
||||
self.messageAdapter.messageBody = @"My message text";
|
||||
[self.messageAdapter performEditingAction:@selector(copy:)];
|
||||
XCTAssertEqualObjects(@"My message text", UIPasteboard.generalPasteboard.string);
|
||||
}
|
||||
|
||||
- (void)testPerformCopyEditingActionWithPhotoMessage
|
||||
{
|
||||
// reset the paste board for clean slate test
|
||||
UIPasteboard.generalPasteboard.items = @[];
|
||||
XCTAssertNil(UIPasteboard.generalPasteboard.image);
|
||||
|
||||
// Grab some random existing image
|
||||
UIImage *image = [UIImage imageNamed:@"savephoto"];
|
||||
TSPhotoAdapter *photoAdapter = [[TSPhotoAdapter alloc] initWithImage:image];
|
||||
self.messageAdapter.mediaItem = photoAdapter;
|
||||
[self.messageAdapter performEditingAction:@selector(copy:)];
|
||||
|
||||
XCTAssertNotNil(UIPasteboard.generalPasteboard.image);
|
||||
}
|
||||
|
||||
- (void)testPerformCopyEditingActionWithVideoMessage
|
||||
{
|
||||
// reset the paste board for clean slate test
|
||||
UIPasteboard.generalPasteboard.items = @[];
|
||||
TSAttachmentStream *videoAttachment = [[TSAttachmentStream alloc] initWithIdentifier:@"fake-video" data:self.fakeVideoData key:nil contentType:@"video/mp4"];
|
||||
self.messageAdapter.mediaItem = [[TSVideoAttachmentAdapter alloc] initWithAttachment:videoAttachment incoming:YES];
|
||||
|
||||
[self.messageAdapter performEditingAction:@selector(copy:)];
|
||||
|
||||
NSData *copiedData = [UIPasteboard.generalPasteboard dataForPasteboardType:(NSString *)kUTTypeMPEG4];
|
||||
XCTAssertEqualObjects(self.fakeVideoData, copiedData);
|
||||
}
|
||||
|
||||
- (void)testPerformCopyEditingActionWithMp3AudioMessage
|
||||
{
|
||||
UIPasteboard.generalPasteboard.items = @[];
|
||||
XCTAssertNil([UIPasteboard.generalPasteboard dataForPasteboardType:(NSString *)kUTTypeMP3]);
|
||||
|
||||
TSAttachmentStream *audioAttachment = [[TSAttachmentStream alloc] initWithIdentifier:@"fake-audio-message" data:self.fakeAudioData key:nil contentType:@"audio/mp3"];
|
||||
self.messageAdapter.mediaItem = [[TSVideoAttachmentAdapter alloc] initWithAttachment:audioAttachment incoming:NO];
|
||||
|
||||
[self.messageAdapter performEditingAction:@selector(copy:)];
|
||||
XCTAssertEqualObjects(self.fakeAudioData, [UIPasteboard.generalPasteboard dataForPasteboardType:(NSString *)kUTTypeMP3]);
|
||||
}
|
||||
|
||||
- (void)testPerformCopyEditingActionWithM4aAudioMessage
|
||||
{
|
||||
UIPasteboard.generalPasteboard.items = @[];
|
||||
XCTAssertNil([UIPasteboard.generalPasteboard dataForPasteboardType:(NSString *)kUTTypeMPEG4Audio]);
|
||||
|
||||
TSAttachmentStream *audioAttachment = [[TSAttachmentStream alloc] initWithIdentifier:@"fake-audio-message" data:self.fakeAudioData key:nil contentType:@"audio/x-m4a"];
|
||||
self.messageAdapter.mediaItem = [[TSVideoAttachmentAdapter alloc] initWithAttachment:audioAttachment incoming:NO];
|
||||
|
||||
[self.messageAdapter performEditingAction:@selector(copy:)];
|
||||
XCTAssertEqualObjects(self.fakeAudioData, [UIPasteboard.generalPasteboard dataForPasteboardType:(NSString *)kUTTypeMPEG4Audio]);
|
||||
}
|
||||
|
||||
- (void)testPerformCopyEditingActionWithGenericAudioMessage
|
||||
{
|
||||
UIPasteboard.generalPasteboard.items = @[];
|
||||
XCTAssertNil([UIPasteboard.generalPasteboard dataForPasteboardType:(NSString *)kUTTypeAudio]);
|
||||
|
||||
TSAttachmentStream *audioAttachment = [[TSAttachmentStream alloc] initWithIdentifier:@"fake-audio-message" data:self.fakeAudioData key:nil contentType:@"audio/wav"];
|
||||
self.messageAdapter.mediaItem = [[TSVideoAttachmentAdapter alloc] initWithAttachment:audioAttachment incoming:NO];
|
||||
|
||||
[self.messageAdapter performEditingAction:@selector(copy:)];
|
||||
XCTAssertEqualObjects(self.fakeAudioData, [UIPasteboard.generalPasteboard dataForPasteboardType:(NSString *)kUTTypeAudio]);
|
||||
}
|
||||
|
||||
// TODO - We don't currenlty have a good way of testing "copy of an animated message attachment"
|
||||
// We need an attachment with some NSData, which requires getting into the crypto layer,
|
||||
// which is outside of my realm.
|
||||
//
|
||||
// Since you can't currently PASTE images into our version of JSQMessageViewController, I tested this by pasting
|
||||
// into native Messages client, and verifying the result was animated.
|
||||
//
|
||||
//- (void)testPerformCopyActionWithAnimatedMessage
|
||||
//{
|
||||
// // reset the paste board for clean slate test
|
||||
// UIPasteboard.generalPasteboard.items = @[];
|
||||
// XCTAssertNil(UIPasteboard.generalPasteboard.image);
|
||||
//
|
||||
// // "some-animated-gif" doesn't exist yet
|
||||
// NSData *imageData = [[NSData alloc] initWithContentsOfFile:@"some-animated-gif"];
|
||||
// //TODO build attachment with imageData
|
||||
// TSAttachmentStream animatedAttachement = [[TSAttachmentStream alloc] initWithIdentifier:@"test-animated-attachment-id" data:imageDatq key:@"TODO" contentType:@"image/gif"];
|
||||
// TSAnimatedAdapter *animatedAdapter = [[TSAnimatedAdapter alloc] initWithAttachment:animatedAttachment];
|
||||
// animatedAdapter.image = image;
|
||||
// self.messageAdapter.mediaItem = animatedAdapter;
|
||||
// [self.messageAdapter performEditingAction:@selector(copy:)];
|
||||
//
|
||||
// // TODO XCTAssert that image is copied as a GIF (e.g. not convereted to a PNG, etc.)
|
||||
// // We want to be sure that we can copy/paste an animated GIF from
|
||||
// // one thread to the other, and ensure it's still animated.
|
||||
//}
|
||||
|
||||
@end
|
||||
Binary file not shown.
Loading…
Reference in New Issue
Block a user