Skip to content

Commit

Permalink
chore: setup and base code
Browse files Browse the repository at this point in the history
  • Loading branch information
Ikecruz committed Jan 31, 2024
1 parent be3f39e commit e8c66b7
Show file tree
Hide file tree
Showing 12 changed files with 13,595 additions and 73 deletions.
1 change: 1 addition & 0 deletions android/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ ContactSelect_minSdkVersion=21
ContactSelect_targetSdkVersion=31
ContactSelect_compileSdkVersion=31
ContactSelect_ndkversion=21.4.7075529
org.gradle.java.home=/usr/lib/jvm/java-17-openjdk
83 changes: 64 additions & 19 deletions android/src/main/java/com/contactselect/ContactSelectModule.java
Original file line number Diff line number Diff line change
@@ -1,32 +1,77 @@
package com.contactselect;

import android.content.pm.PackageManager;
import android.app.Activity;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import androidx.annotation.NonNull;

import com.facebook.react.bridge.Promise;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import com.facebook.react.bridge.ActivityEventListener;
import com.facebook.react.bridge.BaseActivityEventListener;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.module.annotations.ReactModule;
import android.provider.ContactsContract;

@ReactModule(name = ContactSelectModule.NAME)
public class ContactSelectModule extends ReactContextBaseJavaModule {
public static final String NAME = "ContactSelect";
private static final int CONTACT_REQUEST_CODE = 1;
private Callback contactSelectionCallback;

private final ActivityEventListener activityEventListener = new BaseActivityEventListener() {
@Override
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
if (requestCode == CONTACT_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) {
Uri contactUri = data.getData();
if (contactUri != null) {
String[] projection = {ContactsContract.CommonDataKinds.Phone.NUMBER};
Cursor cursor = activity.getContentResolver().query(contactUri, projection, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
String phoneNumber = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
contactSelectionCallback.invoke(null, phoneNumber);
cursor.close();
} else {
contactSelectionCallback.invoke("Failed to retrieve contact");
}
} else {
contactSelectionCallback.invoke("Failed to retrieve contact");
}
} else {
contactSelectionCallback.invoke("Contact selection canceled");
}
}
}
};

public ContactSelectModule(ReactApplicationContext reactContext) {
super(reactContext);
reactContext.addActivityEventListener(activityEventListener);
}

public ContactSelectModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@NonNull
@Override
public String getName() {
return "ContactSelectModule";
}

@Override
@NonNull
public String getName() {
return NAME;
}
@ReactMethod
public void pickContact(Callback callback) {
contactSelectionCallback = callback;

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
ContextCompat.checkSelfPermission(getReactApplicationContext(), android.Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(getCurrentActivity(), new String[]{android.Manifest.permission.READ_CONTACTS}, CONTACT_REQUEST_CODE);
} else {
openContactPicker();
}
}

// Example method
// See https://reactnative.dev/docs/native-modules-android
@ReactMethod
public void multiply(double a, double b, Promise promise) {
promise.resolve(a * b);
}
private void openContactPicker() {
Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI);
getCurrentActivity().startActivityForResult(intent, CONTACT_REQUEST_CODE);
}
}
26 changes: 11 additions & 15 deletions android/src/main/java/com/contactselect/ContactSelectPackage.java
Original file line number Diff line number Diff line change
@@ -1,28 +1,24 @@
package com.contactselect;

import androidx.annotation.NonNull;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.uimanager.ViewManager;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ContactSelectPackage implements ReactPackage {
@NonNull
@Override
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new ContactSelectModule(reactContext));
return modules;
}
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new ContactSelectModule(reactContext));
return modules;
}

@NonNull
@Override
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
33 changes: 33 additions & 0 deletions android/src/main/java/com/contactselect/MainApplication.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.contactselect; // Adjust with your actual package name

import android.app.Application;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;

import java.util.Arrays;
import java.util.List;

public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}

@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new ContactSelectPackage() // Add this line
// Add other packages here if needed
);
}
};

@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
}
42 changes: 19 additions & 23 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,27 @@
import * as React from 'react';
import React from 'react';
import { View, Text, Button } from 'react-native';
import ContactModule from 'react-native-contact-select'; // Adjust the path accordingly

