react-native-contacts
Advanced tools
| import React, { Component } from "react"; | ||
| import { Image, View, Text, StyleSheet } from "react-native"; | ||
| import PropTypes from "prop-types"; | ||
| class Avatar extends Component { | ||
| static propTypes = { | ||
| img: Image.propTypes.source, | ||
| placeholder: PropTypes.string, | ||
| width: PropTypes.number.isRequired, | ||
| height: PropTypes.number.isRequired, | ||
| roundedImage: PropTypes.bool, | ||
| roundedPlaceholder: PropTypes.bool | ||
| }; | ||
| static defaultProps = { | ||
| roundedImage: true, | ||
| roundedPlaceholder: true | ||
| }; | ||
| renderImage = () => { | ||
| const { img, width, height, roundedImage } = this.props; | ||
| const { imageContainer, image } = styles; | ||
| const viewStyle = [imageContainer]; | ||
| if (roundedImage) | ||
| viewStyle.push({ borderRadius: Math.round(width + height) / 2 }); | ||
| return ( | ||
| <View style={viewStyle}> | ||
| <Image style={image} source={img} /> | ||
| </View> | ||
| ); | ||
| }; | ||
| renderPlaceholder = () => { | ||
| const { placeholder, width, height, roundedPlaceholder } = this.props; | ||
| const { placeholderContainer, placeholderText } = styles; | ||
| const viewStyle = [placeholderContainer]; | ||
| if (roundedPlaceholder) | ||
| viewStyle.push({ borderRadius: Math.round(width + height) / 2 }); | ||
| return ( | ||
| <View style={viewStyle}> | ||
| <View style={viewStyle}> | ||
| <Text | ||
| adjustsFontSizeToFit | ||
| numberOfLines={1} | ||
| minimumFontScale={0.01} | ||
| style={[{ fontSize: Math.round(width) / 2 }, placeholderText]} | ||
| > | ||
| {placeholder} | ||
| </Text> | ||
| </View> | ||
| </View> | ||
| ); | ||
| }; | ||
| render() { | ||
| const { img, width, height } = this.props; | ||
| const { container } = styles; | ||
| return ( | ||
| <View style={[container, this.props.style, { width, height }]}> | ||
| {img ? this.renderImage() : this.renderPlaceholder()} | ||
| </View> | ||
| ); | ||
| } | ||
| } | ||
| const styles = StyleSheet.create({ | ||
| container: { | ||
| width: "100%" | ||
| }, | ||
| imageContainer: { | ||
| overflow: "hidden", | ||
| justifyContent: "center", | ||
| height: "100%" | ||
| }, | ||
| image: { | ||
| flex: 1, | ||
| alignSelf: "stretch", | ||
| width: undefined, | ||
| height: undefined | ||
| }, | ||
| placeholderContainer: { | ||
| alignItems: "center", | ||
| justifyContent: "center", | ||
| backgroundColor: "#dddddd", | ||
| height: "100%" | ||
| }, | ||
| placeholderText: { | ||
| fontWeight: "700", | ||
| color: "#ffffff" | ||
| } | ||
| }); | ||
| export default Avatar; |
| import React, { Component } from "react"; | ||
| import { | ||
| View, | ||
| TouchableHighlight, | ||
| Text, | ||
| StyleSheet, | ||
| Platform, | ||
| Animated | ||
| } from "react-native"; | ||
| import PropTypes from "prop-types"; | ||
| import { RectButton } from "react-native-gesture-handler"; | ||
| import Swipeable from "react-native-gesture-handler/Swipeable"; | ||
| class ListItem extends Component { | ||
| static propTypes = { | ||
| leftElement: PropTypes.element, | ||
| title: PropTypes.string, | ||
| description: PropTypes.string, | ||
| rightElement: PropTypes.element, | ||
| rightText: PropTypes.string, | ||
| onPress: PropTypes.func, | ||
| onDelete: PropTypes.func, | ||
| onLongPress: PropTypes.func, | ||
| disabled: PropTypes.bool | ||
| }; | ||
| renderRightAction = (iconName, color, x, progress) => { | ||
| const trans = progress.interpolate({ | ||
| inputRange: [0, 1], | ||
| outputRange: [x, 0] | ||
| }); | ||
| const pressHandler = () => { | ||
| const { onDelete } = this.props; | ||
| if (onDelete) onDelete(); | ||
| this.close(); | ||
| }; | ||
| return ( | ||
| <Animated.View style={{ flex: 1, transform: [{ translateX: trans }] }}> | ||
| <RectButton | ||
| style={[styles.rightAction, { backgroundColor: color }]} | ||
| onPress={pressHandler} | ||
| > | ||
| <Text style={{ color: "#fff" }}>Delete</Text> | ||
| </RectButton> | ||
| </Animated.View> | ||
| ); | ||
| }; | ||
| renderRightActions = progress => ( | ||
| <View style={{ width: 64, flexDirection: "row" }}> | ||
| {this.renderRightAction("trash", "#ef5350", 64, progress)} | ||
| </View> | ||
| ); | ||
| renderRightActions = progress => ( | ||
| <View style={{ width: 64, flexDirection: "row" }}> | ||
| {this.renderRightAction("trash", "#ef5350", 64, progress)} | ||
| </View> | ||
| ); | ||
| updateRef = ref => { | ||
| this.swipeableRow = ref; | ||
| }; | ||
| close = () => { | ||
| this.swipeableRow.close(); | ||
| }; | ||
| render() { | ||
| const { | ||
| leftElement, | ||
| title, | ||
| description, | ||
| rightElement, | ||
| rightText, | ||
| onPress, | ||
| onLongPress, | ||
| disabled | ||
| } = this.props; | ||
| const Component = onPress || onLongPress ? TouchableHighlight : View; | ||
| const { | ||
| itemContainer, | ||
| leftElementContainer, | ||
| rightSectionContainer, | ||
| mainTitleContainer, | ||
| rightElementContainer, | ||
| rightTextContainer, | ||
| titleStyle, | ||
| descriptionStyle | ||
| } = styles; | ||
| return ( | ||
| <Swipeable | ||
| ref={this.updateRef} | ||
| friction={1} | ||
| renderRightActions={this.renderRightActions} | ||
| > | ||
| <Component | ||
| onPress={onPress} | ||
| onLongPress={onLongPress} | ||
| disabled={disabled} | ||
| underlayColor="#f2f3f5" | ||
| > | ||
| <View style={itemContainer}> | ||
| {leftElement ? ( | ||
| <View style={leftElementContainer}>{leftElement}</View> | ||
| ) : ( | ||
| <View /> | ||
| )} | ||
| <View style={rightSectionContainer}> | ||
| <View style={mainTitleContainer}> | ||
| <Text style={titleStyle}>{title}</Text> | ||
| {description ? ( | ||
| <Text style={descriptionStyle}>{description}</Text> | ||
| ) : ( | ||
| <View /> | ||
| )} | ||
| </View> | ||
| <View style={rightTextContainer}> | ||
| {rightText ? <Text>{rightText}</Text> : <View />} | ||
| </View> | ||
| {rightElement ? ( | ||
| <View style={rightElementContainer}>{rightElement}</View> | ||
| ) : ( | ||
| <View /> | ||
| )} | ||
| </View> | ||
| </View> | ||
| </Component> | ||
| </Swipeable> | ||
| ); | ||
| } | ||
| } | ||
| const styles = StyleSheet.create({ | ||
| itemContainer: { | ||
| flexDirection: "row", | ||
| minHeight: 44, | ||
| height: 63 | ||
| }, | ||
| leftElementContainer: { | ||
| justifyContent: "center", | ||
| alignItems: "center", | ||
| flex: 2, | ||
| paddingLeft: 13 | ||
| }, | ||
| rightSectionContainer: { | ||
| marginLeft: 18, | ||
| flexDirection: "row", | ||
| flex: 20, | ||
| borderBottomWidth: StyleSheet.hairlineWidth, | ||
| borderColor: "#515151" | ||
| }, | ||
| mainTitleContainer: { | ||
| justifyContent: "center", | ||
| flexDirection: "column", | ||
| flex: 1 | ||
| }, | ||
| rightElementContainer: { | ||
| justifyContent: "center", | ||
| alignItems: "center", | ||
| flex: 0.4 | ||
| }, | ||
| rightTextContainer: { | ||
| justifyContent: "center", | ||
| marginRight: 10 | ||
| }, | ||
| titleStyle: { | ||
| fontSize: 16 | ||
| }, | ||
| descriptionStyle: { | ||
| fontSize: 14, | ||
| color: "#515151" | ||
| }, | ||
| rightAction: { | ||
| alignItems: "center", | ||
| flex: 1, | ||
| justifyContent: "center" | ||
| } | ||
| }); | ||
| export default ListItem; |
| import React, { Component } from "react"; | ||
| import PropTypes from "prop-types"; | ||
| import { | ||
| View, | ||
| TextInput, | ||
| UIManager, | ||
| LayoutAnimation, | ||
| Animated, | ||
| ActivityIndicator, | ||
| TouchableOpacity, | ||
| TouchableWithoutFeedback, | ||
| Text, | ||
| StyleSheet | ||
| } from "react-native"; | ||
| class SearchBar extends Component { | ||
| static propTypes = { | ||
| searchPlaceholder: PropTypes.string, | ||
| onClear: PropTypes.func, | ||
| onFocus: PropTypes.func, | ||
| onBlur: PropTypes.func, | ||
| onChangeText: PropTypes.func | ||
| }; | ||
| static defaultProps = { | ||
| searchPlaceholder: "Search", | ||
| onClear: () => null, | ||
| onFocus: () => null, | ||
| onBlur: () => null, | ||
| onChangeText: () => null | ||
| }; | ||
| constructor(props) { | ||
| super(props); | ||
| this.state = { | ||
| hasFocus: false, | ||
| isEmpty: true, | ||
| showLoader: false | ||
| }; | ||
| } | ||
| focus = () => { | ||
| this.input.focus(); | ||
| }; | ||
| blur = () => { | ||
| this.input.blur(); | ||
| }; | ||
| clear = () => { | ||
| this.input.clear(); | ||
| this.onChangeText(""); | ||
| this.props.onClear(); | ||
| }; | ||
| cancel = () => { | ||
| this.blur(); | ||
| }; | ||
| showLoader = () => { | ||
| this.setState({ | ||
| showLoader: true | ||
| }); | ||
| }; | ||
| hideLoader = () => { | ||
| this.setState({ | ||
| showLoader: false | ||
| }); | ||
| }; | ||
| onFocus = () => { | ||
| this.props.onFocus(); | ||
| if (UIManager.configureNextLayoutAnimation) LayoutAnimation.easeInEaseOut(); | ||
| this.setState({ | ||
| hasFocus: true | ||
| }); | ||
| }; | ||
| onBlur = () => { | ||
| this.props.onBlur(); | ||
| if (UIManager.configureNextLayoutAnimation) LayoutAnimation.easeInEaseOut(); | ||
| this.setState({ | ||
| hasFocus: false | ||
| }); | ||
| }; | ||
| onChangeText = text => { | ||
| this.props.onChangeText(text); | ||
| this.setState({ isEmpty: text === "" }); | ||
| }; | ||
| render() { | ||
| const { | ||
| container, | ||
| inputStyle, | ||
| leftIconStyle, | ||
| rightContainer, | ||
| rightIconStyle, | ||
| activityIndicator | ||
| } = styles; | ||
| const { searchPlaceholder, style } = this.props; | ||
| const { hasFocus, isEmpty, showLoader } = this.state; | ||
| const inputStyleCollection = [inputStyle]; | ||
| if (hasFocus) inputStyleCollection.push({ flex: 1 }); | ||
| return ( | ||
| <TouchableWithoutFeedback onPress={this.focus} style={style}> | ||
| <Animated.View style={container}> | ||
| <View style={leftIconStyle}> | ||
| <Text>🔍</Text> | ||
| </View> | ||
| <TextInput | ||
| onFocus={this.onFocus} | ||
| onBlur={this.onBlur} | ||
| onChangeText={this.onChangeText} | ||
| placeholder={searchPlaceholder} | ||
| style={inputStyleCollection} | ||
| placeholderTextColor="#515151" | ||
| autoCorrect={false} | ||
| ref={ref => { | ||
| this.input = ref; | ||
| }} | ||
| /> | ||
| <View style={rightContainer}> | ||
| {hasFocus && showLoader ? ( | ||
| <ActivityIndicator | ||
| key="loading" | ||
| style={activityIndicator} | ||
| color="#515151" | ||
| /> | ||
| ) : ( | ||
| <View /> | ||
| )} | ||
| {hasFocus && !isEmpty ? ( | ||
| <TouchableOpacity onPress={this.clear}> | ||
| <View style={rightIconStyle}> | ||
| <Text>ⅹ</Text> | ||
| </View> | ||
| </TouchableOpacity> | ||
| ) : ( | ||
| <View /> | ||
| )} | ||
| </View> | ||
| </Animated.View> | ||
| </TouchableWithoutFeedback> | ||
| ); | ||
| } | ||
| } | ||
| const styles = StyleSheet.create({ | ||
| container: { | ||
| height: 40, | ||
| borderRadius: 5, | ||
| backgroundColor: "#ddd", | ||
| marginLeft: 10, | ||
| marginRight: 10, | ||
| marginBottom: 5, | ||
| marginTop: 5, | ||
| flexDirection: "row", | ||
| alignItems: "center", | ||
| justifyContent: "center" | ||
| }, | ||
| inputStyle: { | ||
| alignSelf: "center", | ||
| marginLeft: 5, | ||
| height: 40, | ||
| fontSize: 14 | ||
| }, | ||
| leftIconStyle: { | ||
| height: 30, | ||
| justifyContent: "center", | ||
| alignItems: "center", | ||
| marginLeft: 8 | ||
| }, | ||
| rightContainer: { | ||
| flexDirection: "row" | ||
| }, | ||
| rightIconStyle: { | ||
| height: 30, | ||
| justifyContent: "center", | ||
| alignItems: "center", | ||
| marginRight: 8 | ||
| }, | ||
| activityIndicator: { | ||
| marginRight: 5 | ||
| } | ||
| }); | ||
| export default SearchBar; |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
@@ -110,18 +110,35 @@ package com.rt2zz.reactnativecontacts; | ||
| /* | ||
| * Returns all contacts matching string | ||
| /** | ||
| * Retrieves contacts matching String. | ||
| * Uses raw URI when <code>rawUri</code> is <code>true</code>, makes assets copy otherwise. | ||
| * | ||
| * @param searchString String to match | ||
| * @param callback user provided callback to run at completion | ||
| */ | ||
| @ReactMethod | ||
| public void getContactsMatchingString(final String searchString, final Callback callback) { | ||
| getAllContactsMatchingString(searchString, callback); | ||
| AsyncTask<Void,Void,Void> myAsyncTask = new AsyncTask<Void,Void,Void>() { | ||
| @Override | ||
| protected Void doInBackground(final Void ... params) { | ||
| Context context = getReactApplicationContext(); | ||
| ContentResolver cr = context.getContentResolver(); | ||
| ContactsProvider contactsProvider = new ContactsProvider(cr); | ||
| WritableArray contacts = contactsProvider.getContactsMatchingString(searchString); | ||
| callback.invoke(null, contacts); | ||
| return null; | ||
| } | ||
| }; | ||
| myAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); | ||
| } | ||
| /** | ||
| * Retrieves contacts matching String. | ||
| * Retrieves contacts matching a phone number. | ||
| * Uses raw URI when <code>rawUri</code> is <code>true</code>, makes assets copy otherwise. | ||
| * | ||
| * @param searchString String to match | ||
| * @param phoneNumber phone number to match | ||
| * @param callback user provided callback to run at completion | ||
| */ | ||
| private void getAllContactsMatchingString(final String searchString, final Callback callback) { | ||
| @ReactMethod | ||
| public void getContactsByPhoneNumber(final String phoneNumber, final Callback callback) { | ||
| AsyncTask<Void,Void,Void> myAsyncTask = new AsyncTask<Void,Void,Void>() { | ||
@@ -133,3 +150,3 @@ @Override | ||
| ContactsProvider contactsProvider = new ContactsProvider(cr); | ||
| WritableArray contacts = contactsProvider.getContactsMatchingString(searchString); | ||
| WritableArray contacts = contactsProvider.getContactsByPhoneNumber(phoneNumber); | ||
@@ -136,0 +153,0 @@ callback.invoke(null, contacts); |
@@ -117,2 +117,31 @@ package com.rt2zz.reactnativecontacts; | ||
| public WritableArray getContactsByPhoneNumber(String phoneNumber) { | ||
| Map<String, Contact> matchingContacts; | ||
| { | ||
| Cursor cursor = contentResolver.query( | ||
| ContactsContract.Data.CONTENT_URI, | ||
| FULL_PROJECTION.toArray(new String[FULL_PROJECTION.size()]), | ||
| ContactsContract.CommonDataKinds.Phone.NUMBER + " LIKE ? OR " | ||
| + ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER + " LIKE ?", | ||
| new String[]{"%" + phoneNumber + "%", "%" + phoneNumber + "%"}, | ||
| null | ||
| ); | ||
| try { | ||
| matchingContacts = loadContactsFrom(cursor); | ||
| } finally { | ||
| if (cursor != null) { | ||
| cursor.close(); | ||
| } | ||
| } | ||
| } | ||
| WritableArray contacts = Arguments.createArray(); | ||
| for (Contact contact : matchingContacts.values()) { | ||
| contacts.pushMap(contact.toMap()); | ||
| } | ||
| return contacts; | ||
| } | ||
| public WritableMap getContactByRawId(String contactRawId) { | ||
@@ -119,0 +148,0 @@ |
+1
-1
@@ -9,3 +9,3 @@ # Contributing | ||
| * write your feature | ||
| * submit a feature test on https://github.com/morenoh149/react-native-contacts-test to test it's integration | ||
| * add example usage to the example app https://github.com/rt2zz/react-native-contacts/tree/master/example | ||
| * submit a pull request on this repo, and describe: | ||
@@ -12,0 +12,0 @@ * a brief description |
@@ -141,2 +141,3 @@ apply plugin: "com.android.application" | ||
| dependencies { | ||
| implementation project(':react-native-gesture-handler') | ||
| implementation project(':react-native-contacts') | ||
@@ -143,0 +144,0 @@ implementation fileTree(dir: "libs", include: ["*.jar"]) |
@@ -6,2 +6,3 @@ package com.example; | ||
| import com.facebook.react.ReactApplication; | ||
| import com.swmansion.gesturehandler.react.RNGestureHandlerPackage; | ||
| import com.rt2zz.reactnativecontacts.ReactNativeContacts; | ||
@@ -28,2 +29,3 @@ import com.facebook.react.ReactNativeHost; | ||
| new MainReactPackage(), | ||
| new RNGestureHandlerPackage(), | ||
| new ReactNativeContacts() | ||
@@ -30,0 +32,0 @@ ); |
@@ -19,1 +19,3 @@ # Project-wide Gradle settings. | ||
| # org.gradle.parallel=true | ||
| android.useAndroidX=true | ||
| android.enableJetifier=true |
| rootProject.name = 'example' | ||
| include ':react-native-gesture-handler' | ||
| project(':react-native-gesture-handler').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-gesture-handler/android') | ||
| include ':react-native-contacts' | ||
@@ -3,0 +5,0 @@ project(':react-native-contacts').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-contacts/android') |
+106
-42
@@ -9,18 +9,26 @@ /** | ||
| import React, {Component} from 'react'; | ||
| import {PermissionsAndroid, Platform, SafeAreaView, ScrollView, StyleSheet, Text, View} from 'react-native'; | ||
| import Contacts from 'react-native-contacts'; | ||
| import React, { Component } from "react"; | ||
| import { | ||
| PermissionsAndroid, | ||
| Platform, | ||
| SafeAreaView, | ||
| ScrollView, | ||
| StyleSheet, | ||
| Text, | ||
| View, | ||
| Image | ||
| } from "react-native"; | ||
| import Contacts from "react-native-contacts"; | ||
| const instructions = Platform.select({ | ||
| ios: 'Press Cmd+R to reload,\n' + 'Cmd+D or shake for dev menu', | ||
| android: | ||
| 'Double tap R on your keyboard to reload,\n' + | ||
| 'Shake or press menu button for dev menu', | ||
| }); | ||
| import ListItem from "./components/ListItem"; | ||
| import Avatar from "./components/Avatar"; | ||
| import SearchBar from "./components/SearchBar"; | ||
| type Props = {}; | ||
| export default class App extends Component<Props> { | ||
| constructor(props) { | ||
| super(props); | ||
| this.search = this.search.bind(this); | ||
| this.state = { | ||
@@ -32,12 +40,9 @@ contacts: [] | ||
| async componentWillMount() { | ||
| if (Platform.OS === 'android') { | ||
| PermissionsAndroid.request( | ||
| PermissionsAndroid.PERMISSIONS.READ_CONTACTS, | ||
| { | ||
| 'title': 'Contacts', | ||
| 'message': 'This app would like to view your contacts.' | ||
| } | ||
| ).then(() => { | ||
| if (Platform.OS === "android") { | ||
| PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.READ_CONTACTS, { | ||
| title: "Contacts", | ||
| message: "This app would like to view your contacts." | ||
| }).then(() => { | ||
| this.loadContacts(); | ||
| }) | ||
| }); | ||
| } else { | ||
@@ -50,18 +55,75 @@ this.loadContacts(); | ||
| Contacts.getAll((err, contacts) => { | ||
| if (err === 'denied'){ | ||
| console.warn('Permission to access contacts was denied'); | ||
| if (err === "denied") { | ||
| console.warn("Permission to access contacts was denied"); | ||
| } else { | ||
| this.setState({ contacts }); | ||
| } | ||
| }) | ||
| }); | ||
| } | ||
| search(text) { | ||
| const phoneNumberRegex = /\b[\+]?[(]?[0-9]{2,6}[)]?[-\s\.]?[-\s\/\.0-9]{3,15}\b/m; | ||
| if (text === "" || text === null) { | ||
| this.loadContacts(); | ||
| } else if (phoneNumberRegex.test(text)) { | ||
| Contacts.getContactsByPhoneNumber(text, (err, contacts) => { | ||
| this.setState({ contacts }); | ||
| }); | ||
| } else { | ||
| Contacts.getContactsMatchingString(text, (err, contacts) => { | ||
| this.setState({ contacts }); | ||
| }); | ||
| } | ||
| } | ||
| render() { | ||
| return ( | ||
| <SafeAreaView style={styles.container}> | ||
| <Text style={styles.welcome}>Welcome to React Native Contacts!</Text> | ||
| <ScrollView style={{flex: 1}}> | ||
| <Text style={styles.instructions}> | ||
| {JSON.stringify(this.state.contacts, null, '\t')} | ||
| </Text> | ||
| <View | ||
| style={{ | ||
| paddingLeft: 100, | ||
| paddingRight: 100, | ||
| justifyContent: "center", | ||
| alignItems: "center" | ||
| }} | ||
| > | ||
| <Image | ||
| source={require("./logo.png")} | ||
| style={{ | ||
| aspectRatio: 6, | ||
| resizeMode: "contain" | ||
| }} | ||
| /> | ||
| </View> | ||
| <SearchBar onChangeText={this.search} /> | ||
| <ScrollView style={{ flex: 1 }}> | ||
| {this.state.contacts.map(contact => { | ||
| return ( | ||
| <ListItem | ||
| leftElement={ | ||
| <Avatar | ||
| img={ | ||
| contact.hasThumbnail | ||
| ? { uri: contact.thumbnailPath } | ||
| : undefined | ||
| } | ||
| placeholder={getAvatarInitials( | ||
| `${contact.givenName} ${contact.familyName}` | ||
| )} | ||
| width={40} | ||
| height={40} | ||
| /> | ||
| } | ||
| key={contact.recordID} | ||
| title={`${contact.givenName} ${contact.familyName}`} | ||
| description={`${contact.company}`} | ||
| onPress={() => Contacts.openExistingContact(contact, () => {})} | ||
| onDelete={() => | ||
| Contacts.deleteContact(contact, () => { | ||
| this.loadContacts(); | ||
| }) | ||
| } | ||
| /> | ||
| ); | ||
| })} | ||
| </ScrollView> | ||
@@ -75,17 +137,19 @@ </SafeAreaView> | ||
| container: { | ||
| flex: 1, | ||
| justifyContent: 'center', | ||
| alignItems: 'center', | ||
| backgroundColor: '#F5FCFF', | ||
| }, | ||
| welcome: { | ||
| fontSize: 20, | ||
| textAlign: 'center', | ||
| margin: 10, | ||
| }, | ||
| instructions: { | ||
| textAlign: 'left', | ||
| color: '#333333', | ||
| marginBottom: 5, | ||
| }, | ||
| flex: 1 | ||
| } | ||
| }); | ||
| const getAvatarInitials = textString => { | ||
| if (!textString) return ""; | ||
| const text = textString.trim(); | ||
| const textSplit = text.split(" "); | ||
| if (textSplit.length <= 1) return text.charAt(0); | ||
| const initials = | ||
| textSplit[0].charAt(0) + textSplit[textSplit.length - 1].charAt(0); | ||
| return initials; | ||
| }; |
@@ -7,8 +7,11 @@ { | ||
| "start": "node node_modules/react-native/local-cli/cli.js start", | ||
| "test": "jest" | ||
| "test": "jest", | ||
| "postinstall": "yarn react-native-jetifier" | ||
| }, | ||
| "dependencies": { | ||
| "@jumpn/react-native-jetifier": "^0.1.4", | ||
| "react": "16.8.3", | ||
| "react-native": "0.59.5", | ||
| "react-native-contacts": "github:rt2zz/react-native-contacts" | ||
| "react-native-contacts": "https://github.com/rt2zz/react-native-contacts.git", | ||
| "react-native-gesture-handler": "^1.3.0" | ||
| }, | ||
@@ -15,0 +18,0 @@ "devDependencies": { |
@@ -1,2 +0,2 @@ | ||
| # react-native-device-info example project | ||
| # react-native-contacts example project | ||
@@ -3,0 +3,0 @@ ## Installation |
+2
-1
@@ -11,2 +11,3 @@ export function getAll(callback: (error: any, contacts: Contact[]) => void): void; | ||
| export function getContactsMatchingString(str: string, callback: (error: any, contacts: Contact[]) => void): void; | ||
| export function getContactsByPhoneNumber(phoneNumber: string, callback: (error: any, contacts: Contact[]) => void): void; | ||
| export function checkPermission(callback: (error: any, result: 'authorized' | 'denied' | 'undefined') => void): void; | ||
@@ -61,2 +62,2 @@ export function requestPermission(callback: (error: any, result: 'authorized' | 'denied' | 'undefined') => void): void; | ||
| birthday: Birthday; | ||
| } | ||
| } |
@@ -102,2 +102,41 @@ #import <AddressBook/AddressBook.h> | ||
| RCT_EXPORT_METHOD(getContactsByPhoneNumber:(NSString *)string callback:(RCTResponseSenderBlock) callback) | ||
| { | ||
| CNContactStore *contactStore = [[CNContactStore alloc] init]; | ||
| if (!contactStore) | ||
| return; | ||
| [self getContactsFromAddressBook:contactStore byPhoneNumber:string callback:callback]; | ||
| } | ||
| -(void) getContactsFromAddressBook:(CNContactStore *)store | ||
| byPhoneNumber:(NSString *)phoneNumber | ||
| callback:(RCTResponseSenderBlock)callback | ||
| { | ||
| NSMutableArray *contacts = [[NSMutableArray alloc] init]; | ||
| NSError *contactError = nil; | ||
| NSArray *keys = @[ | ||
| CNContactEmailAddressesKey, | ||
| CNContactPhoneNumbersKey, | ||
| CNContactFamilyNameKey, | ||
| CNContactGivenNameKey, | ||
| CNContactMiddleNameKey, | ||
| CNContactPostalAddressesKey, | ||
| CNContactOrganizationNameKey, | ||
| CNContactJobTitleKey, | ||
| CNContactImageDataAvailableKey, | ||
| CNContactThumbnailImageDataKey, | ||
| CNContactUrlAddressesKey, | ||
| CNContactBirthdayKey | ||
| ]; | ||
| CNPhoneNumber *cnPhoneNumber = [[CNPhoneNumber alloc] initWithStringValue:phoneNumber]; | ||
| NSArray *arrayOfContacts = [store unifiedContactsMatchingPredicate:[CNContact predicateForContactsMatchingPhoneNumber:cnPhoneNumber] | ||
| keysToFetch:keys | ||
| error:&contactError]; | ||
| [arrayOfContacts enumerateObjectsUsingBlock:^(CNContact * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { | ||
| NSDictionary *contactDictionary = [self contactToDictionary:obj withThumbnails:true]; | ||
| [contacts addObject:contactDictionary]; | ||
| }]; | ||
| callback(@[[NSNull null], contacts]); | ||
| } | ||
| -(void) getAllContacts:(RCTResponseSenderBlock) callback | ||
@@ -525,7 +564,13 @@ withThumbnails:(BOOL) withThumbnails | ||
| UINavigationController* navigation = [[UINavigationController alloc] initWithRootViewController:contactViewController]; | ||
| UIViewController *rooViewController = (UIViewController*)[[[[UIApplication sharedApplication] delegate] window] rootViewController]; | ||
| UIViewController *currentViewController = [UIApplication sharedApplication].keyWindow.rootViewController; | ||
| while (currentViewController.presentedViewController) | ||
| { | ||
| currentViewController = currentViewController.presentedViewController; | ||
| } | ||
| // Cover the contact view with an activity indicator so we can put it in edit mode without user seeing the transition | ||
| UIActivityIndicatorView *activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; | ||
| activityIndicatorView.frame = UIScreen.mainScreen.applicationFrame; | ||
| activityIndicatorView.frame = UIApplication.sharedApplication.keyWindow.frame; | ||
| [activityIndicatorView startAnimating]; | ||
@@ -535,4 +580,5 @@ activityIndicatorView.backgroundColor = [UIColor whiteColor]; | ||
| [rooViewController presentViewController:navigation animated:YES completion:nil]; | ||
| [currentViewController presentViewController:navigation animated:YES completion:nil]; | ||
| // TODO should this 'fake click' method be used? For a brief instance | ||
@@ -539,0 +585,0 @@ // Fake click edit button to enter edit mode |
+1
-1
@@ -7,3 +7,3 @@ { | ||
| }, | ||
| "version": "5.0.2", | ||
| "version": "5.0.3", | ||
| "description": "React Native Contacts (android & ios)", | ||
@@ -10,0 +10,0 @@ "nativePackage": true, |
+29
-5
@@ -1,2 +0,3 @@ | ||
| # React Native Contacts | ||
|  | ||
| To contribute read [CONTRIBUTING.md](CONTRIBUTING.md). | ||
@@ -73,3 +74,20 @@ | ||
| #### Using CocoaPods (react-native 0.60 and above) | ||
| Starting with 0.60, the above instructions stop working on iOS. Instead, you have to do the following: | ||
| - Add the following line inside `ios/Podfile` | ||
| ``` | ||
| target 'app' do | ||
| ... | ||
| pod 'react-native-contacts', :path => '../node_modules/react-native-contacts' <-- add me | ||
| ... | ||
| end | ||
| ``` | ||
| - Run `pod install` in folder `ios` | ||
| ### Android | ||
| For react native versions 0.60 and above you have to use Android X. Android X support was added to react-native-contacts in version 5.x+. If you are using rn 0.59 and below install rnc versions 4.x instead. | ||
| 1. In `android/settings.gradle` | ||
@@ -83,3 +101,3 @@ | ||
| 3. In `android/app/build.gradle` | ||
| 2. In `android/app/build.gradle` | ||
@@ -94,3 +112,3 @@ ```gradle | ||
| 4. register module (in MainApplication.java) | ||
| 3. register module (in MainApplication.java) | ||
@@ -152,2 +170,3 @@ ```java | ||
| * `getContactsMatchingString` (string, callback) - where string is any string to match a name (first, middle, family) to | ||
| * `getContactsByPhoneNumber` (string, callback) - where string is a phone number to match to. | ||
| * `checkPermission` (callback) - checks permission to access Contacts _ios only_ | ||
@@ -232,4 +251,3 @@ * `requestPermission` (callback) - request permission to access Contacts _ios only_ | ||
| }], | ||
| familyName: "Nietzsche", | ||
| givenName: "Friedrich", | ||
| displayName: "Friedrich Nietzsche" | ||
| } | ||
@@ -328,2 +346,8 @@ | ||
| ## Example | ||
| You can find an example app/showcase [here](https://github.com/rt2zz/react-native-contacts/tree/master/example) | ||
|  | ||
| <h2 align="center">Maintainers</h2> | ||
@@ -330,0 +354,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Install scripts
Supply chain riskInstall scripts are run when the package is installed or built. Malicious packages often use scripts that run automatically to execute payloads or fetch additional code.
Found 1 instance in 1 package
8845821
913.58%83
9.21%13587
4.08%366
7.02%1
Infinity%