From a49c035d07cbd4660559e6420907f8e95a53b30b Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Fri, 28 Nov 2025 19:32:31 +0200 Subject: [PATCH] get contacts --- .../contacts/screens/GetContacts.tsx | 109 ++++++++++++++++-- .../contacts/screens/VerifyNumber.tsx | 7 +- src/components/contacts/state.ts | 16 ++- 3 files changed, 114 insertions(+), 18 deletions(-) diff --git a/src/components/contacts/screens/GetContacts.tsx b/src/components/contacts/screens/GetContacts.tsx index 6995ac0fdd..6faf4f57cc 100644 --- a/src/components/contacts/screens/GetContacts.tsx +++ b/src/components/contacts/screens/GetContacts.tsx @@ -1,15 +1,21 @@ -import {View} from 'react-native' +import {Alert, View} from 'react-native' import {useSafeAreaInsets} from 'react-native-safe-area-context' -import {msg, Trans} from '@lingui/macro' +import * as Contacts from 'expo-contacts' +import {msg, t, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {useMutation} from '@tanstack/react-query' +import {wait} from '#/lib/async/wait' +import {logger} from '#/logger' import {atoms as a, tokens, useGutters} from '#/alf' -import {Button, ButtonText} from '#/components/Button' +import {Button, ButtonIcon, ButtonText} from '#/components/Button' import * as Layout from '#/components/Layout' +import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' import {type Action, type State} from '../state' export function GetContacts({ + dispatch, onCancel, }: { state: Extract @@ -18,9 +24,63 @@ export function GetContacts({ }) { const {_} = useLingui() const insets = useSafeAreaInsets() - const gutters = useGutters([0, 'wide']) + const {mutate: uploadContacts, isPending: isUploadPending} = useMutation({ + mutationFn: async (_contacts: Contacts.ExistingContact[]) => { + await wait(2e3, () => {}) + }, + onSuccess: () => { + dispatch({ + type: 'SYNC_CONTACTS_SUCCESS', + payload: { + matches: [], + }, + }) + }, + }) + + const {mutate: getContacts, isPending: isGetContactsPending} = useMutation({ + mutationFn: async () => { + let permissions = await Contacts.getPermissionsAsync() + + if (!permissions.granted && permissions.canAskAgain) { + permissions = await Contacts.requestPermissionsAsync() + } + + if (!permissions.granted) { + throw new PermissionDeniedError() + } + + const contacts = await Contacts.getContactsAsync({ + fields: [ + Contacts.Fields.FirstName, + Contacts.Fields.LastName, + Contacts.Fields.PhoneNumbers, + Contacts.Fields.Image, + ], + }) + + return contacts.data + }, + onSuccess: contacts => { + dispatch({ + type: 'GET_CONTACTS_SUCCESS', + payload: {contacts}, + }) + uploadContacts(contacts) + }, + onError: err => { + if (err instanceof PermissionDeniedError) { + showPermissionDeniedAlert() + } else { + logger.error('Error getting contacts', {safeMessage: err}) + } + }, + }) + + const isPending = isUploadPending || isGetContactsPending + const style = [a.text_md, a.leading_snug, a.mt_md] return ( @@ -70,10 +130,24 @@ export function GetContacts({ and to retain hashed data for matching until I opt out. -