import { StyleSheet, View, Text } from 'react-native';
import { multiply } from 'react-native-contact-select';
function ContactSelector() {
const handleContactSelection = (error: any, phoneNumber: any) => {
if (error) {
console.error(error);
} else {
console.log('Selected phone number:', phoneNumber);
// Do something with the selected contact information
}
};

export default function App() {
const [result, setResult] = React.useState<number | undefined>();

React.useEffect(() => {
multiply(3, 7).then(setResult);
}, []);
const pickContact = () => {
ContactModule.pickContact(handleContactSelection);
};

return (
<View style={styles.container}>
<Text>Result: {result}</Text>
<View>
<Text>Contact Selector</Text>
<Button title="Pick Contact" onPress={pickContact} />
</View>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
box: {
width: 60,
height: 60,
marginVertical: 20,
},
});
export default ContactSelector;
5 changes: 3 additions & 2 deletions ios/ContactSelect-Bridging-Header.h
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
#import <React/RCTBridgeModule.h>
#import <React/RCTViewManager.h>
#import "React/RCTBridgeModule.h"
#import "React/RCTEventEmitter.h"
#import "ContactSelect-Swift.h"
37 changes: 30 additions & 7 deletions ios/ContactSelect.mm
Original file line number Diff line number Diff line change
@@ -1,14 +1,37 @@
#import <React/RCTBridgeModule.h>
#import "ContactSelect-Bridging-Header.h"

@interface RCT_EXTERN_MODULE(ContactSelect, NSObject)
@implementation ContactSelect

RCT_EXTERN_METHOD(multiply:(float)a withB:(float)b
withResolver:(RCTPromiseResolveBlock)resolve
withRejecter:(RCTPromiseRejectBlock)reject)
RCT_EXPORT_MODULE();

+ (BOOL)requiresMainQueueSetup
RCT_EXPORT_METHOD(pickContact:(RCTResponseSenderBlock)callback)
{
return NO;
dispatch_async(dispatch_get_main_queue(), ^{
CNContactPickerViewController *contactPicker = [[CNContactPickerViewController alloc] init];
contactPicker.delegate = self;

UIViewController *rootViewController = [UIApplication sharedApplication].delegate.window.rootViewController;
[rootViewController presentViewController:contactPicker animated:YES completion:nil];

// Save the callback for later use
self.contactSelectionCallback = callback;
});
}

- (void)contactPicker:(CNContactPickerViewController *)picker didSelectContact:(CNContact *)contact
{
NSString *phoneNumber = @"";
for (CNLabeledValue<CNPhoneNumber *> *phone in contact.phoneNumbers) {
phoneNumber = [phone.value stringValue];
break; // Use the first phone number, you can modify this based on your requirements
}

[self.contactSelectionCallback @[[NSNull null], phoneNumber]];
}

- (void)contactPickerDidCancel:(CNContactPickerViewController *)picker
{
[self.contactSelectionCallback @[@"Contact selection canceled"]];
}

@end
48 changes: 44 additions & 4 deletions ios/ContactSelect.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,48 @@
import Foundation
import Contacts
import React

@objc(ContactSelect)
class ContactSelect: NSObject {
class ContactSelect: NSObject, RCTBridgeModule, CNContactPickerDelegate {

var contactSelectionCallback: RCTResponseSenderBlock?

static func moduleName() -> String {
return "ContactSelect"
}

@objc func pickContact(_ callback: @escaping RCTResponseSenderBlock) {
DispatchQueue.main.async {
let contactPicker = CNContactPickerViewController()
contactPicker.delegate = self

guard let rootViewController = UIApplication.shared.keyWindow?.rootViewController else {
callback(["Failed to present contact picker"])
return
}

rootViewController.present(contactPicker, animated: true, completion: nil)

// Save the callback for later use
self.contactSelectionCallback = callback
}
}

func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) {
var phoneNumber = ""
for phone in contact.phoneNumbers {
phoneNumber = phone.value.stringValue
break // Use the first phone number, you can modify this based on your requirements
}

self.contactSelectionCallback?([NSNull(), phoneNumber])
}

func contactPickerDidCancel(_ picker: CNContactPickerViewController) {
self.contactSelectionCallback?(["Contact selection canceled"])
}

@objc(multiply:withB:withResolver:withRejecter:)
func multiply(a: Float, b: Float, resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void {
resolve(a*b)
@objc func constantsToExport() -> [AnyHashable: Any] {
return [:]
}
}
34 changes: 34 additions & 0 deletions ios/Podfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
platform :ios, '9.0'

use_frameworks!

target 'react-native-contact-select' do
pod 'React', :path => '../node_modules/react-native', :subspecs => [
'Core',
'CxxBridge',
'DevSupport',
'RCTText',
'RCTNetwork',
'RCTWebSocket',
'RCTLinkingIOS',
'RCTSettings',
]

pod 'React/RCTImage'
pod 'yoga', :path => '../node_modules/react-native/ReactCommon/yoga'

pod 'React/Core', :path => '../node_modules/react-native/React'
pod 'React/RCTActionSheet', :path => '../node_modules/react-native/Libraries/ActionSheetIOS'
pod 'React/RCTAnimation', :path => '../node_modules/react-native/Libraries/NativeAnimation'
pod 'React/RCTBlob', :path => '../node_modules/react-native/Libraries/Blob'
pod 'React/RCTCameraRoll', :path => '../node_modules/react-native/Libraries/CameraRoll'
pod 'React/RCTGeolocation', :path => '../node_modules/react-native/Libraries/Geolocation'
pod 'React/RCTImage', :path => '../node_modules/react-native/Libraries/Image'
pod 'React/RCTNetwork', :path => '../node_modules/react-native/Libraries/Network'
pod 'React/RCTSettings', :path => '../node_modules/react-native/Libraries/Settings'
pod 'React/RCTText', :path => '../node_modules/react-native/Libraries/Text'
pod 'React/RCTVibration', :path => '../node_modules/react-native/Libraries/Vibration'
pod 'React/RCTWebSocket', :path => '../node_modules/react-native/Libraries/WebSocket'

pod 'ContactSelect', :path => './'
end
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -161,5 +161,9 @@
}
]
]
},
"dependencies": {
"expo-contacts": "^12.8.2",
"expo-permissions": "^14.4.0"
}
}
10 changes: 7 additions & 3 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ const ContactSelect = NativeModules.ContactSelect
}
);

export function multiply(a: number, b: number): Promise<number> {
return ContactSelect.multiply(a, b);
}
export default {
pickContact: (
callback: (error: string | null, phoneNumber?: string) => void
) => {
ContactSelect.pickContact(callback);
},
};
Loading

0 comments on commit e8c66b7

Please sign in to comment.