Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- added: Debug settings scene (Developer Mode only) with nodes/servers inspection, engine `dataDump` viewer, and log viewer
- fixed: Swap quote timeout error interrupting user after cancelling a slow swap search
- fixed: Disable "Migrate Wallets" button when no assets are available to migrate
- fixed: Contacts permission prompt no longer appears on first receive and only shows from transaction-list or payee edit flows

## 4.45.0 (2025-03-10)

Expand Down
4 changes: 1 addition & 3 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,6 @@ export default [

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

'src/components/modals/ContactListModal.tsx',
'src/components/modals/ContactsPermissionModal.tsx',
'src/components/modals/CountryListModal.tsx',
'src/components/modals/DateModal.tsx',
'src/components/modals/FiatListModal.tsx',
Expand Down Expand Up @@ -405,7 +403,7 @@ export default [
'src/controllers/loan-manager/redux/actions.ts',
'src/controllers/loan-manager/util/waitForLoanAccountSync.ts',
'src/hooks/animations/useFadeAnimation.ts',
'src/hooks/redux/useContactThumbnail.ts',

'src/hooks/useAbortable.ts',
'src/hooks/useAccountSyncRatio.tsx',
'src/hooks/useAsyncEffect.ts',
Expand Down
12 changes: 4 additions & 8 deletions src/components/modals/ContactListModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@ import { lstrings } from '../../locales/strings'
import { useDispatch, useSelector } from '../../types/reactRedux'
import type { GuiContact } from '../../types/types'
import { normalizeForSearch } from '../../util/utils'
import { requestContactsPermission } from '../services/PermissionsManager'
import { cacheStyles, type Theme, useTheme } from '../services/ThemeContext'
import { SelectableRow } from '../themed/SelectableRow'
import { maybeShowContactsPermissionModal } from './ContactsPermissionModal'
import { promptForContactsPermission } from './ContactsPermissionModal'
import { ListModal } from './ListModal'

export interface ContactModalResult {
Expand All @@ -26,11 +25,11 @@ interface Props {
contactName: string
}

export function ContactListModal({
export const ContactListModal: React.FC<Props> = ({
bridge,
contactType,
contactName
}: Props): React.ReactElement {
}) => {
const theme = useTheme()
const styles = getStyles(theme)
const contacts = useSelector(state => state.contacts)
Expand Down Expand Up @@ -96,10 +95,7 @@ export function ContactListModal({

useAsyncEffect(
async () => {
const result = await dispatch(maybeShowContactsPermissionModal())
if (result === 'allow') {
await requestContactsPermission(true)
}
await dispatch(promptForContactsPermission())
},
[],
'ContactListModal'
Expand Down
17 changes: 14 additions & 3 deletions src/components/modals/ContactsPermissionModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { config } from '../../theme/appConfig'
import type { ThunkAction } from '../../types/reduxTypes'
import { ButtonsModal } from '../modals/ButtonsModal'
import { Airship } from '../services/AirshipInstance'
import { requestContactsPermission } from '../services/PermissionsManager'
import { cacheStyles, type Theme, useTheme } from '../services/ThemeContext'
import { EdgeText } from '../themed/EdgeText'

Expand Down Expand Up @@ -47,8 +48,9 @@ export function maybeShowContactsPermissionModal(): ThunkAction<

// Bail if we already have permission:
const contactsPermissionOn =
(await check(permissionNames.contacts).catch(_error => 'denied')) ===
'granted'
(await check(permissionNames.contacts).catch(
(_error: unknown) => 'denied'
)) === 'granted'
if (contactsPermissionOn) return

// Show the modal:
Expand All @@ -61,11 +63,20 @@ export function maybeShowContactsPermissionModal(): ThunkAction<
}
}

export function promptForContactsPermission(): ThunkAction<Promise<void>> {
return async dispatch => {
const result = await dispatch(maybeShowContactsPermissionModal())
if (result === 'allow') {
await requestContactsPermission(true)
}
}
}

/**
* Shows the modal if it hasn't been shown before, and attempts to set the
* system contacts permission setting
*/
function ContactsPermissionModal(props: Props) {
const ContactsPermissionModal: React.FC<Props> = props => {
const { bridge } = props
const theme = useTheme()
const styles = getStyles(theme)
Expand Down
17 changes: 17 additions & 0 deletions src/components/scenes/TransactionListScene.tsx
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this commit much. Why were the type deprecated. I guess maybe a little sidebar?

Copy link
Contributor Author

@j0ntz j0ntz Mar 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RN navigation 7 will no longer allow NavigationBase and requires typed navigators only, that's why it was deprecated in the first place. We need to prepare for the transition eventually so we must do it incrementally.

On second thought, though, this commit might not be a valid workaround. We might just need to actually fix everything such that we lift navigator props out of all components using NavigationBase

Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { getWalletName } from '../../util/CurrencyWalletHelpers'
import { calculateSpamThreshold, unixToLocaleDateTime } from '../../util/utils'
import { SceneWrapper } from '../common/SceneWrapper'
import { withWallet } from '../hoc/withWallet'
import { promptForContactsPermission } from '../modals/ContactsPermissionModal'
import { HeaderTitle } from '../navigation/HeaderTitle'
import { cacheStyles, useTheme } from '../services/ThemeContext'
import { ExplorerCard } from '../themed/ExplorerCard'
Expand Down Expand Up @@ -131,6 +132,13 @@ const TransactionListComponent: React.FC<Props> = props => {
return out
}, [atEnd, isTransactionListUnsupported, transactions])

const hasNamedTransactions = React.useMemo(() => {
return transactions.some(transaction => {
const metadataName = transaction.metadata?.name
return metadataName != null && metadataName.trim() !== ''
})
}, [transactions])

// ---------------------------------------------------------------------------
// Side-Effects
// ---------------------------------------------------------------------------
Expand All @@ -142,6 +150,15 @@ const TransactionListComponent: React.FC<Props> = props => {
}
}, [enabledTokenIds, navigation, tokenId])

useAsyncEffect(
async () => {
if (!hasNamedTransactions) return
await dispatch(promptForContactsPermission())
},
[hasNamedTransactions],
'TransactionListScene contacts permission'
)

// Automatically navigate to the token activation confirmation scene if
// the token appears in the unactivatedTokenIds list once the wallet loads
// this state.
Expand Down
32 changes: 2 additions & 30 deletions src/hooks/redux/useContactThumbnail.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,14 @@
import * as React from 'react'
import { check } from 'react-native-permissions'

import { maybeShowContactsPermissionModal } from '../../components/modals/ContactsPermissionModal'
import { requestContactsPermission } from '../../components/services/PermissionsManager'
import { MERCHANT_CONTACTS } from '../../constants/MerchantContacts'
import { permissionNames } from '../../reducers/PermissionsReducer'
import { useDispatch, useSelector } from '../../types/reactRedux'
import { useSelector } from '../../types/reactRedux'
import { normalizeForSearch } from '../../util/utils'
import { useAsyncEffect } from '../useAsyncEffect'

/**
* Looks up a thumbnail image for a contact. Will show a contacts permission
* request modal if we haven't shown it before and the system contacts
* permission is not granted.
* Looks up a thumbnail image for a contact using existing contacts data.
*/
export const useContactThumbnail = (name?: string): string | undefined => {
const contacts = useSelector(state => state.contacts)
const dispatch = useDispatch()

useAsyncEffect(
async () => {
const contactsPermission = await check(permissionNames.contacts).catch(
(_error: unknown) => 'denied'
)

if (
contactsPermission !== 'granted' &&
contactsPermission !== 'limited'
) {
const result = await dispatch(maybeShowContactsPermissionModal())
if (result === 'allow') {
await requestContactsPermission(true)
}
}
},
[],
'useContactThumbnail'
)

return React.useMemo(() => {
if (name == null) return
Expand Down
Loading