diff --git a/SignalServiceKit/src/Messages/Interactions/OWSContact.h b/SignalServiceKit/src/Messages/Interactions/OWSContact.h index f725b43a9..db31669f5 100644 --- a/SignalServiceKit/src/Messages/Interactions/OWSContact.h +++ b/SignalServiceKit/src/Messages/Interactions/OWSContact.h @@ -6,6 +6,7 @@ NS_ASSUME_NONNULL_BEGIN +@class CNContact; @class OWSSignalServiceProtosDataMessage; @class TSAttachment; @class YapDatabaseReadWriteTransaction; @@ -108,6 +109,21 @@ typedef NS_ENUM(NSUInteger, OWSContactAddressType) { @interface OWSContacts : NSObject +#pragma mark - VCard Serialization + ++ (nullable CNContact *)systemContactForVCardData:(NSData *)data; ++ (nullable NSData *)vCardDataForSystemContact:(CNContact *)systemContact; + +#pragma mark - System Contact Conversion + ++ (nullable OWSContact *)contactForSystemContact:(CNContact *)systemContact; ++ (nullable CNContact *)systemContactForContact:(OWSContact *)contact; + +#pragma mark - + ++ (nullable OWSContact *)contactForVCardData:(NSData *)data; ++ (nullable NSData *)vCardDataContact:(OWSContact *)contact; + @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Interactions/OWSContact.m b/SignalServiceKit/src/Messages/Interactions/OWSContact.m index 1287abbc2..9663e41ba 100644 --- a/SignalServiceKit/src/Messages/Interactions/OWSContact.m +++ b/SignalServiceKit/src/Messages/Interactions/OWSContact.m @@ -346,6 +346,8 @@ NS_ASSUME_NONNULL_BEGIN @implementation OWSContacts +#pragma mark - VCard Serialization + + (nullable CNContact *)systemContactForVCardData:(NSData *)data { OWSAssert(data); @@ -366,6 +368,28 @@ NS_ASSUME_NONNULL_BEGIN return contacts.firstObject; } ++ (nullable NSData *)vCardDataForSystemContact:(CNContact *)systemContact +{ + OWSAssert(systemContact); + + NSError *error; + NSData *_Nullable data = [CNContactVCardSerialization dataWithContacts:@[ + systemContact, + ] + error:&error]; + if (!data || error) { + OWSProdLogAndFail(@"%@ could not serialize to vcard: %@", self.logTag, error); + return nil; + } + if (data.length < 1) { + OWSProdLogAndFail(@"%@ empty vcard data: %@", self.logTag, error); + return nil; + } + return data; +} + +#pragma mark - System Contact Conversion + + (nullable OWSContact *)contactForSystemContact:(CNContact *)systemContact { if (!systemContact) { @@ -461,8 +485,135 @@ NS_ASSUME_NONNULL_BEGIN } } ++ (nullable CNContact *)systemContactForContact:(OWSContact *)contact +{ + if (!contact) { + OWSProdLogAndFail(@"%@ Missing contact.", self.logTag); + return nil; + } + if (!contact.isValid) { + OWSProdLogAndFail(@"%@ Invalid contact.", self.logTag); + return nil; + } + + CNMutableContact *systemContact = [CNMutableContact new]; + systemContact.givenName = contact.givenName; + systemContact.middleName = contact.middleName; + systemContact.familyName = contact.familyName; + systemContact.namePrefix = contact.namePrefix; + systemContact.nameSuffix = contact.nameSuffix; + // TODO: Display name. + // contact.displayName = [CNContactFormatter stringFromContact:contact + // style:CNContactFormatterStyleFullName]; contact.organizationName = contact.organizationName.ows_stripped; + + NSMutableArray *> *systemPhoneNumbers = [NSMutableArray new]; + for (OWSContactPhoneNumber *phoneNumber in contact.phoneNumbers) { + if (!phoneNumber.isValid) { + OWSProdLogAndFail(@"%@ invalid phone number.", self.logTag); + continue; + } + switch (phoneNumber.phoneType) { + case OWSContactPhoneType_Home: + [systemPhoneNumbers + addObject:[CNLabeledValue + labeledValueWithLabel:CNLabelHome + value:[CNPhoneNumber + phoneNumberWithStringValue:phoneNumber.phoneNumber]]]; + break; + case OWSContactPhoneType_Mobile: + [systemPhoneNumbers + addObject:[CNLabeledValue + labeledValueWithLabel:CNLabelPhoneNumberMobile + value:[CNPhoneNumber + phoneNumberWithStringValue:phoneNumber.phoneNumber]]]; + break; + case OWSContactPhoneType_Work: + [systemPhoneNumbers + addObject:[CNLabeledValue + labeledValueWithLabel:CNLabelWork + value:[CNPhoneNumber + phoneNumberWithStringValue:phoneNumber.phoneNumber]]]; + break; + default: + [systemPhoneNumbers + addObject:[CNLabeledValue + labeledValueWithLabel:phoneNumber.label + value:[CNPhoneNumber + phoneNumberWithStringValue:phoneNumber.phoneNumber]]]; + break; + } + } + systemContact.phoneNumbers = systemPhoneNumbers; + + NSMutableArray *> *systemEmails = [NSMutableArray new]; + for (OWSContactEmail *email in contact.emails) { + if (!email.isValid) { + OWSProdLogAndFail(@"%@ invalid email.", self.logTag); + continue; + } + switch (email.emailType) { + case OWSContactEmailType_Home: + [systemEmails addObject:[CNLabeledValue labeledValueWithLabel:CNLabelHome value:email.email]]; + break; + case OWSContactEmailType_Mobile: + [systemEmails addObject:[CNLabeledValue labeledValueWithLabel:@"Mobile" value:email.email]]; + break; + case OWSContactEmailType_Work: + [systemEmails addObject:[CNLabeledValue labeledValueWithLabel:CNLabelWork value:email.email]]; + break; + default: + [systemEmails addObject:[CNLabeledValue labeledValueWithLabel:email.label value:email.email]]; + break; + } + } + systemContact.emailAddresses = systemEmails; + + NSMutableArray *> *systemAddresses = [NSMutableArray new]; + for (OWSContactAddress *address in contact.addresses) { + if (!address.isValid) { + OWSProdLogAndFail(@"%@ invalid address.", self.logTag); + continue; + } + CNMutablePostalAddress *systemAddress = [CNMutablePostalAddress new]; + systemAddress.street = address.street; + // TODO: Is this the correct mapping? + // systemAddress.subLocality = address.neighborhood; + systemAddress.city = address.city; + // TODO: Is this the correct mapping? + // systemAddress.subAdministrativeArea = address.region; + systemAddress.state = address.region; + systemAddress.postalCode = address.postcode; + // TODO: Should we be using 2-letter codes, 3-letter codes or names? + systemAddress.ISOCountryCode = address.country; + + switch (address.addressType) { + case OWSContactAddressType_Home: + [systemAddresses addObject:[CNLabeledValue labeledValueWithLabel:CNLabelHome value:systemAddress]]; + break; + case OWSContactAddressType_Work: + [systemAddresses addObject:[CNLabeledValue labeledValueWithLabel:CNLabelWork value:systemAddress]]; + break; + default: + [systemAddresses addObject:[CNLabeledValue labeledValueWithLabel:address.label value:systemAddress]]; + break; + } + } + systemContact.postalAddresses = systemAddresses; + + // TODO: Avatar + + // @property (readonly, copy, nullable, NS_NONATOMIC_IOSONLY) NSData *imageData; + // @property (readonly, copy, nullable, NS_NONATOMIC_IOSONLY) NSData *thumbnailImageData; + + return systemContact; +} + +#pragma mark - + + (nullable OWSContact *)contactForVCardData:(NSData *)data { + OWSAssert(data); + CNContact *_Nullable systemContact = [self systemContactForVCardData:data]; if (!systemContact) { return nil; @@ -470,6 +621,17 @@ NS_ASSUME_NONNULL_BEGIN return [self contactForSystemContact:systemContact]; } ++ (nullable NSData *)vCardDataContact:(OWSContact *)contact +{ + OWSAssert(contact); + + CNContact *_Nullable systemContact = [self systemContactForContact:contact]; + if (!systemContact) { + return nil; + } + return [self vCardDataForSystemContact:systemContact]; +} + @end NS_ASSUME_NONNULL_END