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