1import React from 'react'; 2import { RefreshControl, StyleSheet, Text, View } from 'react-native'; 3import * as Permissions from 'expo-permissions'; 4import * as Contacts from 'expo-contacts'; 5import { NavigationEvents, NavigationScreenProps } from 'react-navigation'; 6import HeaderButtons from 'react-navigation-header-buttons'; 7import { Ionicons } from '@expo/vector-icons'; 8 9import ContactsList from './ContactsList'; 10import * as ContactUtils from './ContactUtils'; 11 12const CONTACT_PAGE_SIZE = 500; 13 14interface State { 15 contacts: Contacts.Contact[]; 16 hasPreviousPage: boolean; 17 hasNextPage: boolean; 18 permission?: boolean; 19 refreshing: boolean; 20} 21 22export default class ContactsScreen extends React.Component<NavigationScreenProps, State> { 23 static navigationOptions = () => { 24 return { 25 title: 'Contacts', 26 headerRight: ( 27 <HeaderButtons 28 IconComponent={Ionicons} 29 OverflowIcon={<Ionicons name="ios-more" size={23} color="blue" />} 30 iconSize={23} 31 color="blue" 32 > 33 <HeaderButtons.Item 34 title="add" 35 iconName="md-add" 36 onPress={async () => { 37 const randomContact = { note: 'Likes expo...' }; 38 39 // @ts-ignore 40 ContactUtils.presentNewContactFormAsync({ contact: randomContact }); 41 // ContactUtils.presentUnknownContactFormAsync({ 42 // contact: randomContact, 43 // }); 44 }} 45 /> 46 </HeaderButtons> 47 ), 48 }; 49 } 50 51 _rawContacts: { [contactId: string]: Contacts.Contact } = {}; 52 readonly state: State = { 53 contacts: [], 54 hasPreviousPage: false, 55 hasNextPage: false, 56 refreshing: false, 57 }; 58 59 componentDidFocus = async () => { 60 await this.checkPermissionAsync(); 61 await this.loadAsync(); 62 } 63 64 checkPermissionAsync = async () => { 65 const { status } = await Permissions.askAsync(Permissions.CONTACTS); 66 this.setState({ permission: status === 'granted' }); 67 } 68 69 loadAsync = async ({ distanceFromEnd }: { distanceFromEnd?: number } = {}, restart = false) => { 70 if (!this.state.permission || this.state.refreshing) { 71 return; 72 } 73 this.setState({ refreshing: true }); 74 75 const pageOffset = restart ? 0 : this.state.contacts.length || 0; 76 77 const pageSize = restart ? Math.max(pageOffset, CONTACT_PAGE_SIZE) : CONTACT_PAGE_SIZE; 78 79 const payload = await Contacts.getContactsAsync({ 80 fields: [Contacts.Fields.Name], 81 sort: Contacts.SortTypes.LastName, 82 pageSize, 83 pageOffset, 84 }); 85 86 const { data: contacts, hasPreviousPage, hasNextPage } = payload; 87 88 if (restart) { 89 this._rawContacts = {}; 90 } 91 92 for (const contact of contacts) { 93 this._rawContacts[contact.id] = contact; 94 } 95 this.setState({ 96 contacts: Object.values(this._rawContacts), 97 hasPreviousPage, 98 hasNextPage, 99 refreshing: false, 100 }); 101 } 102 103 onPressItem = async (id: string) => { 104 // tslint:disable-next-line no-console 105 console.log('onPress', id); 106 this.props.navigation.navigate('ContactDetail', { id }); 107 } 108 109 render() { 110 const { contacts, permission } = this.state; 111 if (!permission) { 112 return ( 113 <View style={styles.permissionContainer}> 114 <NavigationEvents onDidFocus={this.componentDidFocus} /> 115 <Text>No Contact Permission</Text> 116 </View> 117 ); 118 } 119 return ( 120 <View style={styles.container}> 121 <NavigationEvents onDidFocus={this.componentDidFocus} /> 122 <ContactsList 123 onEndReachedThreshold={-1.5} 124 refreshControl={ 125 <RefreshControl 126 refreshing={this.state.refreshing} 127 onRefresh={() => this.loadAsync({}, true)} 128 /> 129 } 130 data={contacts} 131 onPressItem={this.onPressItem} 132 onEndReached={this.loadAsync} 133 /> 134 </View> 135 ); 136 } 137} 138 139const styles = StyleSheet.create({ 140 button: { 141 marginVertical: 10, 142 }, 143 container: { 144 flex: 1, 145 }, 146 permissionContainer: { 147 flex: 1, 148 justifyContent: 'center', 149 alignItems: 'center', 150 }, 151 contactRow: { 152 marginBottom: 12, 153 }, 154}); 155