Skip to content

Commit 86ea451

Browse files
j0ntzJon Tzeng
authored andcommitted
Fix contacts permission prompt timing
Prevent contact permission prompts from appearing during passive thumbnail lookups and limit them to transaction-list or explicit payee edit flows.
1 parent 1c5b1a7 commit 86ea451

6 files changed

Lines changed: 35 additions & 41 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
- added: Debug settings scene (Developer Mode only) with nodes/servers inspection, engine `dataDump` viewer, and log viewer
1010
- fixed: Swap quote timeout error interrupting user after cancelling a slow swap search
1111
- fixed: Disable "Migrate Wallets" button when no assets are available to migrate
12+
- fixed: Contacts permission prompt no longer appears on first receive and only shows from transaction-list or payee edit flows
1213

1314
## 4.45.0 (2025-03-10)
1415

eslint.config.mjs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,6 @@ export default [
174174

175175
'src/components/modals/CategoryModal.tsx',
176176

177-
'src/components/modals/ContactListModal.tsx',
178-
179177
'src/components/modals/CountryListModal.tsx',
180178
'src/components/modals/DateModal.tsx',
181179
'src/components/modals/FiatListModal.tsx',
@@ -405,7 +403,7 @@ export default [
405403
'src/controllers/loan-manager/redux/actions.ts',
406404
'src/controllers/loan-manager/util/waitForLoanAccountSync.ts',
407405
'src/hooks/animations/useFadeAnimation.ts',
408-
'src/hooks/redux/useContactThumbnail.ts',
406+
409407
'src/hooks/useAbortable.ts',
410408
'src/hooks/useAccountSyncRatio.tsx',
411409
'src/hooks/useAsyncEffect.ts',

src/components/modals/ContactListModal.tsx

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,9 @@ import { lstrings } from '../../locales/strings'
99
import { useDispatch, useSelector } from '../../types/reactRedux'
1010
import type { GuiContact } from '../../types/types'
1111
import { normalizeForSearch } from '../../util/utils'
12-
import { requestContactsPermission } from '../services/PermissionsManager'
1312
import { cacheStyles, type Theme, useTheme } from '../services/ThemeContext'
1413
import { SelectableRow } from '../themed/SelectableRow'
15-
import { maybeShowContactsPermissionModal } from './ContactsPermissionModal'
14+
import { promptForContactsPermission } from './ContactsPermissionModal'
1615
import { ListModal } from './ListModal'
1716

1817
export interface ContactModalResult {
@@ -26,11 +25,11 @@ interface Props {
2625
contactName: string
2726
}
2827

29-
export function ContactListModal({
28+
export const ContactListModal: React.FC<Props> = ({
3029
bridge,
3130
contactType,
3231
contactName
33-
}: Props): React.ReactElement {
32+
}) => {
3433
const theme = useTheme()
3534
const styles = getStyles(theme)
3635
const contacts = useSelector(state => state.contacts)
@@ -96,10 +95,7 @@ export function ContactListModal({
9695

9796
useAsyncEffect(
9897
async () => {
99-
const result = await dispatch(maybeShowContactsPermissionModal())
100-
if (result === 'allow') {
101-
await requestContactsPermission(true)
102-
}
98+
await dispatch(promptForContactsPermission())
10399
},
104100
[],
105101
'ContactListModal'

src/components/modals/ContactsPermissionModal.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { config } from '../../theme/appConfig'
1212
import type { ThunkAction } from '../../types/reduxTypes'
1313
import { ButtonsModal } from '../modals/ButtonsModal'
1414
import { Airship } from '../services/AirshipInstance'
15+
import { requestContactsPermission } from '../services/PermissionsManager'
1516
import { cacheStyles, type Theme, useTheme } from '../services/ThemeContext'
1617
import { EdgeText } from '../themed/EdgeText'
1718

@@ -62,6 +63,15 @@ export function maybeShowContactsPermissionModal(): ThunkAction<
6263
}
6364
}
6465

66+
export function promptForContactsPermission(): ThunkAction<Promise<void>> {
67+
return async dispatch => {
68+
const result = await dispatch(maybeShowContactsPermissionModal())
69+
if (result === 'allow') {
70+
await requestContactsPermission(true)
71+
}
72+
}
73+
}
74+
6575
/**
6676
* Shows the modal if it hasn't been shown before, and attempts to set the
6777
* system contacts permission setting

src/components/scenes/TransactionListScene.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { getWalletName } from '../../util/CurrencyWalletHelpers'
3636
import { calculateSpamThreshold, unixToLocaleDateTime } from '../../util/utils'
3737
import { SceneWrapper } from '../common/SceneWrapper'
3838
import { withWallet } from '../hoc/withWallet'
39+
import { promptForContactsPermission } from '../modals/ContactsPermissionModal'
3940
import { HeaderTitle } from '../navigation/HeaderTitle'
4041
import { cacheStyles, useTheme } from '../services/ThemeContext'
4142
import { ExplorerCard } from '../themed/ExplorerCard'
@@ -131,6 +132,13 @@ const TransactionListComponent: React.FC<Props> = props => {
131132
return out
132133
}, [atEnd, isTransactionListUnsupported, transactions])
133134

135+
const hasNamedTransactions = React.useMemo(() => {
136+
return transactions.some(transaction => {
137+
const metadataName = transaction.metadata?.name
138+
return metadataName != null && metadataName.trim() !== ''
139+
})
140+
}, [transactions])
141+
134142
// ---------------------------------------------------------------------------
135143
// Side-Effects
136144
// ---------------------------------------------------------------------------
@@ -142,6 +150,15 @@ const TransactionListComponent: React.FC<Props> = props => {
142150
}
143151
}, [enabledTokenIds, navigation, tokenId])
144152

153+
useAsyncEffect(
154+
async () => {
155+
if (!hasNamedTransactions) return
156+
await dispatch(promptForContactsPermission())
157+
},
158+
[hasNamedTransactions],
159+
'TransactionListScene contacts permission'
160+
)
161+
145162
// Automatically navigate to the token activation confirmation scene if
146163
// the token appears in the unactivatedTokenIds list once the wallet loads
147164
// this state.

src/hooks/redux/useContactThumbnail.ts

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,14 @@
11
import * as React from 'react'
2-
import { check } from 'react-native-permissions'
32

4-
import { maybeShowContactsPermissionModal } from '../../components/modals/ContactsPermissionModal'
5-
import { requestContactsPermission } from '../../components/services/PermissionsManager'
63
import { MERCHANT_CONTACTS } from '../../constants/MerchantContacts'
7-
import { permissionNames } from '../../reducers/PermissionsReducer'
8-
import { useDispatch, useSelector } from '../../types/reactRedux'
4+
import { useSelector } from '../../types/reactRedux'
95
import { normalizeForSearch } from '../../util/utils'
10-
import { useAsyncEffect } from '../useAsyncEffect'
116

127
/**
13-
* Looks up a thumbnail image for a contact. Will show a contacts permission
14-
* request modal if we haven't shown it before and the system contacts
15-
* permission is not granted.
8+
* Looks up a thumbnail image for a contact using existing contacts data.
169
*/
1710
export const useContactThumbnail = (name?: string): string | undefined => {
1811
const contacts = useSelector(state => state.contacts)
19-
const dispatch = useDispatch()
20-
21-
useAsyncEffect(
22-
async () => {
23-
const contactsPermission = await check(permissionNames.contacts).catch(
24-
(_error: unknown) => 'denied'
25-
)
26-
27-
if (
28-
contactsPermission !== 'granted' &&
29-
contactsPermission !== 'limited'
30-
) {
31-
const result = await dispatch(maybeShowContactsPermissionModal())
32-
if (result === 'allow') {
33-
await requestContactsPermission(true)
34-
}
35-
}
36-
},
37-
[],
38-
'useContactThumbnail'
39-
)
4012

4113
return React.useMemo(() => {
4214
if (name == null) return

0 commit comments

Comments
 (0)