Add sharing of attachments.

// FREEBIE
pull/1/head
Matthew Chen 9 years ago
parent 92a56b3a85
commit 5bd44673ea

@ -7,6 +7,7 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
340757C21E5602D6001F15DD /* AttachmentSharing.m in Sources */ = {isa = PBXBuildFile; fileRef = 340757C11E5602D6001F15DD /* AttachmentSharing.m */; };
341BB7491DB727EE001E2975 /* JSQMediaItem+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = 341BB7481DB727EE001E2975 /* JSQMediaItem+OWS.m */; }; 341BB7491DB727EE001E2975 /* JSQMediaItem+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = 341BB7481DB727EE001E2975 /* JSQMediaItem+OWS.m */; };
34535D821E256BE9008A4747 /* UIView+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = 34535D811E256BE9008A4747 /* UIView+OWS.m */; }; 34535D821E256BE9008A4747 /* UIView+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = 34535D811E256BE9008A4747 /* UIView+OWS.m */; };
348F3A4F1E4A533900750D44 /* CallInterstitialViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 348F3A4E1E4A533900750D44 /* CallInterstitialViewController.swift */; }; 348F3A4F1E4A533900750D44 /* CallInterstitialViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 348F3A4E1E4A533900750D44 /* CallInterstitialViewController.swift */; };
@ -599,6 +600,8 @@
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
1B5E7D6C9007F5E5761D79DD /* libPods-SignalTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SignalTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 1B5E7D6C9007F5E5761D79DD /* libPods-SignalTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SignalTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
340757C01E5602D6001F15DD /* AttachmentSharing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AttachmentSharing.h; sourceTree = "<group>"; };
340757C11E5602D6001F15DD /* AttachmentSharing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AttachmentSharing.m; sourceTree = "<group>"; };
341BB7471DB727EE001E2975 /* JSQMediaItem+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "JSQMediaItem+OWS.h"; sourceTree = "<group>"; }; 341BB7471DB727EE001E2975 /* JSQMediaItem+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "JSQMediaItem+OWS.h"; sourceTree = "<group>"; };
341BB7481DB727EE001E2975 /* JSQMediaItem+OWS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "JSQMediaItem+OWS.m"; sourceTree = "<group>"; }; 341BB7481DB727EE001E2975 /* JSQMediaItem+OWS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "JSQMediaItem+OWS.m"; sourceTree = "<group>"; };
34535D801E256BE9008A4747 /* UIView+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+OWS.h"; sourceTree = "<group>"; }; 34535D801E256BE9008A4747 /* UIView+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+OWS.h"; sourceTree = "<group>"; };
@ -2013,18 +2016,20 @@
76EB04FE18170B33006006FC /* View Controllers */ = { 76EB04FE18170B33006006FC /* View Controllers */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
FC3196311A08141D0094C78E /* Settings */, 340757C01E5602D6001F15DD /* AttachmentSharing.h */,
FC3196321A08142D0094C78E /* Signals */, 340757C11E5602D6001F15DD /* AttachmentSharing.m */,
FCFD25791A1543D500F4C644 /* Signup */, 451764261DE939F300EDB8B9 /* ContactsPicker.swift */,
B6BADBE51B88D1AC0086A80D /* LockInteractionController.h */, E94066141DFC5B7B00B15392 /* ContactsPicker.xib */,
B6BADBE61B88D1AC0086A80D /* LockInteractionController.m */,
76EB050B18170B33006006FC /* InCallViewController.h */, 76EB050B18170B33006006FC /* InCallViewController.h */,
76EB050C18170B33006006FC /* InCallViewController.m */, 76EB050C18170B33006006FC /* InCallViewController.m */,
45514DE11DDFA183003EFF90 /* InviteFlow.swift */,
B6BADBE51B88D1AC0086A80D /* LockInteractionController.h */,
B6BADBE61B88D1AC0086A80D /* LockInteractionController.m */,
458E382F1D6682450094BD24 /* OWSQRCodeScanningViewController.h */, 458E382F1D6682450094BD24 /* OWSQRCodeScanningViewController.h */,
458E38301D6682450094BD24 /* OWSQRCodeScanningViewController.m */, 458E38301D6682450094BD24 /* OWSQRCodeScanningViewController.m */,
451764261DE939F300EDB8B9 /* ContactsPicker.swift */, FC3196311A08141D0094C78E /* Settings */,
45514DE11DDFA183003EFF90 /* InviteFlow.swift */, FC3196321A08142D0094C78E /* Signals */,
E94066141DFC5B7B00B15392 /* ContactsPicker.xib */, FCFD25791A1543D500F4C644 /* Signup */,
); );
name = "View Controllers"; name = "View Controllers";
path = "view controllers"; path = "view controllers";
@ -3060,6 +3065,7 @@
45C9DEB81DF4E35A0065CA84 /* WebRTCCallMessageHandler.swift in Sources */, 45C9DEB81DF4E35A0065CA84 /* WebRTCCallMessageHandler.swift in Sources */,
76EB061218170B33006006FC /* LoggingUtil.m in Sources */, 76EB061218170B33006006FC /* LoggingUtil.m in Sources */,
76EB060E18170B33006006FC /* DecayingSampleEstimator.m in Sources */, 76EB060E18170B33006006FC /* DecayingSampleEstimator.m in Sources */,
340757C21E5602D6001F15DD /* AttachmentSharing.m in Sources */,
76EB05BA18170B33006006FC /* CommitPacket.m in Sources */, 76EB05BA18170B33006006FC /* CommitPacket.m in Sources */,
76EB060218170B33006006FC /* InitiatorSessionDescriptor.m in Sources */, 76EB060218170B33006006FC /* InitiatorSessionDescriptor.m in Sources */,
76EB05FC18170B33006006FC /* CallConnectUtil_Server.m in Sources */, 76EB05FC18170B33006006FC /* CallConnectUtil_Server.m in Sources */,

