Share contacts from share extension.

pull/1/head
Matthew Chen 6 years ago
parent c56362cee0
commit bd116f8938

@ -4958,19 +4958,19 @@ interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransiti
BOOL isProfileAvatar = NO;
UIImage *_Nullable avatarImage = contact.image;
if (!avatarImage) {
NSString *firstSignalId = contact.textSecureIdentifiers.firstObject;
if (firstSignalId) {
avatarImage = [self.contactsManager profileImageForPhoneIdentifier:firstSignalId];
if (avatarImage) {
isProfileAvatar = YES;
}
for (NSString *recipientId in contact.textSecureIdentifiers) {
if (avatarImage) {
break;
}
avatarImage = [self.contactsManager profileImageForPhoneIdentifier:recipientId];
if (avatarImage) {
isProfileAvatar = YES;
}
}
contactShareRecord.isProfileAvatar = isProfileAvatar;
ContactShareViewModel *contactShare =
[[ContactShareViewModel alloc] initWithContactShareRecord:contactShareRecord avatarImage:avatarImage];
contactShareRecord.isProfileAvatar = isProfileAvatar;
// TODO: We should probably show this in the same navigation view controller.
ApproveContactShareViewController *approveContactShare =

@ -97,8 +97,16 @@ class ContactShareAddress: ContactShareFieldBase<OWSContactAddress> {
// MARK: -
protocol ContactShareFieldViewDelegate: class {
func contactShareFieldViewDidChangeSelectedState()
}
// MARK: -
class ContactShareFieldView: UIStackView {
weak var delegate: ContactShareFieldViewDelegate?
let field: ContactShareField
let previewViewBlock : (() -> UIView)
@ -112,9 +120,10 @@ class ContactShareFieldView: UIStackView {
fatalError("Unimplemented")
}
required init(field: ContactShareField, previewViewBlock : @escaping (() -> UIView)) {
required init(field: ContactShareField, previewViewBlock : @escaping (() -> UIView), delegate: ContactShareFieldViewDelegate) {
self.field = field
self.previewViewBlock = previewViewBlock
self.delegate = delegate
super.init(frame: CGRect.zero)
@ -161,6 +170,10 @@ class ContactShareFieldView: UIStackView {
}
field.setIsIncluded(!field.isIncluded())
checkbox.isSelected = field.isIncluded()
if let delegate = delegate {
delegate.contactShareFieldViewDidChangeSelectedState()
}
}
}
@ -168,7 +181,8 @@ class ContactShareFieldView: UIStackView {
// TODO: Rename to ContactShareApprovalViewController
@objc
public class ApproveContactShareViewController: OWSViewController, EditContactShareNameViewControllerDelegate {
public class ApproveContactShareViewController: OWSViewController, EditContactShareNameViewControllerDelegate, ContactShareFieldViewDelegate {
weak var delegate: ApproveContactShareViewControllerDelegate?
let contactsManager: OWSContactsManager
@ -208,21 +222,26 @@ public class ApproveContactShareViewController: OWSViewController, EditContactSh
let field = ContactSharePhoneNumber(phoneNumber)
let fieldView = ContactShareFieldView(field: field, previewViewBlock: {
return ContactFieldView.contactFieldView(forPhoneNumber: phoneNumber, layoutMargins: previewInsets, actionBlock: nil)
})
},
delegate: self)
fieldViews.append(fieldView)
}
for email in contactShare.emails {
let field = ContactShareEmail(email)
let fieldView = ContactShareFieldView(field: field, previewViewBlock: {
return ContactFieldView.contactFieldView(forEmail: email, layoutMargins: previewInsets, actionBlock: nil)
})
},
delegate: self)
fieldViews.append(fieldView)
}
for address in contactShare.addresses {
let field = ContactShareAddress(address)
let fieldView = ContactShareFieldView(field: field, previewViewBlock: {
return ContactFieldView.contactFieldView(forAddress: address, layoutMargins: previewInsets, actionBlock: nil)
})
},
delegate: self)
fieldViews.append(fieldView)
}
@ -264,7 +283,16 @@ public class ApproveContactShareViewController: OWSViewController, EditContactSh
// TODO: Surface error with resolution to user if not.
func canShareContact() -> Bool {
return contactShare.ows_isValid
return contactShare.ows_isValid && isAtLeastOneFieldSelected()
}
func isAtLeastOneFieldSelected() -> Bool {
for fieldView in fieldViews {
if fieldView.field.isIncluded() {
return true
}
}
return false
}
func updateNavigationBar() {
@ -384,6 +412,9 @@ public class ApproveContactShareViewController: OWSViewController, EditContactSh
// MARK: -
func didPressSendButton() {
SwiftAssertIsOnMainThread(#function)
assert(canShareContact())
Logger.info("\(logTag) \(#function)")
guard let delegate = self.delegate else {
@ -424,4 +455,10 @@ public class ApproveContactShareViewController: OWSViewController, EditContactSh
self.updateNavigationBar()
}
// MARK: - ContactShareFieldViewDelegate
public func contactShareFieldViewDidChangeSelectedState() {
self.updateNavigationBar()
}
}

@ -23,7 +23,8 @@ typedef void (^SendMessageBlock)(SendCompletionBlock completion);
@interface SharingThreadPickerViewController () <SelectThreadViewControllerDelegate,
AttachmentApprovalViewControllerDelegate,
MessageApprovalViewControllerDelegate>
MessageApprovalViewControllerDelegate,
ApproveContactShareViewControllerDelegate>
@property (nonatomic, readonly) OWSContactsManager *contactsManager;
@property (nonatomic, readonly) OWSMessageSender *messageSender;
@ -123,8 +124,6 @@ typedef void (^SendMessageBlock)(SendCompletionBlock completion);
#pragma mark - SelectThreadViewControllerDelegate
// If the attachment is textual (e.g. text or URL), returns the message text
// for the attachment. Returns nil otherwise.
- (nullable NSString *)convertAttachmentToMessageTextIfPossible
{
if (!self.attachment.isConvertibleToTextMessage) {
@ -144,8 +143,36 @@ typedef void (^SendMessageBlock)(SendCompletionBlock completion);
{
OWSAssert(self.attachment);
OWSAssert(thread);
self.thread = thread;
if (self.attachment.isConvertibleToContactShare) {
NSData *data = self.attachment.data;
OWSContact *_Nullable contact = [OWSContacts contactForVCardData:data];
if (!contact) {
// This should never happen since we verify that the contact can be parsed when building the attachment.
OWSFail(@"%@ could not parse contact share.", self.logTag);
[self.shareViewDelegate shareViewWasCancelled];
return;
}
// TODO: Populate avatar image.
BOOL isProfileAvatar = NO;
UIImage *_Nullable avatarImage = nil;
contact.isProfileAvatar = isProfileAvatar;
ContactShareViewModel *contactShare =
[[ContactShareViewModel alloc] initWithContactShareRecord:contact avatarImage:avatarImage];
ApproveContactShareViewController *approvalVC =
[[ApproveContactShareViewController alloc] initWithContactShare:contactShare
contactsManager:self.contactsManager
delegate:self];
[self.navigationController pushViewController:approvalVC animated:YES];
return;
}
NSString *_Nullable messageText = [self convertAttachmentToMessageTextIfPossible];
// Hide the navigation bar before presenting the approval view.
@ -252,6 +279,38 @@ typedef void (^SendMessageBlock)(SendCompletionBlock completion);
[self cancelShareExperience];
}
#pragma mark - ApproveContactShareViewControllerDelegate
- (void)approveContactShare:(ApproveContactShareViewController *)approvalViewController
didApproveContactShare:(OWSContact *)contactShare
{
DDLogInfo(@"%@ in %s", self.logTag, __PRETTY_FUNCTION__);
[ThreadUtil addThreadToProfileWhitelistIfEmptyContactThread:self.thread];
[self tryToSendMessageWithBlock:^(SendCompletionBlock sendCompletion) {
OWSAssertIsOnMainThread();
__block TSOutgoingMessage *outgoingMessage = nil;
outgoingMessage = [ThreadUtil sendMessageWithContactShare:contactShare
inThread:self.thread
messageSender:self.messageSender
completion:^(NSError *_Nullable error) {
sendCompletion(error, outgoingMessage);
}];
// This is necessary to show progress.
self.outgoingMessage = outgoingMessage;
}
fromViewController:approvalViewController];
}
- (void)approveContactShare:(ApproveContactShareViewController *)approvalViewController
didCancelContactShare:(OWSContact *)contactShare
{
DDLogInfo(@"%@ in %s", self.logTag, __PRETTY_FUNCTION__);
[self cancelShareExperience];
}
#pragma mark - Helpers
- (void)tryToSendMessageWithBlock:(SendMessageBlock)sendMessageBlock

@ -142,6 +142,10 @@ public class SignalAttachment: NSObject {
@objc
public var isConvertibleToTextMessage = false
// This flag should be set for attachments that can be sent as contact shares.
@objc
public var isConvertibleToContactShare = false
// Attachment types are identified using UTIs.
//
// See: https://developer.apple.com/library/content/documentation/Miscellaneous/Reference/UTIRef/Articles/System-DeclaredUniformTypeIdentifiers.html
@ -826,14 +830,14 @@ public class SignalAttachment: NSObject {
let removeMetadataProperties: [String: AnyObject] =
[
kCGImagePropertyExifDictionary as String : kCFNull,
kCGImagePropertyExifAuxDictionary as String : kCFNull,
kCGImagePropertyGPSDictionary as String : kCFNull,
kCGImagePropertyTIFFDictionary as String : kCFNull,
kCGImagePropertyJFIFDictionary as String : kCFNull,
kCGImagePropertyPNGDictionary as String : kCFNull,
kCGImagePropertyIPTCDictionary as String : kCFNull,
kCGImagePropertyMakerAppleDictionary as String : kCFNull
kCGImagePropertyExifDictionary as String: kCFNull,
kCGImagePropertyExifAuxDictionary as String: kCFNull,
kCGImagePropertyGPSDictionary as String: kCFNull,
kCGImagePropertyTIFFDictionary as String: kCFNull,
kCGImagePropertyJFIFDictionary as String: kCFNull,
kCGImagePropertyPNGDictionary as String: kCFNull,
kCGImagePropertyIPTCDictionary as String: kCFNull,
kCGImagePropertyMakerAppleDictionary as String: kCFNull
]
for index in 0...count-1 {

@ -17,6 +17,8 @@
NS_ASSUME_NONNULL_BEGIN
// NOTE: When changing the value of this feature flag, you also need
// to update the filtering in the SAE's info.plist.
BOOL kIsSendingContactSharesEnabled = YES;
NSString *NSStringForContactPhoneType(OWSContactPhoneType value)

@ -60,11 +60,8 @@
SUBQUERY (
$extensionItem.attachments,
$attachment,
(
ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.data"
|| ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.url"
)
AND NOT (ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.vcard")
).@count &gt;= 1
).@count == 1
</string>

@ -694,6 +694,7 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
var customFileName: String?
var isConvertibleToTextMessage = false
var isConvertibleToContactShare = false
let loadCompletion: NSItemProvider.CompletionHandler = { [weak self]
(value, error) in
@ -719,6 +720,15 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
if ShareViewController.itemMatchesSpecificUtiType(itemProvider: itemProvider,
utiType: kUTTypeVCard as String) {
customFileName = "Contact.vcf"
if let contactShare = OWSContacts.contact(forVCardData: data) {
isConvertibleToContactShare = true
} else {
Logger.error("\(strongSelf.logTag) could not parse vcard.")
let writeError = ShareViewControllerError.assertionError(description: "Could not parse vcard data.")
reject(writeError)
return
}
}
let customFileExtension = MIMETypeUtil.fileExtension(forUTIType: srcUtiType)
@ -844,7 +854,10 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
}
let attachment = SignalAttachment.attachment(dataSource: dataSource, dataUTI: specificUTIType, imageQuality: .medium)
if isConvertibleToTextMessage {
if isConvertibleToContactShare {
Logger.info("\(strongSelf.logTag) isConvertibleToContactShare")
attachment.isConvertibleToContactShare = isConvertibleToContactShare
} else if isConvertibleToTextMessage {
Logger.info("\(strongSelf.logTag) isConvertibleToTextMessage")
attachment.isConvertibleToTextMessage = isConvertibleToTextMessage
}

Loading…
Cancel
Save