Skip to content

Commit

Permalink
refactor(contacts): Use react-native-permissions for contacts (#4979)
Browse files Browse the repository at this point in the history
### Description

Replace custom contact permissions checking with
react-native-permissions

### Test plan

Unit tests updated

Ran the wallet locally and logged the `contactPermissionStatus` for each
place it was added. Started with contacts permissions turned off and
verified that it logged as something other than `granted`, then turned
permissions to allow and verified that it logged as `granted` in each
place.

### Related issues

- Fixes ACT-1077

### Backwards compatibility

Yes
  • Loading branch information
finnian0826 committed Mar 1, 2024
1 parent eec7658 commit a78b23a
Show file tree
Hide file tree
Showing 13 changed files with 32 additions and 129 deletions.
3 changes: 3 additions & 0 deletions jest_setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,8 @@ jest.mock('@react-native-clipboard/clipboard', () => ({
hasString: jest.fn(),
}))

// this mock defaults to granting all permissions
jest.mock('react-native-permissions', () => require('react-native-permissions/mock'))

// @ts-ignore
global.__reanimatedWorkletInit = jest.fn()
1 change: 0 additions & 1 deletion src/analytics/ValoraAnalytics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ jest.mock('@segment/analytics-react-native')
jest.mock('@segment/analytics-react-native-plugin-adjust')
jest.mock('@segment/analytics-react-native-plugin-clevertap')
jest.mock('@segment/analytics-react-native-plugin-firebase')
jest.mock('react-native-permissions', () => ({}))
jest.mock('@sentry/react-native', () => ({ init: jest.fn() }))
jest.mock('src/redux/store', () => ({ store: { getState: jest.fn() } }))
jest.mock('src/config', () => ({
Expand Down
6 changes: 3 additions & 3 deletions src/home/WalletHome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ import colors from 'src/styles/colors'
import { Spacing } from 'src/styles/styles'
import { celoAddressSelector, coreTokensSelector } from 'src/tokens/selectors'
import TransactionFeed from 'src/transactions/feed/TransactionFeed'
import { hasGrantedContactsPermission } from 'src/utils/contacts'
import { userInSanctionedCountrySelector } from 'src/utils/countryFeatures'
import { checkContactsPermission } from 'src/utils/permissions'

const AnimatedSectionList = Animated.createAnimatedComponent(SectionList)

Expand Down Expand Up @@ -96,8 +96,8 @@ function WalletHome() {
return
}

const hasGivenContactPermission = await checkContactsPermission()
if (hasGivenContactPermission) {
const contactPermissionStatusGranted = await hasGrantedContactsPermission()
if (contactPermissionStatusGranted) {
dispatch(importContacts())
}
}
Expand Down
13 changes: 6 additions & 7 deletions src/identity/contactMapping.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ import { contactsToRecipients } from 'src/recipients/recipient'
import { phoneRecipientCacheSelector, setPhoneRecipientCache } from 'src/recipients/reducer'
import { getFeatureGate } from 'src/statsig'
import Logger from 'src/utils/Logger'
import { getAllContacts } from 'src/utils/contacts'
import { checkContactsPermission } from 'src/utils/permissions'
import { getAllContacts, hasGrantedContactsPermission } from 'src/utils/contacts'
import networkConfig from 'src/web3/networkConfig'
import { getConnectedAccount } from 'src/web3/saga'
import { walletAddressSelector } from 'src/web3/selectors'
Expand Down Expand Up @@ -249,7 +248,7 @@ describe('saveContacts', () => {
await expectSaga(saveContacts)
.provide([
[select(phoneNumberVerifiedSelector), true],
[call(checkContactsPermission), true],
[call(hasGrantedContactsPermission), true],
[select(phoneRecipientCacheSelector), mockPhoneRecipientCache],
[select(e164NumberSelector), mockE164Number],
[select(lastSavedContactsHashSelector), null],
Expand Down Expand Up @@ -282,7 +281,7 @@ describe('saveContacts', () => {
await expectSaga(saveContacts)
.provide([
[select(phoneNumberVerifiedSelector), true],
[call(checkContactsPermission), true],
[call(hasGrantedContactsPermission), true],
[
select(phoneRecipientCacheSelector),
{ ...mockPhoneRecipientCache, [mockE164Number2]: {} },
Expand Down Expand Up @@ -320,7 +319,7 @@ describe('saveContacts', () => {
await expectSaga(saveContacts)
.provide([
[select(phoneNumberVerifiedSelector), true],
[call(checkContactsPermission), true],
[call(hasGrantedContactsPermission), true],
[select(phoneRecipientCacheSelector), mockPhoneRecipientCache],
[select(e164NumberSelector), mockE164Number],
[
Expand All @@ -347,7 +346,7 @@ describe('saveContacts', () => {
await expectSaga(saveContacts)
.provide([
[select(phoneNumberVerifiedSelector), phoneVerified],
[call(checkContactsPermission), contactsEnabled],
[call(hasGrantedContactsPermission), contactsEnabled],
])
.not.select(phoneRecipientCacheSelector)
.not.select(e164NumberSelector)
Expand All @@ -362,7 +361,7 @@ describe('saveContacts', () => {
await expectSaga(saveContacts)
.provide([
[select(phoneNumberVerifiedSelector), true],
[call(checkContactsPermission), true],
[call(hasGrantedContactsPermission), true],
[select(phoneRecipientCacheSelector), mockPhoneRecipientCache],
[select(e164NumberSelector), mockE164Number],
[select(lastSavedContactsHashSelector), undefined],
Expand Down
10 changes: 4 additions & 6 deletions src/identity/contactMapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,9 @@ import { SentryTransaction } from 'src/sentry/SentryTransactions'
import { getFeatureGate } from 'src/statsig'
import { StatsigFeatureGates } from 'src/statsig/types'
import Logger from 'src/utils/Logger'
import { getAllContacts } from 'src/utils/contacts'
import { getAllContacts, hasGrantedContactsPermission } from 'src/utils/contacts'
import { ensureError } from 'src/utils/ensureError'
import { fetchWithTimeout } from 'src/utils/fetchWithTimeout'
import { checkContactsPermission } from 'src/utils/permissions'
import { calculateSha256Hash } from 'src/utils/random'
import { getContractKit } from 'src/web3/contracts'
import networkConfig from 'src/web3/networkConfig'
Expand Down Expand Up @@ -92,8 +91,8 @@ export function* doImportContactsWrapper() {
}

function* doImportContacts() {
const hasGivenContactPermission: boolean = yield* call(checkContactsPermission)
if (!hasGivenContactPermission) {
const contactPermissionStatusGranted = yield* call(hasGrantedContactsPermission)
if (!contactPermissionStatusGranted) {
Logger.warn(TAG, 'Contact permissions denied. Skipping import.')
ValoraAnalytics.track(IdentityEvents.contacts_import_permission_denied)
return true
Expand Down Expand Up @@ -424,8 +423,7 @@ export function* saveContacts() {
try {
const saveContactsGate = getFeatureGate(StatsigFeatureGates.SAVE_CONTACTS)
const phoneVerified = yield* select(phoneNumberVerifiedSelector)
// TODO(satish): use rn permissions
const contactsEnabled: boolean = yield* call(checkContactsPermission)
const contactsEnabled = yield* call(hasGrantedContactsPermission)

if (!saveContactsGate || !phoneVerified || !contactsEnabled) {
Logger.debug(`${TAG}/saveContacts`, "Skipping because pre conditions aren't met", {
Expand Down
2 changes: 0 additions & 2 deletions src/navigator/QRNavigator.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import QRNavigator, { QRCodePicker, QRCodeProps } from 'src/navigator/QRNavigato
import MockedNavigator from 'test/MockedNavigator'
import { createMockStore } from 'test/utils'

jest.mock('react-native-permissions', () => jest.fn())

jest.mock('src/qrcode/StyledQRGen', () => jest.fn().mockReturnValue(''))
jest.mock('src/qrcode/QRGen', () => jest.fn().mockReturnValue(''))

Expand Down
1 change: 0 additions & 1 deletion src/send/SelectRecipientButtons.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { StatsigFeatureGates } from 'src/statsig/types'
import { navigateToPhoneSettings } from 'src/utils/linking'
import { createMockStore } from 'test/utils'

jest.mock('react-native-permissions', () => require('react-native-permissions/mock'))
jest.mock('src/statsig')

const renderComponent = (phoneNumberVerified = false) => {
Expand Down
5 changes: 1 addition & 4 deletions src/send/SelectRecipientButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { useAsync } from 'react-async-hook'
import { useTranslation } from 'react-i18next'
import { Platform } from 'react-native'
import {
PERMISSIONS,
RESULTS as PERMISSION_RESULTS,
PermissionStatus,
check as checkPermission,
Expand All @@ -21,11 +20,9 @@ import { navigate } from 'src/navigator/NavigationService'
import { Screens } from 'src/navigator/Screens'
import useSelector from 'src/redux/useSelector'
import Logger from 'src/utils/Logger'
import { CONTACTS_PERMISSION } from 'src/utils/contacts'
import { navigateToPhoneSettings } from 'src/utils/linking'

const CONTACTS_PERMISSION =
Platform.OS === 'ios' ? PERMISSIONS.IOS.CONTACTS : PERMISSIONS.ANDROID.READ_CONTACTS

type Props = {
onContactsPermissionGranted: () => void
}
Expand Down
2 changes: 0 additions & 2 deletions src/send/SendSelectRecipient.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@ jest.mock('src/recipients/recipient', () => ({
}))

jest.mock('react-native-device-info', () => ({ getFontScaleSync: () => 1 }))
// this mock defaults to granting all permissions
jest.mock('react-native-permissions', () => require('react-native-permissions/mock'))

const mockScreenProps = ({
defaultTokenIdOverride,
Expand Down
18 changes: 15 additions & 3 deletions src/utils/contacts.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
import { Platform } from 'react-native'
import { getAll, getMinimal, MinimalContact } from 'react-native-contacts'
import {
check as checkPermission,
RESULTS as PERMISSION_RESULTS,
PERMISSIONS,
} from 'react-native-permissions'
import Logger from 'src/utils/Logger'
import { checkContactsPermission } from 'src/utils/permissions'

const TAG = 'utils/contacts'

export const CONTACTS_PERMISSION =
Platform.OS === 'ios' ? PERMISSIONS.IOS.CONTACTS : PERMISSIONS.ANDROID.READ_CONTACTS

export async function hasGrantedContactsPermission() {
const contactPermissionStatus = await checkPermission(CONTACTS_PERMISSION)
return contactPermissionStatus === PERMISSION_RESULTS.GRANTED
}

// Stop gap solution since getMinimal is not yet implement on iOS
function customGetAll(callback: (error: any, contacts: MinimalContact[]) => void) {
getAll((error, fullContacts) => {
Expand Down Expand Up @@ -38,8 +50,8 @@ function customGetAll(callback: (error: any, contacts: MinimalContact[]) => void
}

export async function getAllContacts(): Promise<MinimalContact[] | null> {
const contactPermissionsGiven = await checkContactsPermission()
if (!contactPermissionsGiven) {
const contactPermissionStatusGranted = await hasGrantedContactsPermission()
if (!contactPermissionStatusGranted) {
Logger.warn(TAG, 'Permissions not given for retrieving contacts')
return null
}
Expand Down
51 changes: 0 additions & 51 deletions src/utils/permissions.android.ts

This file was deleted.

18 changes: 0 additions & 18 deletions src/utils/permissions.d.ts

This file was deleted.

31 changes: 0 additions & 31 deletions src/utils/permissions.ios.ts

This file was deleted.

0 comments on commit a78b23a

Please sign in to comment.