@ -1,11 +1,8 @@
// TSMessageAdapter.m
// //
// Signal // Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
// Created by Frederic Jacobs on 24/11/14.
// Copyright (c) 2014 Open Whisper Systems. All rights reserved.
// //
#import "AttachmentSharing.h"
#import "OWSCall.h" #import "OWSCall.h"
#import "TSAttachmentPointer.h" #import "TSAttachmentPointer.h"
#import "TSAttachmentStream.h" #import "TSAttachmentStream.h"
@ -222,14 +219,15 @@
- (BOOL)canPerformEditingAction:(SEL)action - (BOOL)canPerformEditingAction:(SEL)action
{ {
// Deletes are always handled by TSMessageAdapter // Deletes are always handled by TSMessageAdapter
if (action == @selector(delete:)) { if (action == @selector(delete:)) {
return YES; return YES;
} }
// Delegate other actions for media items // Delegate other actions for media items
if (self.isMediaMessage) { if ([self attachmentStream] && action == NSSelectorFromString(@"share:")) {
return YES;
} else if (self.isMediaMessage) {
return [self.mediaItem canPerformEditingAction:action]; return [self.mediaItem canPerformEditingAction:action];
} else if (self.messageType == TSInfoMessageAdapter || self.messageType == TSErrorMessageAdapter) { } else if (self.messageType == TSInfoMessageAdapter || self.messageType == TSErrorMessageAdapter) {
return NO; return NO;
@ -249,8 +247,16 @@
DDLogDebug(@"Deleting interaction with uniqueId: %@", self.interaction.uniqueId); DDLogDebug(@"Deleting interaction with uniqueId: %@", self.interaction.uniqueId);
[self.interaction remove]; [self.interaction remove];
return; return;
} else if (action == NSSelectorFromString(@"share:")) {
TSAttachmentStream *stream = [self attachmentStream];
OWSAssert(stream);
if (stream) {
[AttachmentSharing showShareUIForAttachment:stream];
}
return;
} }
// Delegate other actions for media items // Delegate other actions for media items
if (self.isMediaMessage) { if (self.isMediaMessage) {
[self.mediaItem performEditingAction:action]; [self.mediaItem performEditingAction:action];
@ -271,6 +277,29 @@
[self.mediaItem class]); [self.mediaItem class]);
} }
- (TSAttachmentStream *)attachmentStream
{
if (![self.interaction isKindOfClass:[TSMessage class]]) {
return nil;
}
TSMessage *message = (TSMessage *)self.interaction;
if (![message hasAttachments]) {
return nil;
}
OWSAssert(message.attachmentIds.count <= 1);
NSString *attachmentID = message.attachmentIds[0];
TSAttachment *attachment = [TSAttachment fetchObjectWithUniqueID:attachmentID];
if (![attachment isKindOfClass:[TSAttachmentStream class]]) {
return nil;
}
TSAttachmentStream *stream = (TSAttachmentStream *)attachment;
return stream;
}
- (BOOL)isMediaMessage { - (BOOL)isMediaMessage {
return _mediaItem ? YES : NO; return _mediaItem ? YES : NO;
} }

