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