Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TIMOB-18359] iOS: Fix bug for retrieving birthday in iOS8 and other contact updates #6598

Merged
merged 2 commits into from
Jan 30, 2015
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
40 changes: 37 additions & 3 deletions apidoc/Titanium/Contacts/Person.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,24 @@ properties:
type: String
platforms: [android, iphone, ipad, tizen, blackberry]

- name: alternateBirthday
summary: |
Alternate birthday of the person. Single Dictionary.
description: |
The format of the dictionary accepted by this property is as follows:

* Keys: `calendarIdentifier`, `era`, `year`, `month`, `day` and `isLeapMonth`.
* Values: Use `chinese`, `hebrew` and `islamic-civil` for `calendarIdentifier`.
Use `number` type for `era`, `year`, `month` and `day`. These must be consistent with
corresponding `calendarIdentifier`.
Use `boolean` type for `isLeapMonth`.


type: Dictionary
platforms: [iphone, ipad]
osver: {ios: {min: "8.0"}}
since: 3.6.0

- name: created
summary: Date and time that the person record was created. Single value.
description: Date format is "_yyyy_-_MM_-_dd_**T**_HH_**:**_mm_**:**_ss_**.**_SSS_**+0000**"
Expand Down Expand Up @@ -151,7 +169,7 @@ properties:

- name: instantMessage
summary: |
Instant messenger names of the person. Multi-value.
Instant messenger information of the person. Multi-value.
description: |
The format of the dictionary accepted by this property is as follows:

Expand All @@ -160,15 +178,31 @@ properties:
`string` type value.

The `service` key value may be one of `AIM`, `Facebook`, `GaduGadu`, `GoogleTalk`, `ICQ`,
`MSN`, `QQ`, `Skype`, or `Yahoo`.
`Jabber`, `MSN`, `QQ`, `Skype`, or `Yahoo`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is Jabber supported on Android? Again the keys are home,work etc. As in socialProfile almost any String value will be accepted but there are predefined values in iOS which we should document

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added support for Jabber in Android in next commit. The keys home, work, others were there before this PR, and if changed, will break the parity for android. The predefined values are actually exactly these service key values written in the doc, and are case sensitive.


On BlackBerry, the following apply: `aim`, `aliwangwang`, `bbmPin`, `googleTalk`, `icq`,
`irc`, `jabber`, `msLcs`, `msn`, `qq`, `sametime`, `skype`, `yahooMessenger`, `yahooMessenger`
`irc`, `jabber`, `msLcs`, `msn`, `qq`, `sametime`, `skype`, `yahooMessenger`


type: Dictionary
platforms: [android, iphone, ipad, blackberry]

- name: socialProfile
summary: |
Social profile information of the person. Multi-value.
description: |
The format of the dictionary accepted by this property is as follows:

* Keys: any of `home`, `work` and/or `other`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the keys are incorrect. Shouldn't they be facebookProfile, flickrProfile etc? I think any String value is accepted but there are predefined values in iOS

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As explained in the below note, the current sdk reads the input for instantMessage in this format. And I followed the same format for socialProfile so that the behavior and usage is similar. The predefined values are actually the service key values explained in the docs, and are case sensitive. Will emphasize in the next commit.

* Values: arrays of dictionary types with two keys, `service` and `username` each with a
`string` type value.

The `service` key value may be one of `Twitter`, `SinaWeibo`, `GameCenter`, `Facebook`,
`Myspace`, `LinkedIn`, or `Flickr`.