@ -1,5 +1,6 @@
// Created by Frederic Jacobs on 17/12/14. //
// Copyright (c) 2014 Open Whisper Systems. All rights reserved. // Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "TSPhotoAdapter.h" #import "TSPhotoAdapter.h"
#import "TSAttachmentStream.h" #import "TSAttachmentStream.h"

@ -0,0 +1,13 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import <Foundation/Foundation.h>
@class TSAttachmentStream;
@interface AttachmentSharing : NSObject
+ (void)showShareUIForAttachment:(TSAttachmentStream *)stream;
@end

@ -0,0 +1,76 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "AttachmentSharing.h"
#import "TSAttachmentStream.h"
@implementation AttachmentSharing
+ (void)showShareUIForAttachment:(TSAttachmentStream *)stream {
OWSAssert(stream);
NSString *filePath = stream.filePath;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSError *error;
NSData *data = [NSData dataWithContentsOfFile:filePath options:0 error:&error];
if (!data || error) {
DDLogError(@"%@ %s could not read data from attachment: %@.",
self.tag,
__PRETTY_FUNCTION__,
error);
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[AttachmentSharing showShareUIForData:data];
});
});
}
+ (void)showShareUIForData:(NSData *)data {
AssertIsOnMainThread();
OWSAssert(data);
UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:@[data, ]
applicationActivities:@[
]];
[activityViewController setCompletionWithItemsHandler:^(UIActivityType __nullable activityType,
BOOL completed,
NSArray * __nullable returnedItems,
NSError * __nullable activityError) {
if (activityError) {
DDLogInfo(@"%@ Failed to share with activityError: %@", self.tag, activityError);
} else if (completed) {
DDLogInfo(@"%@ Did share with activityType: %@", self.tag, activityType);
}
}];
// Find the frontmost presented UIViewController from which to present the
// share view.
UIWindow *window = [UIApplication sharedApplication].keyWindow;
UIViewController *fromViewController = window.rootViewController;
while (fromViewController.presentedViewController) {
fromViewController = fromViewController.presentedViewController;
}
OWSAssert(fromViewController);
[fromViewController presentViewController:activityViewController
animated:YES
completion:nil];
}
#pragma mark - Logging
+ (NSString *)tag
{
return [NSString stringWithFormat:@"[%@]", self.class];
}
- (NSString *)tag
{
return self.class.tag;
}
@end

@ -216,9 +216,8 @@
action:@selector(copyAttachment:)], action:@selector(copyAttachment:)],
[[UIMenuItem alloc] initWithTitle:NSLocalizedString(@"ATTACHMENT_VIEW_SAVE_ACTION", @"Short name for edit menu item to save contents of media message.") [[UIMenuItem alloc] initWithTitle:NSLocalizedString(@"ATTACHMENT_VIEW_SAVE_ACTION", @"Short name for edit menu item to save contents of media message.")
action:@selector(saveAttachment:)], action:@selector(saveAttachment:)],
// TODO: We should implement sharing. [[UIMenuItem alloc] initWithTitle:NSLocalizedString(@"ATTACHMENT_VIEW_SHARE_ACTION", @"Short name for edit menu item to share contents of media message.")
// [[UIMenuItem alloc] initWithTitle:NSLocalizedString(@"ATTACHMENT_VIEW_SHARE_ACTION", @"Short name for edit menu item to share contents of media message.") action:@selector(shareAttachment:)],
// action:@selector(shareAttachment:)],
]; ];
[UIMenuController sharedMenuController].menuItems = menuItems; [UIMenuController sharedMenuController].menuItems = menuItems;
CGPoint location = [sender locationInView:self.view]; CGPoint location = [sender locationInView:self.view];
@ -241,9 +240,8 @@
OWSAssert([[self.messageItem media] isKindOfClass:[TSPhotoAdapter class]] || OWSAssert([[self.messageItem media] isKindOfClass:[TSPhotoAdapter class]] ||
[[self.messageItem media] isKindOfClass:[TSAnimatedAdapter class]]); [[self.messageItem media] isKindOfClass:[TSAnimatedAdapter class]]);
id<OWSMessageEditing> messageEditing = (id<OWSMessageEditing>) self.messageItem.media; OWSAssert([self.messageItem canPerformEditingAction:selector]);
OWSAssert([messageEditing canPerformEditingAction:selector]); [self.messageItem performEditingAction:selector];
[messageEditing performEditingAction:selector];
} }
- (void)copyAttachment:(id)sender { - (void)copyAttachment:(id)sender {
@ -255,10 +253,7 @@
} }
- (void)shareAttachment:(id)sender { - (void)shareAttachment:(id)sender {
// TODO: We should implement sharing with UIActivityViewController. [self performEditingActionWithSelector:NSSelectorFromString(@"share:")];
//
// It seems that loading of the contents of the attachment is done
// with TSAttachment and TSAttachmentStream.
} }
#pragma mark - Presentation #pragma mark - Presentation

