From e02c83f4117d5cbbe36e7e51290f5e0403791bd1 Mon Sep 17 00:00:00 2001 From: Bartosz Dotryw Date: Sat, 22 Dec 2018 02:32:57 +0100 Subject: [PATCH] [expo] Contacts refactor and update to match SDK 31 --- types/expo/expo-tests.tsx | 172 +++++++++++- types/expo/index.d.ts | 577 +++++++++++++++++++++++++++++--------- 2 files changed, 619 insertions(+), 130 deletions(-) diff --git a/types/expo/expo-tests.tsx b/types/expo/expo-tests.tsx index 8d0b1568be..57e028e8bf 100644 --- a/types/expo/expo-tests.tsx +++ b/types/expo/expo-tests.tsx @@ -19,6 +19,7 @@ import { Camera, CameraObject, Constants, + Contacts, DocumentPicker, EdgeInsets, EdgePadding, @@ -934,7 +935,7 @@ const userLocationCallback = (event: EventUserLocation) => console.log(event); style={{ flex: 1 }} /> ); -// #endegion +// #endregion async () => { const updateEventListener: Updates.UpdateEventListener = ({ type, manifest, message }) => { @@ -1067,3 +1068,172 @@ async () => { const userAgent: string = await Constants.getWebViewUserAgentAsync(); }; // #endregion + +// #region Contacts +Contacts.Fields.ID === 'id'; +Contacts.Fields.Name === 'name'; +Contacts.Fields.FirstName === 'firstName'; +Contacts.Fields.MiddleName === 'middleName'; +Contacts.Fields.LastName === 'lastName'; +Contacts.Fields.NamePrefix === 'namePrefix'; +Contacts.Fields.NameSuffix === 'nameSuffix'; +Contacts.Fields.PhoneticFirstName === 'phoneticFirstName'; +Contacts.Fields.PhoneticMiddleName === 'phoneticMiddleName'; +Contacts.Fields.PhoneticLastName === 'phoneticLastName'; +Contacts.Fields.Birthday === 'birthday'; +Contacts.Fields.Emails === 'emails'; +Contacts.Fields.PhoneNumbers === 'phoneNumbers'; +Contacts.Fields.Addresses === 'addresses'; +Contacts.Fields.InstantMessageAddresses === 'instantMessageAddresses'; +Contacts.Fields.UrlAddresses === 'urlAddresses'; +Contacts.Fields.Company === 'company'; +Contacts.Fields.JobTitle === 'jobTitle'; +Contacts.Fields.Department === 'department'; +Contacts.Fields.ImageAvailable === 'imageAvailable'; +Contacts.Fields.Image === 'image'; +Contacts.Fields.Note === 'note'; +Contacts.Fields.Dates === 'dates'; +Contacts.Fields.Relationships === 'relationships'; +Contacts.Fields.Nickname === 'nickname'; +Contacts.Fields.RawImage === 'rawImage'; +Contacts.Fields.MaidenName === 'maidenName'; +Contacts.Fields.ContactType === 'contactType'; +Contacts.Fields.SocialProfiles === 'socialProfiles'; +Contacts.Fields.NonGregorianBirthday === 'nonGregorianBirthday'; + +const contact: Contacts.Contact = { + [Contacts.Fields.ID]: 'id', + [Contacts.Fields.Name]: 'name', + [Contacts.Fields.FirstName]: 'firstName', + [Contacts.Fields.MiddleName]: 'middleName', + [Contacts.Fields.LastName]: 'lastName', + [Contacts.Fields.NamePrefix]: 'namePrefix', + [Contacts.Fields.NameSuffix]: 'nameSuffix', + [Contacts.Fields.PhoneticFirstName]: 'phoneticFirstName', + [Contacts.Fields.PhoneticMiddleName]: 'phoneticMiddleName', + [Contacts.Fields.PhoneticLastName]: 'phoneticLastName', + [Contacts.Fields.Birthday]: { + day: 1, + month: 1, + year: 2010, + format: Contacts.CalendarFormats.Gregorian, + id: 'id', + label: 'label' + }, + [Contacts.Fields.Emails]: [{ + email: 'email', + isPrimary: true, + id: 'id', + label: 'label' + }], + [Contacts.Fields.PhoneNumbers]: [{ + number: 'number', + isPrimary: true, + digits: 'digits', + countryCode: 'countryCode', + id: 'id', + label: 'label' + }], + [Contacts.Fields.Addresses]: [{ + street: 'street', + city: 'city', + country: 'country', + region: 'region', + neighborhood: 'neighborhood', + postalCode: 'postalCode', + poBox: 'poBox', + isoCountryCode: 'isoCountryCode', + id: 'id', + label: 'label' + }], + [Contacts.Fields.InstantMessageAddresses]: [{ + service: 'service', + username: 'username', + localizedProfile: 'localizedProfile', + id: 'id', + label: 'label' + }], + [Contacts.Fields.UrlAddresses]: [{ + url: 'url', + id: 'id', + label: 'label' + }], + [Contacts.Fields.Company]: 'company', + [Contacts.Fields.JobTitle]: 'jobTitle', + [Contacts.Fields.Department]: 'department', + [Contacts.Fields.ImageAvailable]: true, + [Contacts.Fields.Image]: { + uri: 'uri' + }, + [Contacts.Fields.Note]: 'note', + [Contacts.Fields.Dates]: [{ + day: 1, + month: 1, + year: 2010, + format: Contacts.CalendarFormats.Gregorian, + id: 'id', + label: 'label' + }], + [Contacts.Fields.Relationships]: [{ + name: 'name', + id: 'id', + label: 'label' + }], + [Contacts.Fields.Nickname]: 'nickname', + [Contacts.Fields.RawImage]: { + uri: 'uri' + }, + [Contacts.Fields.MaidenName]: 'maidenName', + [Contacts.Fields.ContactType]: Contacts.ContactTypes.Person, + [Contacts.Fields.SocialProfiles]: [{ + service: 'service', + username: 'username', + localizedProfile: 'localizedProfile', + url: 'url', + userId: 'userId', + id: 'id', + label: 'label' + }], + [Contacts.Fields.NonGregorianBirthday]: { + day: 1, + month: 1, + year: 2010, + format: Contacts.CalendarFormats.Hebrew, + id: 'id', + label: 'label' + } +}; + +async () => { + const response1: Contacts.ContactResponse = await Contacts.getContactsAsync(); + const response2: Contacts.ContactResponse = await Contacts.getContactsAsync({ id: 'contactId' }); + + const response3: Contacts.Contact = await Contacts.getContactByIdAsync('contactId'); + const response4: Contacts.Contact = await Contacts.getContactByIdAsync('contactId', [Contacts.Fields.Name]); + + const response5: string = await Contacts.addContactAsync(contact); + const response6: string = await Contacts.addContactAsync(contact, 'containerId'); + + const response7: string = await Contacts.updateContactAsync(contact); + await Contacts.removeContactAsync('contactId'); + const response8: string = await Contacts.writeContactToFileAsync({ id: 'contactId' }); + await Contacts.presentFormAsync('contactId'); + await Contacts.addExistingGroupToContainerAsync('groupId', 'containerId'); + + const response9: string = await Contacts.createGroupAsync('groupId'); + const response10: string = await Contacts.createGroupAsync('groupId', 'containerId'); + + await Contacts.updateGroupNameAsync('groupName', 'groupId'); + await Contacts.removeGroupAsync('groupId'); + await Contacts.addExistingContactToGroupAsync('contactId', 'groupId'); + await Contacts.removeContactFromGroupAsync('contactId', 'groupId'); + + const response11 = await Contacts.getGroupsAsync({ groupName: 'groupName' }); + response11.forEach((_: Contacts.Group) => _); + + const response12: string = await Contacts.getDefaultContainerIdAsync(); + + const response13 = await Contacts.getContainersAsync({ containerId: 'containerId' }); + response13.forEach((_: Contacts.Container) => _); +}; +// #endregion diff --git a/types/expo/index.d.ts b/types/expo/index.d.ts index ee300d855d..fcaf421315 100644 --- a/types/expo/index.d.ts +++ b/types/expo/index.d.ts @@ -21,18 +21,8 @@ export * from 'react-native-maps'; export { default as MapView } from 'react-native-maps'; import { EventSubscription } from 'fbemitter'; -import { Component, ComponentClass, Ref, ComponentType } from 'react'; -import { - ColorPropType, - ImageRequireSource, - ImageURISource, - LinkingStatic as ReactNativeLinkingStatic, - NativeEventEmitter, - ViewProps, - ViewStyle, - Permission, - StyleProp -} from 'react-native'; +import { Component, ComponentClass, ComponentType, Ref } from 'react'; +import { ImageRequireSource, ImageURISource, LinkingStatic as ReactNativeLinkingStatic, StyleProp, ViewProps, ViewStyle } from 'react-native'; export type Axis = number; export type BarCodeReadCallback = (params: { type: string; data: string; }) => void; @@ -1010,150 +1000,479 @@ export namespace Constants { * Contacts */ export namespace Contacts { - type PhoneNumbers = 'phoneNumbers'; - type Emails = 'emails'; - type Addresses = 'addresses'; - type Image = 'image'; - type Thumbnail = 'thumbnail'; - type Note = 'note'; - type Birthday = 'birthday'; - type NonGregorianBirthday = 'nonGregorianBirthday'; - type NamePrefix = 'namePrefix'; - type NameSuffix = 'nameSuffix'; - type PhoneticFirstName = 'phoneticFirstName'; - type PhoneticMiddleName = 'phoneticMiddleName'; - type PhoneticLastName = 'phoneticLastName'; - type SocialProfiles = 'socialProfiles'; - type InstantMessageAddresses = 'instantMessageAddresses'; - type UrlAddresses = 'urlAddresses'; - type Dates = 'dates'; - type Relationships = 'relationships'; + // Constants - const PHONE_NUMBERS: PhoneNumbers; - const EMAILS: Emails; - const ADDRESSES: Addresses; - const IMAGE: Image; - const THUMBNAIL: Thumbnail; - const NOTE: Note; - const BIRTHDAY: Birthday; - const NON_GREGORIAN_BIRTHDAY: NonGregorianBirthday; - const NAME_PREFIX: NamePrefix; - const NAME_SUFFIX: NameSuffix; - const PHONETIC_FIRST_NAME: PhoneticFirstName; - const PHONETIC_MIDDLE_NAME: PhoneticMiddleName; - const PHONETIC_LAST_NAME: PhoneticLastName; - const SOCIAL_PROFILES: SocialProfiles; - const IM_ADDRESSES: InstantMessageAddresses; - const URLS: UrlAddresses; - const DATES: Dates; - const RELATIONSHIPS: Relationships; + namespace Fields { + type Id = 'id'; + type Name = 'name'; + type FirstName = 'firstName'; + type MiddleName = 'middleName'; + type LastName = 'lastName'; + type NamePrefix = 'namePrefix'; + type NameSuffix = 'nameSuffix'; + type PhoneticFirstName = 'phoneticFirstName'; + type PhoneticMiddleName = 'phoneticMiddleName'; + type PhoneticLastName = 'phoneticLastName'; + type Birthday = 'birthday'; + type Emails = 'emails'; + type PhoneNumbers = 'phoneNumbers'; + type Addresses = 'addresses'; + type InstantMessageAddresses = 'instantMessageAddresses'; + type UrlAddresses = 'urlAddresses'; + type Company = 'company'; + type JobTitle = 'jobTitle'; + type Department = 'department'; + type ImageAvailable = 'imageAvailable'; + type Image = 'image'; + type Note = 'note'; + type Dates = 'dates'; + type Relationships = 'relationships'; + type Nickname = 'nickname'; + type RawImage = 'rawImage'; + type MaidenName = 'maidenName'; + type ContactType = 'contactType'; + type SocialProfiles = 'socialProfiles'; + type NonGregorianBirthday = 'nonGregorianBirthday'; - type FieldType = PhoneNumbers | Emails | Addresses | Image | Thumbnail | - Note | Birthday | NonGregorianBirthday | NamePrefix | NameSuffix | - PhoneticFirstName | PhoneticMiddleName | PhoneticLastName | SocialProfiles | - InstantMessageAddresses | UrlAddresses | Dates | Relationships; - - interface Options { - pageSize?: number; - pageOffset?: number; - fields?: FieldType[]; + const ID: Id; + const Name: Name; + const FirstName: FirstName; + const MiddleName: MiddleName; + const LastName: LastName; + const NamePrefix: NamePrefix; + const NameSuffix: NameSuffix; + const PhoneticFirstName: PhoneticFirstName; + const PhoneticMiddleName: PhoneticMiddleName; + const PhoneticLastName: PhoneticLastName; + const Birthday: Birthday; + const Emails: Emails; + const PhoneNumbers: PhoneNumbers; + const Addresses: Addresses; + const InstantMessageAddresses: InstantMessageAddresses; + const UrlAddresses: UrlAddresses; + const Company: Company; + const JobTitle: JobTitle; + const Department: Department; + const ImageAvailable: ImageAvailable; + const Image: Image; + const Note: Note; + const Dates: Dates; + const Relationships: Relationships; + const Nickname: Nickname; + const RawImage: RawImage; + const MaidenName: MaidenName; + const ContactType: ContactType; + const SocialProfiles: SocialProfiles; + const NonGregorianBirthday: NonGregorianBirthday; } + type Field = + | Fields.Id + | Fields.Name + | Fields.FirstName + | Fields.MiddleName + | Fields.LastName + | Fields.NamePrefix + | Fields.NameSuffix + | Fields.PhoneticFirstName + | Fields.PhoneticMiddleName + | Fields.PhoneticLastName + | Fields.Birthday + | Fields.Emails + | Fields.PhoneNumbers + | Fields.Addresses + | Fields.InstantMessageAddresses + | Fields.UrlAddresses + | Fields.Company + | Fields.JobTitle + | Fields.Department + | Fields.ImageAvailable + | Fields.Image + | Fields.Note + | Fields.Dates + | Fields.Relationships + | Fields.Nickname + | Fields.RawImage + | Fields.MaidenName + | Fields.ContactType + | Fields.SocialProfiles + | Fields.NonGregorianBirthday; + + namespace FormTypes { + type New = 'new'; + type Unknown = 'unknown'; + type Default = 'default'; + + const New: New; + const Unknown: Unknown; + const Default: Default; + } + + type FormType = FormTypes.New | FormTypes.Unknown | FormTypes.Default; + + /** + * iOS Only + */ + namespace ContactTypes { + type Person = 'person'; + type Company = 'company'; + + const Person: Person; + const Company: Company; + } + + type ContactType = ContactTypes.Person | ContactTypes.Company; + + namespace SortTypes { + type FirstName = 'firstName'; + type LastName = 'lastName'; + type UserDefault = 'userDefault'; + + const FirstName: FirstName; + const LastName: LastName; + const UserDefault: UserDefault; + } + + type SortType = SortTypes.FirstName | SortTypes.LastName | SortTypes.UserDefault; + + /** + * iOS Only + */ + namespace ContainerTypes { + type Local = 'local'; + type Exchange = 'exchange'; + type CardDAV = 'cardDAV'; + type Unassigned = 'unassigned'; + + const Local: Local; + const Exchange: Exchange; + const CardDAV: CardDAV; + const Unassigned: Unassigned; + } + + type ContainerType = ContainerTypes.Local | ContainerTypes.Exchange | ContainerTypes.CardDAV | ContainerTypes.Unassigned; + + namespace CalendarFormats { + type Gregorian = 'gregorian'; + type Chinese = 'chinese'; + type Hebrew = 'hebrew'; + type Islamic = 'islamic'; + + const Gregorian: Gregorian; + const Chinese: Chinese; + const Hebrew: Hebrew; + const Islamic: Islamic; + } + + type CalendarFormat = CalendarFormats.Gregorian | CalendarFormats.Chinese | CalendarFormats.Hebrew | CalendarFormats.Islamic; + + // Types + + /** + * A set of fields that define information about a single entity. + */ interface Contact { id: string; - contactType: string; name: string; firstName?: string; middleName?: string; lastName?: string; - previousLastName?: string; + maidenName?: string; namePrefix?: string; nameSuffix?: string; nickname?: string; phoneticFirstName?: string; phoneticMiddleName?: string; phoneticLastName?: string; - emails?: Array<{ - email?: string; - primary?: boolean; - label: string; - id: string; - }>; - phoneNumbers?: Array<{ - number?: string; - primary?: boolean; - digits?: string; - countryCode?: string; - label: string; - id: string; - }>; - addresses?: Array<{ - street?: string; - city?: string; - country?: string; - region?: string; - neighborhood?: string; - postalCode?: string; - poBox?: string; - isoCountryCode?: string; - label: string; - id: string; - }>; - socialProfiles?: Array<{ - service?: string; - localizedProfile?: string; - url?: string; - username?: string; - userId?: string; - label: string; - id: string; - }>; - instantMessageAddresses?: Array<{ - service?: string; - username?: string; - localizedService?: string; - label: string; - id: string; - }>; - urls?: { - label: string; - url?: string; - id: string; - }; company?: string; jobTitle?: string; department?: string; - imageAvailable?: boolean; - image?: { - uri?: string; - }; - thumbnail?: { - uri?: string; - }; note?: string; - dates?: Array<{ - day?: number; - month?: number; - year?: number; - id: string; - label: string; - }>; - relationships?: Array<{ - label: string; - name?: string; - id: string; - }>; + imageAvailable?: boolean; + image?: Image; + rawImage?: Image; + contactType: ContactType; + birthday?: ContactDate; + dates?: ContactDate[]; + relationships?: Relationship[]; + emails?: Email[]; + phoneNumbers?: PhoneNumber[]; + addresses?: Address[]; + instantMessageAddresses?: InstantMessageAddress[]; + urlAddresses?: UrlAddress[]; + /** + * iOS only + */ + nonGregorianBirthday?: ContactDate; + /** + * iOS only + */ + socialProfiles?: SocialProfile[]; } - interface Response { + interface ContactDate { + day: number; + month: number; + year: number; + format: CalendarFormat; + id: string; + label: string; + } + + interface Relationship { + name: string; + id: string; + label: string; + } + + interface Email { + email: string; + isPrimary: boolean; + id: string; + label: string; + } + + interface PhoneNumber { + number: string; + isPrimary: boolean; + digits: string; + countryCode: string; + id: string; + label: string; + } + + interface Address { + street: string; + city: string; + country: string; + region: string; + neighborhood: string; + postalCode: string; + poBox: string; + isoCountryCode: string; + id: string; + label: string; + } + + /** + * iOS only + */ + interface SocialProfile { + service: string; + username: string; + localizedProfile: string; + url: string; + userId: string; + id: string; + label: string; + } + + interface InstantMessageAddress { + service: string; + username: string; + localizedProfile: string; + id: string; + label: string; + } + + interface UrlAddress { + url: string; + id: string; + label: string; + } + + /** + * Information regarding thumbnail images + */ + interface Image { + uri: string; + /** + * iOS only + * In Android you can get dimensions using `ReactNative.Image.getSize` + */ + width?: number; + /** + * iOS only + * In Android you can get dimensions using `ReactNative.Image.getSize` + */ + height?: number; + /** + * Avoid using Base 64 in React Native + * iOS only + */ + base64?: string; + } + + /** + * A parent to contacts. A contact can belong to multiple groups. + * iOS Only + */ + interface Group { + id: string; + name: string; + } + + /** + * A parent to contacts and groups. + * iOS Only + */ + interface Container { + id: string; + name: string; + } + + /** + * Denotes the functionality of a native contact form + */ + interface FormOptions { + displayedPropertyKeys?: Field[]; + message?: string; + alternateName?: string; + cancelButtonTitle?: string; + groupId?: string; + allowsEditing?: boolean; + allowsActions?: boolean; + shouldShowLinkedContacts?: boolean; + isNew?: boolean; + preventAnimation?: boolean; + } + + /** + * Used to query contacts from the user's device + */ + interface ContactQuery { + fields?: Field[]; + pageSize?: number; + pageOffset?: number; + id?: string; + /** Android Only */ + sort?: SortType; + /** iOS Only */ + name?: string; + /** iOS Only */ + groupId?: string; + /** iOS Only */ + containerID?: string; + /** iOS Only */ + rawContacts?: boolean; + } + + interface ContactResponse { data: Contact[]; - total: number; + /** + * This will be true if there are more contacts to retrieve beyond what is returned + */ hasNextPage: boolean; + /** + * `true` if there are previous contacts that weren't retrieved due to `pageOffset` + */ hasPreviousPage: boolean; } - function getContactsAsync(options: Options): Promise; - function getContactByIdAsync(options: { id?: string; fields?: FieldType[] }): Promise; + /** + * Used to query native contact groups. + * iOS Only + */ + interface GroupQuery { + groupName?: string; + groupId?: string; + containerId?: string; + } + + /** + * Used to query native contact containers. + * iOS Only + */ + interface ContainerQuery { + contactId?: string; + groupId?: string; + containerId?: string; + } + + // Methods + + /** + * Return a list of contacts that fit a given criteria. You can get all of the contacts by passing no criteria. + */ + function getContactsAsync(contactQuery?: ContactQuery): Promise; + /** + * Returns a contact matching the input id. Used for gathering precise data about a contact. + */ + function getContactByIdAsync(contactId: string, fields?: Field[]): Promise; + + // iOS Only - temporary + /** + * Creates a new contact and adds it to the system. + * iOS Only - temporary + */ + function addContactAsync(contact: Contact, containerId?: string): Promise; + /** + * Mutate the information of an existing contact. + * iOS Only - temporary + */ + function updateContactAsync(contact: Contact): Promise; + /** + * Delete a contact from the system. + * iOS Only - temporary + */ + function removeContactAsync(contactId: string): Promise; + /** + * Query a set of contacts and write them to a local uri that can be used for sharing with `ReactNative.Share`. + * iOS Only - temporary + */ + function writeContactToFileAsync(contactQuery: ContactQuery): Promise; + + // iOS Only + /** + * Present a native form for manipulating contacts. + * iOS Only + */ + function presentFormAsync(contactId: string, contact?: Contact, formOptions?: FormOptions): Promise; + /** + * Add a group to a container. + * iOS Only + */ + function addExistingGroupToContainerAsync(groupId: string, containerId: string): Promise; + /** + * Create a group with a name, and add it to a container. If the container is undefined, the default container will be targeted. + * iOS Only + */ + function createGroupAsync(groupName: string, containerId?: string): Promise; + /** + * Change the name of an existing group. + * iOS Only + */ + function updateGroupNameAsync(groupName: string, groupId: string): Promise; + /** + * Delete a group from the device. + * iOS Only + */ + function removeGroupAsync(groupId: string): Promise; + /** + * Add a contact as a member to a group. A contact can be a member of multiple groups. + * iOS Only + */ + function addExistingContactToGroupAsync(contactId: string, groupId: string): Promise; + /** + * Remove a contact's membership from a given group. This will not delete the contact. + * iOS Only + */ + function removeContactFromGroupAsync(contactId: string, groupId: string): Promise; + /** + * Query and return a list of system groups. + * iOS Only + */ + function getGroupsAsync(query: GroupQuery): Promise; + /** + * Get the default container's ID. + * iOS Only + */ + function getDefaultContainerIdAsync(): Promise; + /** + * Query a list of system containers. + * iOS Only + */ + function getContainersAsync(containerQuery: ContainerQuery): Promise; } /**