type: Dictionary
platforms: [iphone, ipad]
since: 3.6.0
- name: jobTitle
summary: Job title of the person. Single value.
type: String
Expand Down
77 changes: 45 additions & 32 deletions iphone/Classes/ContactsModule.m
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* Appcelerator Titanium Mobile
* Copyright (c) 2009-2014 by Appcelerator, Inc. All Rights Reserved.
* Copyright (c) 2009-2015 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Apache Public License
* Please see the LICENSE included with this distribution for details.
*/
Expand Down Expand Up @@ -548,37 +548,50 @@ -(BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)pe
}
}
else {
propertyName = [[[TiContactsPerson multiValueProperties] allKeysForObject:[NSNumber numberWithInt:property]] objectAtIndex:0];
ABMultiValueRef multival = ABRecordCopyValue(person, property);
CFIndex index = ABMultiValueGetIndexForIdentifier(multival, identifier);

CFTypeRef val = ABMultiValueCopyValueAtIndex(multival, index);
if (val != NULL) {
value = [[(id)val retain] autorelease]; // Force toll-free bridging & autorelease
CFRelease(val);
}

CFStringRef CFlabel = ABMultiValueCopyLabelAtIndex(multival, index);
NSArray* labelKeys = [[TiContactsPerson multiValueLabels] allKeysForObject:(NSString*)CFlabel];
if ([labelKeys count] > 0) {
label = [NSString stringWithString:[labelKeys objectAtIndex:0]];
}
else {
// Hack for Exchange and other 'cute' setups where there is no label associated with a multival property;
// in this case, force it to be the property name.
if (CFlabel != NULL) {
label = [NSString stringWithString:(NSString*)CFlabel];
}
// There may also be cases where we get a property from the system that we can't handle, because it's undocumented or not in the map.
else if (propertyName != nil) {
label = [NSString stringWithString:propertyName];
}
}
if (CFlabel != NULL) {
CFRelease(CFlabel);
}

CFRelease(multival);
//birthdays for iOS8 is multivalue and NOT kABPersonBirthdayProperty only in DELEGATE, but undocumented in Apple
if ([TiUtils isIOS8OrGreater] && property == 999) {
if (identifier == 0) {
propertyName = @"birthday";
value = (NSString *) ABRecordCopyValue(person, kABPersonBirthdayProperty);
}
else {
propertyName = @"alternateBirthday";
value = (NSDictionary *) ABRecordCopyValue(person, kABPersonAlternateBirthdayProperty);
}
}
else {
propertyName = [[[TiContactsPerson multiValueProperties] allKeysForObject:[NSNumber numberWithInt:property]] objectAtIndex:0];
ABMultiValueRef multival = ABRecordCopyValue(person, property);
CFIndex index = ABMultiValueGetIndexForIdentifier(multival, identifier);

CFTypeRef val = ABMultiValueCopyValueAtIndex(multival, index);
if (val != NULL) {
value = [[(id)val retain] autorelease]; // Force toll-free bridging & autorelease
CFRelease(val);
}

CFStringRef CFlabel = ABMultiValueCopyLabelAtIndex(multival, index);
NSArray* labelKeys = [[TiContactsPerson multiValueLabels] allKeysForObject:(NSString*)CFlabel];
if ([labelKeys count] > 0) {
label = [NSString stringWithString:[labelKeys objectAtIndex:0]];
}
else {
// Hack for Exchange and other 'cute' setups where there is no label associated with a multival property;
// in this case, force it to be the property name.
if (CFlabel != NULL) {
label = [NSString stringWithString:(NSString*)CFlabel];
}
// There may also be cases where we get a property from the system that we can't handle, because it's undocumented or not in the map.
else if (propertyName != nil) {
label = [NSString stringWithString:propertyName];
}
}
if (CFlabel != NULL) {
CFRelease(CFlabel);
}

CFRelease(multival);
}
}

NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:personObject,@"person",propertyName,@"property",value,@"value",label,@"label",nil];
Expand Down
53 changes: 38 additions & 15 deletions iphone/Classes/TiContactsPerson.m
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* Appcelerator Titanium Mobile
* Copyright (c) 2009-2010 by Appcelerator, Inc. All Rights Reserved.
* Copyright (c) 2009-2015 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Apache Public License
* Please see the LICENSE included with this distribution for details.
*/
Expand Down Expand Up @@ -94,6 +94,7 @@ +(NSDictionary*)multiValueProperties
[[NSDictionary alloc] initWithObjectsAndKeys:NUMINT (kABPersonEmailProperty),@"email",
NUMINT(kABPersonAddressProperty),@"address",
NUMINT(kABPersonPhoneProperty),@"phone",
NUMINT(kABPersonSocialProfileProperty),@"socialProfile",
NUMINT(kABPersonInstantMessageProperty),@"instantMessage",
NUMINT(kABPersonRelatedNamesProperty),@"relatedNames",
NUMINT(kABPersonDateProperty),@"date",
Expand All @@ -110,6 +111,7 @@ +(NSDictionary*)multiValueTypes
[[NSDictionary alloc] initWithObjectsAndKeys:NUMINT(kABMultiStringPropertyType),NUMINT(kABPersonEmailProperty),
NUMINT(kABMultiDictionaryPropertyType),NUMINT(kABPersonAddressProperty),
NUMINT(kABMultiStringPropertyType),NUMINT(kABPersonPhoneProperty),
NUMINT(kABMultiDictionaryPropertyType),NUMINT(kABPersonSocialProfileProperty),
NUMINT(kABMultiDictionaryPropertyType),NUMINT(kABPersonInstantMessageProperty),
NUMINT(kABMultiStringPropertyType),NUMINT(kABPersonRelatedNamesProperty),
NUMINT(kABMultiDateTimePropertyType),NUMINT(kABPersonDateProperty),
Expand All @@ -132,11 +134,23 @@ +(NSDictionary*)multiValueLabels
kABPersonPhoneMainLabel,@"main",
kABPersonPhoneIPhoneLabel,@"iPhone",
kABPersonPhoneHomeFAXLabel,@"homeFax",
kABPersonSocialProfileServiceFacebook,@"facebookProfile",// Social Profile Labels
kABPersonSocialProfileServiceFlickr,@"flickrProfile",
kABPersonSocialProfileServiceGameCenter,@"gameCenterProfile",
kABPersonSocialProfileServiceLinkedIn,@"linkedInProfile",
kABPersonSocialProfileServiceMyspace,@"myspaceProfile",
kABPersonSocialProfileServiceSinaWeibo,@"sinaWeiboProfile",
kABPersonSocialProfileServiceTwitter,@"twitterProfile",
kABPersonInstantMessageServiceAIM,@"aim", // IM labels
kABPersonInstantMessageServiceICQ,@"icq",
kABPersonInstantMessageServiceJabber,@"jabber",
kABPersonInstantMessageServiceMSN,@"msn",
kABPersonInstantMessageServiceYahoo,@"yahoo",
kABPersonInstantMessageServiceQQ,@"qq",
kABPersonInstantMessageServiceSkype,@"skype",
kABPersonInstantMessageServiceGoogleTalk,@"googletalk",
kABPersonInstantMessageServiceGaduGadu,@"gadugadu",
kABPersonInstantMessageServiceFacebook,@"facebook",
kABPersonMotherLabel,@"mother", // Relation labels
kABPersonFatherLabel,@"father",
kABPersonParentLabel,@"parent",
Expand Down Expand Up @@ -331,7 +345,7 @@ -(id)valueForUndefinedKey:(NSString *)key

return result;
}
// Multi-value property
// Multi-value property
else if (property = [[TiContactsPerson multiValueProperties] valueForKey:key]) {
ABPropertyID propertyID = [property intValue];
ABMultiValueRef multiVal = ABRecordCopyValue([self record], propertyID);
Expand Down Expand Up @@ -369,24 +383,33 @@ -(void)setValue:(id)value forUndefinedKey:(NSString*)key
location:CODELOCATION];
}
}
//alternate birthdays have to be done seperately as it uses NSDict for setting ABRecord instead of MultiValueRef
else if ([TiUtils isIOS8OrGreater] && [key isEqualToString:@"alternateBirthday"]) {
ENSURE_TYPE(value, NSDictionary);
CFErrorRef error;
if (!ABRecordSetValue([self record], kABPersonAlternateBirthdayProperty, value, &error)) {
CFStringRef reason = CFErrorCopyDescription(error);
NSString* str = [NSString stringWithString:(NSString*)reason];
CFRelease(reason);
[self throwException:[NSString stringWithFormat:@"Failed to set contact property %@: %@", key, str] subreason:nil location:CODELOCATION];
}
}
// Multi-value property
else if (property = [[TiContactsPerson multiValueProperties] valueForKey:key]) {
ENSURE_TYPE(value, NSDictionary)
ABPropertyID propertyID = [property intValue];
ENSURE_TYPE(value, NSDictionary);
ABPropertyID propertyID = [property intValue];
int type = [[[TiContactsPerson multiValueTypes] objectForKey:property] intValue];

ABMultiValueRef multiVal = [self dictionaryToMultiValue:value type:type];
CFErrorRef error;
if (!ABRecordSetValue([self record], propertyID, multiVal, &error)) {

CFStringRef reason = CFErrorCopyDescription(error);
NSString* str = [NSString stringWithString:(NSString*)reason];
CFRelease(reason);
[self throwException:[NSString stringWithFormat:@"Failed to set contact property %@: %@", key, str]
ABMultiValueRef multiVal = [self dictionaryToMultiValue:value type:type];
CFErrorRef error;
if (!ABRecordSetValue([self record], propertyID, multiVal, &error)) {
CFStringRef reason = CFErrorCopyDescription(error);
NSString* str = [NSString stringWithString:(NSString*)reason];
CFRelease(reason);
[self throwException:[NSString stringWithFormat:@"Failed to set contact property %@: %@", key, str]
subreason:nil
location:CODELOCATION];
}
}
}
}
// Something else
else {
[super setValue:value forUndefinedKey:key];
Expand Down