1import { useFocusEffect } from '@react-navigation/native'; 2import { StackNavigationProp } from '@react-navigation/stack'; 3import * as Contacts from 'expo-contacts'; 4import { Platform } from 'expo-modules-core'; 5import React from 'react'; 6import { RefreshControl, StyleSheet, Text, View } from 'react-native'; 7 8import * as ContactUtils from './ContactUtils'; 9import ContactsList from './ContactsList'; 10import HeaderContainerRight from '../../components/HeaderContainerRight'; 11import HeaderIconButton from '../../components/HeaderIconButton'; 12import usePermissions from '../../utilities/usePermissions'; 13import { useResolvedValue } from '../../utilities/useResolvedValue'; 14 15type StackParams = { 16 ContactDetail: { id: string }; 17}; 18 19type Props = { 20 navigation: StackNavigationProp<StackParams>; 21}; 22 23const CONTACT_PAGE_SIZE = 500; 24 25export default function ContactsScreen({ navigation }: Props) { 26 React.useLayoutEffect(() => { 27 navigation.setOptions({ 28 title: 'Contacts', 29 headerRight: () => ( 30 <HeaderContainerRight> 31 <HeaderIconButton 32 disabled={Platform.select({ web: true, default: false })} 33 name="md-add" 34 onPress={() => { 35 const randomContact = { note: 'Likes expo...' } as Contacts.Contact; 36 ContactUtils.presentNewContactFormAsync({ contact: randomContact }); 37 }} 38 /> 39 </HeaderContainerRight> 40 ), 41 }); 42 }, [navigation]); 43 44 const [isAvailable, error] = useResolvedValue(Contacts.isAvailableAsync); 45 const [permission] = usePermissions(Contacts.requestPermissionsAsync); 46 47 const warning = React.useMemo(() => { 48 if (error) { 49 return `An unknown error occurred while checking the API availability: ${error.message}`; 50 } else if (isAvailable === null) { 51 return 'Checking availability...'; 52 } else if (isAvailable === false) { 53 return 'Contacts API is not available on this platform.'; 54 } else if (!permission) { 55 return 'Contacts permission has not been granted for this app. Grant permission in the Settings app to continue.'; 56 } else if (permission) { 57 return null; 58 } 59 return 'Pending user permission...'; 60 }, [error, permission, isAvailable]); 61 62 if (warning) { 63 return ( 64 <View style={styles.permissionContainer}> 65 <Text>{warning}</Text> 66 </View> 67 ); 68 } 69 70 return <ContactsView navigation={navigation} />; 71} 72 73function ContactsView({ navigation }: Props) { 74 let rawContacts: Record<string, Contacts.Contact> = {}; 75 76 const [contacts, setContacts] = React.useState<Contacts.Contact[]>([]); 77 const [hasNextPage, setHasNextPage] = React.useState(true); 78 const [refreshing, setRefreshing] = React.useState(false); 79 80 const onPressItem = React.useCallback( 81 (id: string) => { 82 navigation.navigate('ContactDetail', { id }); 83 }, 84 [navigation] 85 ); 86 87 const loadAsync = async (event: { distanceFromEnd?: number } = {}, restart = false) => { 88 if (!hasNextPage || refreshing || Platform.OS === 'web') { 89 return; 90 } 91 setRefreshing(true); 92 93 const pageOffset = restart ? 0 : contacts.length || 0; 94 95 const pageSize = restart ? Math.max(pageOffset, CONTACT_PAGE_SIZE) : CONTACT_PAGE_SIZE; 96 97 const payload = await Contacts.getContactsAsync({ 98 fields: [Contacts.Fields.Name], 99 sort: Contacts.SortTypes.LastName, 100 pageSize, 101 pageOffset, 102 }); 103 104 const { data: nextContacts } = payload; 105 106 if (restart) { 107 rawContacts = {}; 108 } 109 110 for (const contact of nextContacts) { 111 rawContacts[contact.id!] = contact; 112 } 113 setContacts(Object.values(rawContacts)); 114 setHasNextPage(payload.hasNextPage); 115 setRefreshing(false); 116 }; 117 118 const onFocus = React.useCallback(() => { 119 loadAsync(); 120 }, []); 121 122 useFocusEffect(onFocus); 123 124 return ( 125 <ContactsList 126 onEndReachedThreshold={-1.5} 127 refreshControl={ 128 <RefreshControl refreshing={refreshing} onRefresh={() => loadAsync({}, true)} /> 129 } 130 data={contacts} 131 onPressItem={onPressItem} 132 onEndReached={loadAsync} 133 /> 134 ); 135} 136 137const styles = StyleSheet.create({ 138 button: { 139 marginVertical: 10, 140 }, 141 permissionContainer: { 142 flex: 1, 143 justifyContent: 'center', 144 alignItems: 'center', 145 }, 146 contactRow: { 147 marginBottom: 12, 148 }, 149}); 150