react-native-contacts 8.0.4 → 8.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.tool-versions CHANGED
@@ -1 +1 @@
1
- nodejs lts
1
+ nodejs 22.15.0
package/README.md CHANGED
@@ -194,6 +194,16 @@ If you'd like to read/write the contact's notes, call the `iosEnableNotesUsage(t
194
194
  * `requestPermission()`: Promise<string> - request permission to access Contacts _ios only_
195
195
  * `writePhotoToPath()` - writes the contact photo to a given path _android only_
196
196
 
197
+ ### ios group specific functions
198
+ * `getGroups()`: Promise - returns an array of all groups. Each group contains `{ identifier: string; name: string;}`
199
+ * `getGroup: (identifier: string)`: Promise - returns the group matching the provided group identifier.
200
+ * `deleteGroup(identifier: string)`: Promise - deletes a group by group identifier.
201
+ * `updateGroup(identifier: string, groupData: Pick<Group, 'name'>`: Promise - updates an existing group's details. You can only change the group name.
202
+ * `addGroup(group: Pick<Group, 'name'>)`: Promise - adds a new group. Group name should be provided.
203
+ * `contactsInGroup(identifier: string)`: Promise - retrieves all contacts within a specified group.
204
+ * `addContactsToGroup(groupIdentifier: string, contactIdentifiers: string[])`: Promise - adds contacts to a group. Only contacts with id that has `:ABperson` as suffix can be added.
205
+ * `removeContactsFromGroup(groupIdentifier: string, contactIdentifiers: string[])`: Promise - removes specified contacts from a group.
206
+
197
207
  ## Example Contact Record
198
208
  ```js
199
209
  {
package/index.d.ts CHANGED
@@ -4,79 +4,117 @@ export function getContactById(contactId: string): Promise<Contact | null>;
4
4
  export function getCount(): Promise<number>;
5
5
  export function getPhotoForId(contactId: string): Promise<string>;
6
6
  export function addContact(contact: Partial<Contact>): Promise<Contact>;
7
- export function openContactForm(contact: Partial<Contact>): Promise<Contact | null>;
7
+ export function openContactForm(
8
+ contact: Partial<Contact>
9
+ ): Promise<Contact | null>;
8
10
  export function openExistingContact(contact: Contact): Promise<Contact>;
9
- export function viewExistingContact(contact: { recordID: string }): Promise<Contact | void>
11
+ export function viewExistingContact(contact: {
12
+ recordID: string;
13
+ }): Promise<Contact | void>;
10
14
  export function editExistingContact(contact: Contact): Promise<Contact>;
11
- export function updateContact(contact: Partial<Contact> & {recordID: string}): Promise<void>;
15
+ export function updateContact(
16
+ contact: Partial<Contact> & { recordID: string }
17
+ ): Promise<void>;
12
18
  export function deleteContact(contact: Contact): Promise<void>;
13
19
  export function getContactsMatchingString(str: string): Promise<Contact[]>;
14
- export function getContactsByPhoneNumber(phoneNumber: string): Promise<Contact[]>;
15
- export function getContactsByEmailAddress(emailAddress: string): Promise<Contact[]>;
16
- export function checkPermission(): Promise<'authorized' | 'denied' | 'undefined'>;
17
- export function requestPermission(): Promise<'authorized' | 'denied' | 'undefined'>;
18
- export function writePhotoToPath(contactId: string, file: string): Promise<boolean>;
20
+ export function getContactsByPhoneNumber(
21
+ phoneNumber: string
22
+ ): Promise<Contact[]>;
23
+ export function getContactsByEmailAddress(
24
+ emailAddress: string
25
+ ): Promise<Contact[]>;
26
+ export function checkPermission(): Promise<
27
+ "authorized" | "denied" | "undefined"
28
+ >;
29
+ export function requestPermission(): Promise<
30
+ "authorized" | "denied" | "undefined"
31
+ >;
32
+ export function writePhotoToPath(
33
+ contactId: string,
34
+ file: string
35
+ ): Promise<boolean>;
19
36
  export function iosEnableNotesUsage(enabled: boolean): void;
20
37
 
38
+ export function getGroups(): Promise<Group[]>;
39
+ export function getGroup(identifier: string): Promise<Group | null>;
40
+ export function deleteGroup(identifier: string): Promise<boolean>;
41
+ export function updateGroup(
42
+ identifier: string,
43
+ groupData: Pick<Group, "name">
44
+ ): Promise<Group>;
45
+ export function addGroup(group: Pick<Group, "name">): Promise<Group>;
46
+ export function contactsInGroup(identifier: string): Promise<Contact[]>;
47
+ export function addContactsToGroup(
48
+ groupIdentifier: string,
49
+ contactIdentifiers: string[]
50
+ ): Promise<boolean>;
51
+ export function removeContactsFromGroup(
52
+ groupIdentifier: string,
53
+ contactIdentifiers: string[]
54
+ ): Promise<boolean>;
55
+ export interface Group {
56
+ identifier: string;
57
+ name: string;
58
+ }
21
59
  export interface EmailAddress {
22
- label: string;
23
- email: string;
60
+ label: string;
61
+ email: string;
24
62
  }
25
63
 
26
64
  export interface PhoneNumber {
27
- label: string;
28
- number: string;
65
+ label: string;
66
+ number: string;
29
67
  }
30
68
 
31
69
  export interface PostalAddress {
32
- label: string;
33
- formattedAddress: string;
34
- street: string;
35
- pobox: string;
36
- neighborhood: string;
37
- city: string;
38
- region: string;
39
- state: string;
40
- postCode: string;
41
- country: string;
70
+ label: string;
71
+ formattedAddress: string;
72
+ street: string;
73
+ pobox: string;
74
+ neighborhood: string;
75
+ city: string;
76
+ region: string;
77
+ state: string;
78
+ postCode: string;
79
+ country: string;
42
80
  }
43
81
 
44
82
  export interface InstantMessageAddress {
45
- username: string;
46
- service: string;
83
+ username: string;
84
+ service: string;
47
85
  }
48
86
 
49
87
  export interface Birthday {
50
- day: number;
51
- month: number;
52
- year?: number;
88
+ day: number;
89
+ month: number;
90
+ year?: number;
53
91
  }
54
92
 
55
93
  export interface UrlAddress {
56
- url: string;
57
- label: string;
94
+ url: string;
95
+ label: string;
58
96
  }
59
97
 
60
98
  export interface Contact {
61
- recordID: string;
62
- backTitle: string;
63
- company: string|null;
64
- emailAddresses: EmailAddress[];
65
- displayName: string|null;
66
- familyName: string;
67
- givenName: string|null;
68
- middleName: string;
69
- jobTitle: string|null;
70
- phoneNumbers: PhoneNumber[];
71
- hasThumbnail: boolean;
72
- thumbnailPath: string;
73
- isStarred: boolean;
74
- postalAddresses: PostalAddress[];
75
- prefix: string|null;
76
- suffix: string|null;
77
- department: string|null;
78
- birthday?: Birthday;
79
- imAddresses: InstantMessageAddress[];
80
- urlAddresses: UrlAddress[];
81
- note: string|null;
99
+ recordID: string;
100
+ backTitle: string;
101
+ company: string | null;
102
+ emailAddresses: EmailAddress[];
103
+ displayName: string | null;
104
+ familyName: string;
105
+ givenName: string | null;
106
+ middleName: string;
107
+ jobTitle: string | null;
108
+ phoneNumbers: PhoneNumber[];
109
+ hasThumbnail: boolean;
110
+ thumbnailPath: string;
111
+ isStarred: boolean;
112
+ postalAddresses: PostalAddress[];
113
+ prefix: string | null;
114
+ suffix: string | null;
115
+ department: string | null;
116
+ birthday?: Birthday;
117
+ imAddresses: InstantMessageAddress[];
118
+ urlAddresses: UrlAddress[];
119
+ note: string | null;
82
120
  }
package/index.ts CHANGED
@@ -1,9 +1,8 @@
1
1
  import { NativeModules } from "react-native";
2
2
  import NativeContacts from "./src/NativeContacts";
3
- import { Contact, PermissionType } from "./type";
3
+ import { Contact, Group, PermissionType } from "./type";
4
4
 
5
- const isTurboModuleEnabled = global.__turboModuleProxy != null;
6
- const Contacts = isTurboModuleEnabled ? NativeContacts : NativeModules.Contacts;
5
+ const Contacts = NativeModules.Contacts ?? NativeContacts;
7
6
 
8
7
  async function getAll(): Promise<Contact[]> {
9
8
  return Contacts.getAll();
@@ -87,6 +86,31 @@ async function writePhotoToPath(
87
86
  ): Promise<boolean> {
88
87
  return Contacts.writePhotoToPath(contactId, file);
89
88
  }
89
+
90
+ async function getGroups(): Promise<Group[]> {
91
+ return Contacts.getGroups();
92
+ }
93
+ async function getGroup(identifier: string): Promise<Group | null> {
94
+ return Contacts.getGroup(identifier);
95
+ }
96
+ async function deleteGroup(identifier: string): Promise<boolean> {
97
+ return Contacts.deleteGroup(identifier);
98
+ }
99
+ async function updateGroup(identifier: string, groupData: Pick<Group, 'name'>): Promise<Group> {
100
+ return Contacts.updateGroup(identifier, groupData);
101
+ }
102
+ async function addGroup(group: Pick<Group, 'name'>): Promise<Group>{
103
+ return Contacts.addGroup(group);
104
+ }
105
+ async function contactsInGroup(identifier: string): Promise<Contact[]> {
106
+ return Contacts.contactsInGroup(identifier);
107
+ }
108
+ async function addContactsToGroup(groupIdentifier: string, contactIdentifiers: string[]): Promise<boolean> {
109
+ return Contacts.addContactsToGroup(groupIdentifier, contactIdentifiers);
110
+ }
111
+ async function removeContactsFromGroup(groupIdentifier: string, contactIdentifiers: string[]): Promise<boolean> {
112
+ return Contacts.removeContactsFromGroup(groupIdentifier, contactIdentifiers);
113
+ }
90
114
  export default {
91
115
  getAll,
92
116
  getAllWithoutPhotos,
@@ -106,4 +130,12 @@ export default {
106
130
  checkPermission,
107
131
  requestPermission,
108
132
  writePhotoToPath,
133
+ getGroups,
134
+ getGroup,
135
+ deleteGroup,
136
+ updateGroup,
137
+ addGroup,
138
+ contactsInGroup,
139
+ addContactsToGroup,
140
+ removeContactsFromGroup
109
141
  };
@@ -3,12 +3,9 @@
3
3
  #import "RCTContacts.h"
4
4
  #import <AssetsLibrary/AssetsLibrary.h>
5
5
  #import <React/RCTLog.h>
6
+ #import <React/RCTUtils.h>
6
7
  #import <Photos/Photos.h>
7
8
 
8
- // #ifdef RCT_NEW_ARCH_ENABLED
9
- // #import "RNContactsSpec.h"
10
- // #endif
11
-
12
9
  @implementation RCTContacts {
13
10
  CNContactStore * contactStore;
14
11
 
@@ -56,24 +53,27 @@ RCT_REMAP_METHOD(getAll, withResolver:(RCTPromiseResolveBlock) resolve
56
53
  }
57
54
 
58
55
 
59
- RCT_EXPORT_METHOD(checkPermission:(RCTPromiseResolveBlock) resolve
56
+ RCT_EXPORT_METHOD(checkPermission:(RCTPromiseResolveBlock)resolve
60
57
  rejecter:(RCTPromiseRejectBlock) __unused reject)
61
58
  {
62
59
  CNAuthorizationStatus authStatus = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];
63
60
 
64
- if (authStatus == CNAuthorizationStatusDenied || authStatus == CNAuthorizationStatusRestricted){
61
+ if (authStatus == CNAuthorizationStatusDenied || authStatus == CNAuthorizationStatusRestricted) {
65
62
  resolve(@"denied");
66
- } else if (authStatus == CNAuthorizationStatusAuthorized){
63
+ } else if (authStatus == CNAuthorizationStatusAuthorized) {
67
64
  resolve(@"authorized");
68
- } else if(@available(iOS 18, *)) {
69
- if (authStatus == CNAuthorizationStatusLimited) {
65
+ } else if (@available(iOS 18, *)) {
66
+ if (authStatus == CNAuthorizationStatusRestricted) {
70
67
  resolve(@"limited");
68
+ } else {
69
+ resolve(@"undefined");
71
70
  }
72
71
  } else {
73
72
  resolve(@"undefined");
74
73
  }
75
74
  }
76
75
 
76
+
77
77
  RCT_EXPORT_METHOD(requestPermission:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) __unused reject)
78
78
  {
79
79
  CNContactStore* contactStore = [[CNContactStore alloc] init];
@@ -555,35 +555,34 @@ RCT_EXPORT_METHOD(getCount:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromise
555
555
  return [paths firstObject];
556
556
  }
557
557
 
558
- RCT_EXPORT_METHOD(getPhotoForId:(nonnull NSString *)recordID resolver:(RCTPromiseResolveBlock) resolve
559
- rejecter:(RCTPromiseRejectBlock) reject)
558
+ RCT_EXPORT_METHOD(getPhotoForId:(nonnull NSString *)recordID resolver:(RCTPromiseResolveBlock)resolve
559
+ rejecter:(RCTPromiseRejectBlock)reject)
560
560
  {
561
- CNContactStore* contactStore = [self contactsStore:reject];
562
- if(!contactStore)
561
+ CNContactStore *contactStore = [self contactsStore:reject];
562
+ if (!contactStore)
563
563
  return;
564
564
 
565
565
  CNEntityType entityType = CNEntityTypeContacts;
566
- if([CNContactStore authorizationStatusForEntityType:entityType] == CNAuthorizationStatusNotDetermined)
567
- {
566
+ CNAuthorizationStatus authStatus = [CNContactStore authorizationStatusForEntityType:entityType];
567
+
568
+ if (authStatus == CNAuthorizationStatusNotDetermined) {
568
569
  [contactStore requestAccessForEntityType:entityType completionHandler:^(BOOL granted, NSError * _Nullable error) {
569
- if(granted){
570
+ if (granted) {
570
571
  resolve([self getFilePathForThumbnailImage:recordID addressBook:contactStore]);
571
572
  }
572
573
  }];
573
- }
574
- else if([CNContactStore authorizationStatusForEntityType:entityType]== CNAuthorizationStatusAuthorized)
575
- {
574
+ } else if (authStatus == CNAuthorizationStatusAuthorized) {
576
575
  resolve([self getFilePathForThumbnailImage:recordID addressBook:contactStore]);
577
- }
578
- else if(@available(iOS 18, *))
579
- {
580
- if([CNContactStore authorizationStatusForEntityType:entityType] == CNAuthorizationStatusLimited)
581
- {
576
+ } else if (@available(iOS 18, *)) {
577
+ if (authStatus == CNAuthorizationStatusRestricted) {
582
578
  resolve([self getFilePathForThumbnailImage:recordID addressBook:contactStore]);
583
579
  }
580
+ } else {
581
+ reject(@"CONTACT_ACCESS_DENIED", @"Contact access is not authorized.", nil);
584
582
  }
585
583
  }
586
584
 
585
+
587
586
  -(NSString *) getFilePathForThumbnailImage:(NSString *)recordID
588
587
  addressBook:(CNContactStore*)addressBook
589
588
  {
@@ -600,35 +599,36 @@ RCT_EXPORT_METHOD(getPhotoForId:(nonnull NSString *)recordID resolver:(RCTPromis
600
599
  return [self getFilePathForThumbnailImage:contact recordID:recordID];
601
600
  }
602
601
 
603
- RCT_EXPORT_METHOD(getContactById:(nonnull NSString *)recordID resolver:(RCTPromiseResolveBlock) resolve
604
- rejecter:(RCTPromiseRejectBlock) reject)
602
+
603
+ RCT_EXPORT_METHOD(getContactById:(nonnull NSString *)recordID resolver:(RCTPromiseResolveBlock)resolve
604
+ rejecter:(RCTPromiseRejectBlock)reject)
605
605
  {
606
- CNContactStore* contactStore = [self contactsStore:reject];
607
- if(!contactStore)
606
+ CNContactStore *contactStore = [self contactsStore:reject];
607
+ if (!contactStore)
608
608
  return;
609
609
 
610
610
  CNEntityType entityType = CNEntityTypeContacts;
611
- if([CNContactStore authorizationStatusForEntityType:entityType] == CNAuthorizationStatusNotDetermined)
612
- {
611
+
612
+ CNAuthorizationStatus authStatus = [CNContactStore authorizationStatusForEntityType:entityType];
613
+
614
+ if (authStatus == CNAuthorizationStatusNotDetermined) {
613
615
  [contactStore requestAccessForEntityType:entityType completionHandler:^(BOOL granted, NSError * _Nullable error) {
614
- if(granted){
615
- resolve([self getContact:recordID addressBook:contactStore withThumbnails:false]);
616
+ if (granted) {
617
+ resolve([self getContact:recordID addressBook:contactStore withThumbnails:NO]);
616
618
  }
617
619
  }];
618
- }
619
- else if([CNContactStore authorizationStatusForEntityType:entityType]== CNAuthorizationStatusAuthorized)
620
- {
621
- resolve([self getContact:recordID addressBook:contactStore withThumbnails:false]);
622
- }
623
- else if(@available(iOS 18, *))
624
- {
625
- if([CNContactStore authorizationStatusForEntityType:entityType] == CNAuthorizationStatusLimited)
626
- {
627
- resolve([self getContact:recordID addressBook:contactStore withThumbnails:false]);
620
+ } else if (authStatus == CNAuthorizationStatusAuthorized) {
621
+ resolve([self getContact:recordID addressBook:contactStore withThumbnails:NO]);
622
+ } else if (@available(iOS 18, *)) {
623
+ if (authStatus == CNAuthorizationStatusRestricted) {
624
+ resolve([self getContact:recordID addressBook:contactStore withThumbnails:NO]);
628
625
  }
626
+ } else {
627
+ reject(@"CONTACT_ACCESS_DENIED", @"Contact access is not authorized.", nil);
629
628
  }
630
629
  }
631
630
 
631
+
632
632
  -(NSDictionary *) getContact:(NSString *)recordID
633
633
  addressBook:(CNContactStore*)addressBook
634
634
  withThumbnails:(BOOL) withThumbnails
@@ -687,7 +687,7 @@ RCT_EXPORT_METHOD(addContact:(NSDictionary *)contactData resolver:(RCTPromiseRes
687
687
  }
688
688
  }
689
689
 
690
- RCT_EXPORT_METHOD(openContactForm:(NSDictionary *)contactData
690
+ RCT_EXPORT_METHOD(openContactForm:(NSDictionary *)contactData
691
691
  resolver:(RCTPromiseResolveBlock) resolve
692
692
  rejecter:(RCTPromiseRejectBlock) __unused reject)
693
693
  {
@@ -702,12 +702,8 @@ RCT_EXPORT_METHOD(openContactForm:(NSDictionary *)contactData
702
702
 
703
703
  dispatch_async(dispatch_get_main_queue(), ^{
704
704
  UINavigationController* navigation = [[UINavigationController alloc] initWithRootViewController:controller];
705
- UIViewController *viewController = (UIViewController*)[[[[UIApplication sharedApplication] delegate] window] rootViewController];
706
- while (viewController.presentedViewController)
707
- {
708
- viewController = viewController.presentedViewController;
709
- }
710
- [viewController presentViewController:navigation animated:YES completion:nil];
705
+ UIViewController *presentingViewController = RCTPresentedViewController();
706
+ [presentingViewController presentViewController:navigation animated:YES completion:nil];
711
707
 
712
708
  self->updateContactPromise = resolve;
713
709
  });
@@ -759,7 +755,7 @@ RCT_EXPORT_METHOD(openExistingContact:(NSDictionary *)contactData resolver:(RCTP
759
755
  activityIndicatorStyle = UIActivityIndicatorViewStyleMedium;
760
756
  activityIndicatorBackgroundColor = [UIColor secondarySystemGroupedBackgroundColor];
761
757
  } else {
762
- activityIndicatorStyle = UIActivityIndicatorViewStyleGray;
758
+ activityIndicatorStyle = UIActivityIndicatorViewStyleMedium;
763
759
  activityIndicatorBackgroundColor = [UIColor whiteColor];;
764
760
  }
765
761
 
@@ -791,7 +787,7 @@ RCT_EXPORT_METHOD(openExistingContact:(NSDictionary *)contactData resolver:(RCTP
791
787
  [activityIndicatorView removeFromSuperview];
792
788
  });
793
789
 
794
- updateContactPromise = resolve;
790
+ self->updateContactPromise = resolve;
795
791
  });
796
792
 
797
793
  }
@@ -831,15 +827,8 @@ RCT_EXPORT_METHOD(viewExistingContact:(NSDictionary *)contactData resolver:(RCTP
831
827
 
832
828
  dispatch_async(dispatch_get_main_queue(), ^{
833
829
  UINavigationController* navigation = [[UINavigationController alloc] initWithRootViewController:contactViewController];
834
-
835
- UIViewController *currentViewController = [UIApplication sharedApplication].keyWindow.rootViewController;
836
-
837
- while (currentViewController.presentedViewController)
838
- {
839
- currentViewController = currentViewController.presentedViewController;
840
- }
841
-
842
- [currentViewController presentViewController:navigation animated:YES completion:nil];
830
+ UIViewController *presentingViewController = RCTPresentedViewController();
831
+ [presentingViewController presentViewController:navigation animated:YES completion:nil];
843
832
 
844
833
  updateContactPromise = resolve;
845
834
  });
@@ -922,15 +911,8 @@ RCT_EXPORT_METHOD(editExistingContact:(NSDictionary *)contactData resolver:(RCTP
922
911
 
923
912
  dispatch_async(dispatch_get_main_queue(), ^{
924
913
  UINavigationController* navigation = [[UINavigationController alloc] initWithRootViewController:controller];
925
- UIViewController *viewController = (UIViewController*)[[[[UIApplication sharedApplication] delegate] window] rootViewController];
926
-
927
- //navigation.navigationBar.titleTextAttributes = @{NSForegroundColorAttributeName : [UIColor redColor]};
928
-
929
- while (viewController.presentedViewController)
930
- {
931
- viewController = viewController.presentedViewController;
932
- }
933
- [viewController presentViewController:navigation animated:YES completion:nil];
914
+ UIViewController *presentingViewController = RCTPresentedViewController();
915
+ [presentingViewController presentViewController:navigation animated:YES completion:nil];
934
916
  [controller presentViewController:alert animated:YES completion:nil];
935
917
 
936
918
  self->updateContactPromise = resolve;
@@ -1250,7 +1232,7 @@ RCT_EXPORT_METHOD(deleteContact:(NSDictionary *)contactData resolver:(RCTPromise
1250
1232
  }
1251
1233
 
1252
1234
  RCT_EXPORT_METHOD(writePhotoToPath:(nonnull NSString *)path resolver:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject)
1253
- {
1235
+ {
1254
1236
  @try {
1255
1237
  //Nothing is implemented here
1256
1238
  } @catch (NSException *exception) {
@@ -1258,6 +1240,382 @@ RCT_EXPORT_METHOD(writePhotoToPath:(nonnull NSString *)path resolver:(RCTPromise
1258
1240
  }
1259
1241
  }
1260
1242
 
1243
+ RCT_EXPORT_METHOD(getGroups:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
1244
+ {
1245
+ if (!contactStore) {
1246
+ contactStore = [[CNContactStore alloc] init];
1247
+ }
1248
+
1249
+ NSError *error = nil;
1250
+ NSArray<CNGroup *> *groups = [contactStore groupsMatchingPredicate:nil error:&error];
1251
+
1252
+ if (error) {
1253
+ reject(@"get_groups_error", @"Failed to fetch groups", error);
1254
+ return;
1255
+ }
1256
+
1257
+ NSMutableArray *groupArray = [NSMutableArray array];
1258
+ for (CNGroup *group in groups) {
1259
+ NSDictionary *groupDict = @{
1260
+ @"identifier": group.identifier ?: @"",
1261
+ @"name": group.name ?: @""
1262
+ };
1263
+ [groupArray addObject:groupDict];
1264
+ }
1265
+
1266
+ resolve(groupArray);
1267
+ }
1268
+ RCT_EXPORT_METHOD(getGroup:(NSString *)identifier resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
1269
+ {
1270
+ CNContactStore *contactStore = [self contactsStore:reject];
1271
+ if (!contactStore) {
1272
+ // contactsStore method handles rejection
1273
+ return;
1274
+ }
1275
+
1276
+ NSError *error = nil;
1277
+ NSPredicate *predicate = [CNGroup predicateForGroupsWithIdentifiers:@[identifier]];
1278
+ NSArray<CNGroup *> *groups = [contactStore groupsMatchingPredicate:predicate error:&error];
1279
+
1280
+ if (error) {
1281
+ reject(@"get_group_error", @"Failed to fetch group", error);
1282
+ return;
1283
+ }
1284
+
1285
+ if (groups.count == 0) {
1286
+ reject(@"get_group_not_found", @"No group found with the given identifier", nil);
1287
+ return;
1288
+ }
1289
+
1290
+ CNGroup *group = groups.firstObject;
1291
+ NSDictionary *groupDict = @{
1292
+ @"identifier": group.identifier ?: @"",
1293
+ @"name": group.name ?: @""
1294
+ };
1295
+
1296
+ resolve(groupDict);
1297
+ }
1298
+
1299
+ RCT_EXPORT_METHOD(deleteGroup:(NSString *)identifier
1300
+ resolver:(RCTPromiseResolveBlock)resolve
1301
+ rejecter:(RCTPromiseRejectBlock)reject) {
1302
+ if (!contactStore) {
1303
+ contactStore = [[CNContactStore alloc] init];
1304
+ }
1305
+
1306
+ NSError *error = nil;
1307
+ NSPredicate *predicate = [CNGroup predicateForGroupsWithIdentifiers:@[identifier]];
1308
+ NSArray<CNGroup *> *groups = [contactStore groupsMatchingPredicate:predicate error:&error];
1309
+
1310
+ if (error) {
1311
+ reject(@"delete_group_error", @"Failed to fetch group", error);
1312
+ return;
1313
+ }
1314
+
1315
+ if (groups.count == 0) {
1316
+ reject(@"delete_group_not_found", @"No group found with the given identifier", nil);
1317
+ return;
1318
+ }
1319
+
1320
+ CNGroup *groupToDelete = groups.firstObject;
1321
+ CNMutableGroup *mutableGroup = [groupToDelete mutableCopy];
1322
+ CNSaveRequest *saveRequest = [[CNSaveRequest alloc] init];
1323
+ [saveRequest deleteGroup:mutableGroup];
1324
+
1325
+ BOOL success = [contactStore executeSaveRequest:saveRequest error:&error];
1326
+
1327
+ if (success) {
1328
+ resolve(@(YES));
1329
+ } else {
1330
+ reject(@"delete_group_failed", @"Failed to delete group", error);
1331
+ }
1332
+ }
1333
+
1334
+ RCT_EXPORT_METHOD(updateGroup:(NSString *)identifier
1335
+ groupData:(NSDictionary *)groupData
1336
+ resolver:(RCTPromiseResolveBlock)resolve
1337
+ rejecter:(RCTPromiseRejectBlock)reject)
1338
+ {
1339
+ if (!contactStore) {
1340
+ contactStore = [[CNContactStore alloc] init];
1341
+ }
1342
+
1343
+ NSError *error = nil;
1344
+ NSPredicate *predicate = [CNGroup predicateForGroupsWithIdentifiers:@[identifier]];
1345
+ NSArray<CNGroup *> *groups = [contactStore groupsMatchingPredicate:predicate error:&error];
1346
+
1347
+ if (error) {
1348
+ reject(@"update_group_error", @"Failed to fetch group", error);
1349
+ return;
1350
+ }
1351
+
1352
+ if (groups.count == 0) {
1353
+ reject(@"update_group_not_found", @"No group found with the given identifier", nil);
1354
+ return;
1355
+ }
1356
+
1357
+ CNGroup *groupToUpdate = groups.firstObject;
1358
+ CNMutableGroup *mutableGroup = [groupToUpdate mutableCopy];
1359
+
1360
+ // Update group details based on groupData
1361
+ NSString *newName = groupData[@"name"];
1362
+ if (newName && [newName isKindOfClass:[NSString class]] && newName.length > 0) {
1363
+ mutableGroup.name = newName;
1364
+ }
1365
+
1366
+ CNSaveRequest *saveRequest = [[CNSaveRequest alloc] init];
1367
+ [saveRequest updateGroup:mutableGroup];
1368
+
1369
+ BOOL success = [contactStore executeSaveRequest:saveRequest error:&error];
1370
+
1371
+ if (success) {
1372
+ NSDictionary *updatedGroupDict = @{
1373
+ @"identifier": mutableGroup.identifier ?: @"",
1374
+ @"name": mutableGroup.name ?: @""
1375
+ };
1376
+ resolve(updatedGroupDict);
1377
+ } else {
1378
+ reject(@"update_group_failed", @"Failed to update group", error);
1379
+ }
1380
+ }
1381
+
1382
+ RCT_EXPORT_METHOD(addGroup:(NSDictionary *)groupData
1383
+ resolver:(RCTPromiseResolveBlock)resolve
1384
+ rejecter:(RCTPromiseRejectBlock)reject)
1385
+ {
1386
+ if (!groupData || ![groupData isKindOfClass:[NSDictionary class]]) {
1387
+ reject(@"invalid_data", @"Group data must be a dictionary", nil);
1388
+ return;
1389
+ }
1390
+
1391
+ NSString *groupName = groupData[@"name"];
1392
+ if (!groupName || ![groupName isKindOfClass:[NSString class]] || groupName.length == 0) {
1393
+ reject(@"invalid_name", @"Group name is required and must be a non-empty string", nil);
1394
+ return;
1395
+ }
1396
+
1397
+ CNContactStore *store = [self contactsStore:reject];
1398
+ if (!store) {
1399
+ // contactsStore method handles rejection
1400
+ return;
1401
+ }
1402
+
1403
+ CNMutableGroup *mutableGroup = [[CNMutableGroup alloc] init];
1404
+ mutableGroup.name = groupName;
1405
+
1406
+ CNSaveRequest *saveRequest = [[CNSaveRequest alloc] init];
1407
+ [saveRequest addGroup:mutableGroup toContainerWithIdentifier:nil]; // Add to default container
1408
+
1409
+ NSError *error = nil;
1410
+ BOOL success = [store executeSaveRequest:saveRequest error:&error];
1411
+ if (success) {
1412
+ NSDictionary *groupDict = @{
1413
+ @"identifier": mutableGroup.identifier ?: @"",
1414
+ @"name": mutableGroup.name ?: @""
1415
+ };
1416
+ resolve(groupDict);
1417
+ } else {
1418
+ NSString *errorMessage = error.localizedDescription ?: @"Unknown error adding group";
1419
+ reject(@"add_group_error", errorMessage, error);
1420
+ }
1421
+ }
1422
+
1423
+ RCT_EXPORT_METHOD(contactsInGroup:(NSString *)identifier
1424
+ resolver:(RCTPromiseResolveBlock)resolve
1425
+ rejecter:(RCTPromiseRejectBlock)reject)
1426
+ {
1427
+ CNContactStore *store = [self contactsStore:reject];
1428
+ if (!store) {
1429
+ return;
1430
+ }
1431
+
1432
+ NSError *error = nil;
1433
+ NSPredicate *predicate = [CNContact predicateForContactsInGroupWithIdentifier:identifier];
1434
+ NSArray *keysToFetch = @[
1435
+ CNContactIdentifierKey,
1436
+ CNContactGivenNameKey,
1437
+ CNContactFamilyNameKey,
1438
+ CNContactMiddleNameKey,
1439
+ CNContactEmailAddressesKey,
1440
+ CNContactPhoneNumbersKey,
1441
+ CNContactPostalAddressesKey,
1442
+ CNContactOrganizationNameKey,
1443
+ CNContactJobTitleKey,
1444
+ CNContactImageDataAvailableKey,
1445
+ CNContactThumbnailImageDataKey,
1446
+ CNContactUrlAddressesKey,
1447
+ CNContactBirthdayKey,
1448
+ CNContactInstantMessageAddressesKey
1449
+ ];
1450
+
1451
+ if (notesUsageEnabled) {
1452
+ keysToFetch = [keysToFetch arrayByAddingObject:CNContactNoteKey];
1453
+ }
1454
+
1455
+ CNContactFetchRequest *fetchRequest = [[CNContactFetchRequest alloc] initWithKeysToFetch:keysToFetch];
1456
+ fetchRequest.predicate = predicate;
1457
+
1458
+ NSMutableArray *contacts = [NSMutableArray array];
1459
+
1460
+ BOOL success = [store enumerateContactsWithFetchRequest:fetchRequest error:&error usingBlock:^(CNContact * _Nonnull contact, BOOL * _Nonnull stop) {
1461
+ NSDictionary *contactDict = [self contactToDictionary:contact withThumbnails:true];
1462
+ [contacts addObject:contactDict];
1463
+ }];
1464
+
1465
+ if (!success) {
1466
+ reject(@"contacts_in_group_error", @"Failed to fetch contacts in group", error);
1467
+ return;
1468
+ }
1469
+
1470
+ resolve(contacts);
1471
+ }
1472
+
1473
+ RCT_EXPORT_METHOD(addContactsToGroup:(NSString *)groupId
1474
+ contactIds:(NSArray<NSString *> *)contactIds
1475
+ resolver:(RCTPromiseResolveBlock)resolve
1476
+ rejecter:(RCTPromiseRejectBlock)reject)
1477
+ {
1478
+ // Ensure contactStore is initialized
1479
+ if (!contactStore) {
1480
+ contactStore = [[CNContactStore alloc] init];
1481
+ }
1482
+
1483
+ // Check authorization
1484
+ CNAuthorizationStatus authStatus = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];
1485
+ if (@available(iOS 18.0, *)) {
1486
+ if (authStatus != CNAuthorizationStatusAuthorized && authStatus != CNAuthorizationStatusLimited) {
1487
+ reject(@"permission_denied", @"Contacts permission denied", nil);
1488
+ return;
1489
+ }
1490
+ }
1491
+ else if (authStatus != CNAuthorizationStatusAuthorized) {
1492
+ reject(@"permission_denied", @"Contacts permission denied", nil);
1493
+ return;
1494
+ }
1495
+
1496
+ NSError *error = nil;
1497
+
1498
+ // Fetch the group
1499
+ NSPredicate *predicate = [CNGroup predicateForGroupsWithIdentifiers:@[groupId]];
1500
+ NSArray<CNGroup *> *groups = [contactStore groupsMatchingPredicate:predicate error:&error];
1501
+
1502
+ if (error) {
1503
+ reject(@"group_fetch_error", @"Failed to fetch group", error);
1504
+ return;
1505
+ }
1506
+
1507
+ if (groups.count == 0) {
1508
+ reject(@"group_not_found", @"No group found with the given identifier", nil);
1509
+ return;
1510
+ }
1511
+
1512
+ CNGroup *group = groups.firstObject;
1513
+
1514
+ // Initialize CNSaveRequest
1515
+ CNSaveRequest *saveRequest = [[CNSaveRequest alloc] init];
1516
+
1517
+ // Iterate over contactIds and add each contact to the group
1518
+ for (NSString *contactId in contactIds) {
1519
+ // Fetch the contact
1520
+ NSError *contactError = nil;
1521
+ CNContact *contact = [contactStore unifiedContactWithIdentifier:contactId
1522
+ keysToFetch:@[CNContactIdentifierKey]
1523
+ error:&contactError];
1524
+ if (contactError) {
1525
+ reject(@"contact_fetch_error", [NSString stringWithFormat:@"Failed to fetch contact with ID %@", contactId], contactError);
1526
+ return;
1527
+ }
1528
+
1529
+ if (!contact) {
1530
+ reject(@"contact_not_found", [NSString stringWithFormat:@"No contact found with ID %@", contactId], nil);
1531
+ return;
1532
+ }
1533
+
1534
+ // Add contact to group
1535
+ [saveRequest addMember:contact toGroup:group];
1536
+ }
1537
+
1538
+ // Execute the save request
1539
+ BOOL success = [contactStore executeSaveRequest:saveRequest error:&error];
1540
+
1541
+ if (success) {
1542
+ resolve(@(YES));
1543
+ } else {
1544
+ reject(@"add_contacts_error", @"Failed to add contacts to group", error);
1545
+ }
1546
+ }
1547
+
1548
+ RCT_EXPORT_METHOD(removeContactsFromGroup:(NSString *)groupId
1549
+ contactIds:(NSArray<NSString *> *)contactIds
1550
+ resolver:(RCTPromiseResolveBlock)resolve
1551
+ rejecter:(RCTPromiseRejectBlock)reject) {
1552
+ // Ensure contactStore is initialized
1553
+ if (!contactStore) {
1554
+ contactStore = [[CNContactStore alloc] init];
1555
+ }
1556
+
1557
+ // Check authorization status
1558
+ CNAuthorizationStatus authStatus = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];
1559
+ if (@available(iOS 18.0, *)) {
1560
+ if (authStatus != CNAuthorizationStatusAuthorized && authStatus != CNAuthorizationStatusLimited) {
1561
+ reject(@"permission_denied", @"Contacts permission denied", nil);
1562
+ return;
1563
+ }
1564
+ }
1565
+ else if (authStatus != CNAuthorizationStatusAuthorized) {
1566
+ reject(@"permission_denied", @"Contacts permission denied", nil);
1567
+ return;
1568
+ }
1569
+
1570
+ // Fetch the group
1571
+ NSError *error = nil;
1572
+ NSPredicate *predicate = [CNGroup predicateForGroupsWithIdentifiers:@[groupId]];
1573
+ NSArray<CNGroup *> *groups = [contactStore groupsMatchingPredicate:predicate error:&error];
1574
+
1575
+ if (error) {
1576
+ reject(@"group_fetch_error", @"Failed to fetch group", error);
1577
+ return;
1578
+ }
1579
+
1580
+ if (groups.count == 0) {
1581
+ reject(@"group_not_found", @"No group found with the given identifier", nil);
1582
+ return;
1583
+ }
1584
+
1585
+ CNGroup *group = groups.firstObject;
1586
+ CNMutableGroup *mutableGroup = [group mutableCopy];
1587
+ CNSaveRequest *saveRequest = [[CNSaveRequest alloc] init];
1588
+
1589
+ // Iterate over contactIds and remove each contact from the group
1590
+ for (NSString *contactId in contactIds) {
1591
+ NSError *contactError = nil;
1592
+ CNContact *contact = [contactStore unifiedContactWithIdentifier:contactId
1593
+ keysToFetch:@[CNContactIdentifierKey]
1594
+ error:&contactError];
1595
+ if (contactError) {
1596
+ reject(@"contact_fetch_error", [NSString stringWithFormat:@"Failed to fetch contact with ID %@", contactId], contactError);
1597
+ return;
1598
+ }
1599
+
1600
+ if (!contact) {
1601
+ reject(@"contact_not_found", [NSString stringWithFormat:@"No contact found with ID %@", contactId], nil);
1602
+ return;
1603
+ }
1604
+
1605
+ [saveRequest removeMember:contact fromGroup:mutableGroup];
1606
+ }
1607
+
1608
+ // Execute the save request
1609
+ BOOL success = [contactStore executeSaveRequest:saveRequest error:&error];
1610
+
1611
+ if (success) {
1612
+ resolve(@(YES));
1613
+ } else {
1614
+ reject(@"remove_contacts_error", @"Failed to remove contacts from group", error);
1615
+ }
1616
+ }
1617
+
1618
+
1261
1619
  -(CNContactStore*) contactsStore: (RCTPromiseRejectBlock) reject {
1262
1620
  if(!contactStore) {
1263
1621
  CNContactStore* store = [[CNContactStore alloc] init];
@@ -1295,24 +1653,25 @@ RCT_EXPORT_METHOD(writePhotoToPath:(nonnull NSString *)path resolver:(RCTPromise
1295
1653
 
1296
1654
  #endif
1297
1655
 
1298
- - (void)getAll:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
1656
+ - (void)getAll:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
1299
1657
  [self getAllContacts:resolve reject:reject withThumbnails:true];
1300
1658
  }
1301
1659
 
1302
- - (void)checkPermission:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
1303
- CNAuthorizationStatus authStatus = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];
1304
- if (authStatus == CNAuthorizationStatusDenied || authStatus == CNAuthorizationStatusRestricted){
1305
- resolve(@"denied");
1306
- } else if (authStatus == CNAuthorizationStatusAuthorized){
1307
- resolve(@"authorized");
1308
- } else if(@available(iOS 18, *)) {
1309
- if (authStatus == CNAuthorizationStatusLimited) {
1310
- resolve(@"limited");
1311
- }
1312
- } else {
1313
- resolve(@"undefined");
1314
- }
1315
- }
1660
+ - (void)checkPermission:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
1661
+ CNAuthorizationStatus authStatus = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];
1662
+
1663
+ if (authStatus == CNAuthorizationStatusDenied || authStatus == CNAuthorizationStatusRestricted) {
1664
+ resolve(@"denied");
1665
+ } else if (authStatus == CNAuthorizationStatusAuthorized) {
1666
+ resolve(@"authorized");
1667
+ } else if (@available(iOS 18, *)) {
1668
+ if (authStatus == CNAuthorizationStatusRestricted) {
1669
+ resolve(@"limited");
1670
+ }
1671
+ } else {
1672
+ resolve(@"undefined");
1673
+ }
1674
+ }
1316
1675
 
1317
1676
 
1318
1677
  - (void)deleteContact:(NSDictionary *)contactData resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
@@ -1411,15 +1770,8 @@ RCT_EXPORT_METHOD(writePhotoToPath:(nonnull NSString *)path resolver:(RCTPromise
1411
1770
 
1412
1771
  dispatch_async(dispatch_get_main_queue(), ^{
1413
1772
  UINavigationController* navigation = [[UINavigationController alloc] initWithRootViewController:controller];
1414
- UIViewController *viewController = (UIViewController*)[[[[UIApplication sharedApplication] delegate] window] rootViewController];
1415
-
1416
- //navigation.navigationBar.titleTextAttributes = @{NSForegroundColorAttributeName : [UIColor redColor]};
1417
-
1418
- while (viewController.presentedViewController)
1419
- {
1420
- viewController = viewController.presentedViewController;
1421
- }
1422
- [viewController presentViewController:navigation animated:YES completion:nil];
1773
+ UIViewController *presentingViewController = RCTPresentedViewController();
1774
+ [presentingViewController presentViewController:navigation animated:YES completion:nil];
1423
1775
  [controller presentViewController:alert animated:YES completion:nil];
1424
1776
 
1425
1777
  self->updateContactPromise = resolve;
@@ -1440,37 +1792,33 @@ RCT_EXPORT_METHOD(writePhotoToPath:(nonnull NSString *)path resolver:(RCTPromise
1440
1792
  }
1441
1793
 
1442
1794
 
1443
- - (void)getAllWithoutPhotos:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
1795
+ - (void)getAllWithoutPhotos:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
1444
1796
  [self getAllContacts:resolve reject:reject withThumbnails:false];
1445
1797
  }
1446
1798
 
1447
1799
 
1448
- - (void)getContactById:(nonnull NSString *)recordID resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
1449
- CNContactStore* contactStore = [self contactsStore:reject];
1450
- if(!contactStore)
1451
- return;
1800
+ - (void)getContactById:(nonnull NSString *)recordID resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
1801
+ CNContactStore *contactStore = [self contactsStore:reject];
1802
+ if (!contactStore)
1803
+ return;
1452
1804
 
1453
- CNEntityType entityType = CNEntityTypeContacts;
1454
- if([CNContactStore authorizationStatusForEntityType:entityType] == CNAuthorizationStatusNotDetermined)
1455
- {
1456
- [contactStore requestAccessForEntityType:entityType completionHandler:^(BOOL granted, NSError * _Nullable error) {
1457
- if(granted){
1458
- resolve([self getContact:recordID addressBook:contactStore withThumbnails:false]);
1459
- }
1460
- }];
1461
- }
1462
- else if([CNContactStore authorizationStatusForEntityType:entityType]== CNAuthorizationStatusAuthorized)
1463
- {
1464
- resolve([self getContact:recordID addressBook:contactStore withThumbnails:false]);
1465
- }
1466
- else if(@available(iOS 18, *))
1467
- {
1468
- if([CNContactStore authorizationStatusForEntityType:entityType] == CNAuthorizationStatusLimited)
1469
- {
1470
- resolve([self getContact:recordID addressBook:contactStore withThumbnails:false]);
1805
+ CNEntityType entityType = CNEntityTypeContacts;
1806
+ CNAuthorizationStatus authorizationStatus = [CNContactStore authorizationStatusForEntityType:entityType];
1807
+
1808
+ if (authorizationStatus == CNAuthorizationStatusNotDetermined) {
1809
+ [contactStore requestAccessForEntityType:entityType completionHandler:^(BOOL granted, NSError * _Nullable error) {
1810
+ if (granted) {
1811
+ resolve([self getContact:recordID addressBook:contactStore withThumbnails:NO]);
1812
+ }
1813
+ }];
1814
+ } else if (authorizationStatus == CNAuthorizationStatusAuthorized) {
1815
+ resolve([self getContact:recordID addressBook:contactStore withThumbnails:NO]);
1816
+ } else if (@available(iOS 18, *)) {
1817
+ if (authorizationStatus == CNAuthorizationStatusRestricted) {
1818
+ resolve([self getContact:recordID addressBook:contactStore withThumbnails:NO]);
1471
1819
  }
1472
- }
1473
- }
1820
+ }
1821
+ }
1474
1822
 
1475
1823
 
1476
1824
  - (void)getContactsByEmailAddress:(NSString *)string resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
@@ -1497,39 +1845,35 @@ RCT_EXPORT_METHOD(writePhotoToPath:(nonnull NSString *)path resolver:(RCTPromise
1497
1845
  }
1498
1846
 
1499
1847
 
1500
- - (void)getCount:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
1848
+ - (void)getCount:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
1501
1849
  [self getAllContactsCount:resolve reject:reject];
1502
1850
  }
1503
1851
 
1504
1852
 
1505
- - (void)getPhotoForId:(nonnull NSString *)recordID resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
1506
- CNContactStore* contactStore = [self contactsStore:reject];
1507
- if(!contactStore)
1508
- return;
1853
+ - (void)getPhotoForId:(nonnull NSString *)recordID resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
1854
+ CNContactStore *contactStore = [self contactsStore:reject];
1855
+ if (!contactStore)
1856
+ return;
1509
1857
 
1510
- CNEntityType entityType = CNEntityTypeContacts;
1511
- if([CNContactStore authorizationStatusForEntityType:entityType] == CNAuthorizationStatusNotDetermined)
1512
- {
1513
- [contactStore requestAccessForEntityType:entityType completionHandler:^(BOOL granted, NSError * _Nullable error) {
1514
- if(granted){
1515
- resolve([self getFilePathForThumbnailImage:recordID addressBook:contactStore]);
1516
- }
1517
- }];
1518
- }
1519
- else if([CNContactStore authorizationStatusForEntityType:entityType]== CNAuthorizationStatusAuthorized)
1520
- {
1521
- resolve([self getFilePathForThumbnailImage:recordID addressBook:contactStore]);
1522
- }
1523
- else if(@available(iOS 18, *))
1524
- {
1525
- if([CNContactStore authorizationStatusForEntityType:entityType] == CNAuthorizationStatusLimited)
1526
- {
1858
+ CNEntityType entityType = CNEntityTypeContacts;
1859
+ CNAuthorizationStatus authStatus = [CNContactStore authorizationStatusForEntityType:entityType];
1860
+
1861
+ if (authStatus == CNAuthorizationStatusNotDetermined) {
1862
+ [contactStore requestAccessForEntityType:entityType completionHandler:^(BOOL granted, NSError * _Nullable error) {
1863
+ if (granted) {
1527
1864
  resolve([self getFilePathForThumbnailImage:recordID addressBook:contactStore]);
1528
1865
  }
1529
-
1866
+ }];
1867
+ } else if (authStatus == CNAuthorizationStatusAuthorized) {
1868
+ resolve([self getFilePathForThumbnailImage:recordID addressBook:contactStore]);
1869
+ } else if (@available(iOS 18, *)) {
1870
+ if (authStatus == CNAuthorizationStatusRestricted) {
1871
+ resolve([self getFilePathForThumbnailImage:recordID addressBook:contactStore]);
1530
1872
  }
1531
- }
1532
-
1873
+ } else {
1874
+ reject(@"CONTACT_ACCESS_DENIED", @"Contact access is not authorized.", nil);
1875
+ }
1876
+ }
1533
1877
 
1534
1878
  - (void)openContactForm:(NSDictionary *)contactData resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
1535
1879
  CNMutableContact * contact = [[CNMutableContact alloc] init];
@@ -1543,12 +1887,8 @@ RCT_EXPORT_METHOD(writePhotoToPath:(nonnull NSString *)path resolver:(RCTPromise
1543
1887
 
1544
1888
  dispatch_async(dispatch_get_main_queue(), ^{
1545
1889
  UINavigationController* navigation = [[UINavigationController alloc] initWithRootViewController:controller];
1546
- UIViewController *viewController = (UIViewController*)[[[[UIApplication sharedApplication] delegate] window] rootViewController];
1547
- while (viewController.presentedViewController)
1548
- {
1549
- viewController = viewController.presentedViewController;
1550
- }
1551
- [viewController presentViewController:navigation animated:YES completion:nil];
1890
+ UIViewController *presentingViewController = RCTPresentedViewController();
1891
+ [presentingViewController presentViewController:navigation animated:YES completion:nil];
1552
1892
 
1553
1893
  self->updateContactPromise = resolve;
1554
1894
  });
@@ -1641,7 +1981,7 @@ RCT_EXPORT_METHOD(writePhotoToPath:(nonnull NSString *)path resolver:(RCTPromise
1641
1981
  }
1642
1982
 
1643
1983
 
1644
- - (void)requestPermission:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
1984
+ - (void)requestPermission:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
1645
1985
  CNContactStore* contactStore = [[CNContactStore alloc] init];
1646
1986
 
1647
1987
  [contactStore requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {
@@ -1724,15 +2064,8 @@ RCT_EXPORT_METHOD(writePhotoToPath:(nonnull NSString *)path resolver:(RCTPromise
1724
2064
 
1725
2065
  dispatch_async(dispatch_get_main_queue(), ^{
1726
2066
  UINavigationController* navigation = [[UINavigationController alloc] initWithRootViewController:contactViewController];
1727
-
1728
- UIViewController *currentViewController = [UIApplication sharedApplication].keyWindow.rootViewController;
1729
-
1730
- while (currentViewController.presentedViewController)
1731
- {
1732
- currentViewController = currentViewController.presentedViewController;
1733
- }
1734
-
1735
- [currentViewController presentViewController:navigation animated:YES completion:nil];
2067
+ UIViewController *presentingViewController = RCTPresentedViewController();
2068
+ [presentingViewController presentViewController:navigation animated:YES completion:nil];
1736
2069
 
1737
2070
  updateContactPromise = resolve;
1738
2071
  });
@@ -1744,7 +2077,7 @@ RCT_EXPORT_METHOD(writePhotoToPath:(nonnull NSString *)path resolver:(RCTPromise
1744
2077
  }
1745
2078
 
1746
2079
 
1747
- - (void)writePhotoToPath:(NSString *)contactId file:(NSString *)file resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
2080
+ - (void)writePhotoToPath:(NSString *)contactId file:(NSString *)file resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
1748
2081
  reject(@"Error", @"not implemented", nil);
1749
2082
  }
1750
2083
 
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "type": "git",
5
5
  "url": "https://github.com/rt2zz/react-native-contacts.git"
6
6
  },
7
- "version": "8.0.4",
7
+ "version": "8.0.6",
8
8
  "description": "React Native Contacts (android & ios)",
9
9
  "nativePackage": true,
10
10
  "keywords": [
@@ -30,7 +30,7 @@
30
30
  },
31
31
  "homepage": "https://github.com/rt2zz/react-native-contacts",
32
32
  "main": "index.ts",
33
- "types": "index.ts",
33
+ "types": "index.d.ts",
34
34
  "scripts": {
35
35
  "test": "echo \"Error: no test specified\" && exit 1"
36
36
  },
@@ -19,20 +19,6 @@ Pod::Spec.new do |s|
19
19
  s.source_files = "ios/**/*.{h,m,mm,swift}"
20
20
  s.frameworks = 'Contacts', 'ContactsUI', 'Photos'
21
21
 
22
- s.dependency 'React-Core'
22
+ install_modules_dependencies(s)
23
23
 
24
- if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then
25
- s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1"
26
- s.pod_target_xcconfig = {
27
- "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"",
28
- "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1",
29
- "CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
30
- }
31
- s.dependency "React-Codegen"
32
- s.dependency "RCT-Folly", folly_version
33
- s.dependency "RCTRequired"
34
- s.dependency "RCTTypeSafety"
35
- s.dependency "ReactCommon/turbomodule/core"
36
- install_modules_dependencies(s)
37
- end
38
24
  end
@@ -1,6 +1,6 @@
1
1
  import type { TurboModule } from "react-native/Libraries/TurboModule/RCTExport";
2
2
  import { TurboModuleRegistry } from "react-native";
3
- import { Contact, PermissionType } from "../type";
3
+ import { Contact, Group, PermissionType } from "../type";
4
4
 
5
5
  export interface Spec extends TurboModule {
6
6
  getAll: () => Promise<any>;
@@ -22,6 +22,20 @@ export interface Spec extends TurboModule {
22
22
  requestPermission: () => Promise<PermissionType>;
23
23
  writePhotoToPath: (contactId: string, file: string) => Promise<boolean>;
24
24
  iosEnableNotesUsage: (enabled: boolean) => void;
25
+ getGroups(): Promise<Group[]>;
26
+ getGroup: (identifier: string) => Promise<Group | null>;
27
+ deleteGroup(identifier: string): Promise<boolean>;
28
+ updateGroup(identifier: string, groupData: Object): Promise<Group>;
29
+ addGroup(group: Object): Promise<Group>;
30
+ contactsInGroup(identifier: string): Promise<Contact[]>;
31
+ addContactsToGroup(
32
+ groupIdentifier: string,
33
+ contactIdentifiers: string[]
34
+ ): Promise<boolean>;
35
+ removeContactsFromGroup(
36
+ groupIdentifier: string,
37
+ contactIdentifiers: string[]
38
+ ): Promise<boolean>;
25
39
  }
26
40
 
27
41
  export default TurboModuleRegistry.get<Spec>("RCTContacts");
package/type.ts CHANGED
@@ -61,4 +61,9 @@ export interface Contact {
61
61
  note: string;
62
62
  }
63
63
 
64
- export type PermissionType = 'authorized' | 'limited' | 'denied' | 'undefined'
64
+ export interface Group {
65
+ identifier: string;
66
+ name: string;
67
+ }
68
+
69
+ export type PermissionType = "authorized" | "limited" | "denied" | "undefined";