@ -252,6 +252,8 @@ typedef enum : NSUInteger {
[JSQMessagesCollectionViewCell registerMenuAction:@selector(delete:)]; [JSQMessagesCollectionViewCell registerMenuAction:@selector(delete:)];
SEL saveSelector = NSSelectorFromString(@"save:"); SEL saveSelector = NSSelectorFromString(@"save:");
[JSQMessagesCollectionViewCell registerMenuAction:saveSelector]; [JSQMessagesCollectionViewCell registerMenuAction:saveSelector];
SEL shareSelector = NSSelectorFromString(@"share:");
[JSQMessagesCollectionViewCell registerMenuAction:shareSelector];
[self initializeCollectionViewLayout]; [self initializeCollectionViewLayout];
[self registerCustomMessageNibs]; [self registerCustomMessageNibs];
@ -393,9 +395,14 @@ typedef enum : NSUInteger {
// Other views might change these custom menu items, so we // Other views might change these custom menu items, so we
// need to set them every time we enter this view. // need to set them every time we enter this view.
SEL saveSelector = NSSelectorFromString(@"save:"); SEL saveSelector = NSSelectorFromString(@"save:");
SEL shareSelector = NSSelectorFromString(@"share:");
[UIMenuController sharedMenuController].menuItems = @[ [[UIMenuItem alloc] initWithTitle:NSLocalizedString(@"EDIT_ITEM_SAVE_ACTION", [UIMenuController sharedMenuController].menuItems = @[ [[UIMenuItem alloc] initWithTitle:NSLocalizedString(@"EDIT_ITEM_SAVE_ACTION",
@"Short name for edit menu item to save contents of media message.") @"Short name for edit menu item to save contents of media message.")
action:saveSelector]]; action:saveSelector],
[[UIMenuItem alloc] initWithTitle:NSLocalizedString(@"EDIT_ITEM_SHARE_ACTION",
@"Short name for edit menu item to share contents of media message.")
action:shareSelector],
];
} }
- (void)startReadTimer { - (void)startReadTimer {

@ -169,6 +169,9 @@
/* Short name for edit menu item to save contents of media message. */ /* Short name for edit menu item to save contents of media message. */
"EDIT_ITEM_SAVE_ACTION" = "Save"; "EDIT_ITEM_SAVE_ACTION" = "Save";
/* Short name for edit menu item to share contents of media message. */
"EDIT_ITEM_SHARE_ACTION" = "Share";
/* body of email sent to contacts when inviting to install Signal. Embeds {{link to install Signal}} and {{link to WhisperSystems home page}} */ /* body of email sent to contacts when inviting to install Signal. Embeds {{link to install Signal}} and {{link to WhisperSystems home page}} */
"EMAIL_INVITE_BODY" = "Hey,\n\nLately I've been using Signal to keep the conversations on my iPhone private. I'd like you to install it too, so we can be confident that only you and I can read our messages or hear our calls.\n\nSignal is available for iPhones and Android. Get it here: %@\n\nSignal works like your existing messaging app. We can send pictures and video, make calls, and start group chats. The best part is, no one else can see any of it, not even the people who make Signal!\n\nYou can read more about Open Whisper Systems, the people who make Signal, here: %@"; "EMAIL_INVITE_BODY" = "Hey,\n\nLately I've been using Signal to keep the conversations on my iPhone private. I'd like you to install it too, so we can be confident that only you and I can read our messages or hear our calls.\n\nSignal is available for iPhones and Android. Get it here: %@\n\nSignal works like your existing messaging app. We can send pictures and video, make calls, and start group chats. The best part is, no one else can see any of it, not even the people who make Signal!\n\nYou can read more about Open Whisper Systems, the people who make Signal, here: %@";

Loading…
Cancel
Save