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