From 11a214637d53e7905d2883781c95a7e70af204d9 Mon Sep 17 00:00:00 2001
From: Finnian Jacobson-Schulte
<140328381+finnian0826@users.noreply.github.com>
Date: Thu, 10 Oct 2024 08:49:08 +1300
Subject: [PATCH] feat(positions): Make positions tappable (#6137)
### Description
If the positions corresponds to a pool, navigate to the pool info page,
otherwise open an internal browser with `manageUrl`.
### Test plan
**Manual:**
EarnPosition:
https://github.com/user-attachments/assets/43e67b34-5b06-4cec-b34e-36adcb2e4f84
Position:
https://github.com/user-attachments/assets/be13ed9c-81bf-4cbe-bd17-c310076a1a3e
### Related issues
- Fixes ACT-1383
### Backwards compatibility
Yes
### Network scalability
If a new NetworkId and/or Network are added in the future, the changes
in this PR will:
- [X] Continue to work without code changes, OR trigger a compilation
error (guaranteeing we find it when a new network is added)
---
src/positions/selectors.ts | 2 +-
src/positions/types.ts | 1 +
src/tokens/PositionItem.test.tsx | 46 +++++++++++++-
src/tokens/PositionItem.tsx | 103 ++++++++++++++++++-------------
test/RootStateSchema.json | 3 +
test/values.ts | 1 +
6 files changed, 109 insertions(+), 47 deletions(-)
diff --git a/src/positions/selectors.ts b/src/positions/selectors.ts
index 5331e27a080..257b879c845 100644
--- a/src/positions/selectors.ts
+++ b/src/positions/selectors.ts
@@ -25,7 +25,7 @@ export const allowHooksPreviewSelector = () =>
const positionsSelector = (state: RootState) =>
showPositionsSelector() ? state.positions.positions : []
-const earnPositionIdsSelector = (state: RootState) => state.positions.earnPositionIds
+export const earnPositionIdsSelector = (state: RootState) => state.positions.earnPositionIds
export const positionsStatusSelector = (state: RootState) =>
showPositionsSelector() ? state.positions.status : 'idle'
export const positionsFetchedAtSelector = (state: RootState) => state.positions.positionsFetchedAt
diff --git a/src/positions/types.ts b/src/positions/types.ts
index cc2aeece96a..4be9164973d 100644
--- a/src/positions/types.ts
+++ b/src/positions/types.ts
@@ -7,6 +7,7 @@ export interface PositionDisplayProps {
title: string
description: string
imageUrl: string
+ manageUrl?: string
}
type DataProps = EarnDataProps
diff --git a/src/tokens/PositionItem.test.tsx b/src/tokens/PositionItem.test.tsx
index 3d1a3a024d8..024caf9b3cf 100644
--- a/src/tokens/PositionItem.test.tsx
+++ b/src/tokens/PositionItem.test.tsx
@@ -1,12 +1,14 @@
import { fireEvent, render } from '@testing-library/react-native'
import React from 'react'
import { Provider } from 'react-redux'
-import { AssetsEvents } from 'src/analytics/Events'
import AppAnalytics from 'src/analytics/AppAnalytics'
+import { AssetsEvents } from 'src/analytics/Events'
+import { navigate } from 'src/navigator/NavigationService'
+import { Screens } from 'src/navigator/Screens'
import { AppTokenPosition } from 'src/positions/types'
import { PositionItem } from 'src/tokens/PositionItem'
import { createMockStore } from 'test/utils'
-import { mockPositions } from 'test/values'
+import { mockEarnPositions, mockPositions } from 'test/values'
beforeEach(() => {
jest.clearAllMocks()
@@ -34,6 +36,46 @@ describe('PositionItem', () => {
})
})
+ it('navigates to internal browser manageUrl when tapped and manageUrl exists, not an earnPosition', () => {
+ const { getByText } = render(
+
+
+
+ )
+
+ fireEvent.press(getByText('MOO / CELO'))
+ expect(navigate).toHaveBeenCalledWith(Screens.WebViewScreen, { uri: 'mock-position.com' })
+ })
+ it('does not call navigate when tapped and manageUrl does not, not an earnPosition', () => {
+ const { getByText } = render(
+
+
+
+ )
+
+ fireEvent.press(getByText('G$ / cUSD'))
+ expect(navigate).not.toHaveBeenCalled()
+ })
+
+ it('navigates to EarnPoolInfoScreen when tapped if position is an earnPosition', () => {
+ const { getByText } = render(
+
+
+
+ )
+
+ fireEvent.press(getByText('USDC'))
+ expect(navigate).toHaveBeenCalledWith(Screens.EarnPoolInfoScreen, {
+ pool: mockEarnPositions[0],
+ })
+ })
+
it('shows the correct info for a position', () => {
const { getByText } = render(
diff --git a/src/tokens/PositionItem.tsx b/src/tokens/PositionItem.tsx
index 9776d33c946..51c76105e8d 100644
--- a/src/tokens/PositionItem.tsx
+++ b/src/tokens/PositionItem.tsx
@@ -1,16 +1,22 @@
import BigNumber from 'bignumber.js'
import React from 'react'
-import { StyleSheet, Text, View } from 'react-native'
-import { TouchableWithoutFeedback } from 'react-native-gesture-handler'
-import { AssetsEvents } from 'src/analytics/Events'
+import { Platform, StyleSheet, Text, View } from 'react-native'
+import { useSelector } from 'react-redux'
import AppAnalytics from 'src/analytics/AppAnalytics'
+import { AssetsEvents } from 'src/analytics/Events'
+import { openUrl } from 'src/app/actions'
import LegacyTokenDisplay from 'src/components/LegacyTokenDisplay'
-import { Position } from 'src/positions/types'
+import Touchable from 'src/components/Touchable'
+import { navigate } from 'src/navigator/NavigationService'
+import { Screens } from 'src/navigator/Screens'
+import { earnPositionIdsSelector } from 'src/positions/selectors'
+import { EarnPosition, Position } from 'src/positions/types'
+import { useDispatch } from 'src/redux/hooks'
import Colors from 'src/styles/colors'
import { typeScale } from 'src/styles/fonts'
import { Spacing } from 'src/styles/styles'
-import { Currency } from 'src/utils/currencies'
import { PositionIcon } from 'src/tokens/PositionIcon'
+import { Currency } from 'src/utils/currencies'
export const PositionItem = ({
position,
@@ -19,12 +25,15 @@ export const PositionItem = ({
position: Position
hideBalances?: boolean
}) => {
+ const dispatch = useDispatch()
+
const balanceInDecimal =
position.type === 'contract-position' ? undefined : new BigNumber(position.balance)
const balanceUsd =
position.type === 'contract-position'
? new BigNumber(position.balanceUsd)
: new BigNumber(position.balance).multipliedBy(position.priceUsd)
+ const earnPositionIds = useSelector(earnPositionIdsSelector)
const onPress = () => {
AppAnalytics.track(AssetsEvents.tap_asset, {
@@ -36,50 +45,56 @@ export const PositionItem = ({
description: position.displayProps.description,
balanceUsd: balanceUsd.toNumber(),
})
+ const uri = position.displayProps.manageUrl
+ if (earnPositionIds.includes(position.positionId)) {
+ navigate(Screens.EarnPoolInfoScreen, { pool: position as EarnPosition })
+ } else if (uri) {
+ Platform.OS === 'android'
+ ? navigate(Screens.WebViewScreen, { uri })
+ : dispatch(openUrl(uri, true))
+ }
}
return (
-
-
-
+
+ <>
+
+
-
-
- {position.displayProps.title}
-
- {position.displayProps.description}
-
-
- {!hideBalances && (
-
- {balanceUsd.gt(0) || balanceUsd.lt(0) ? (
-
- ) : (
- // If the balance is 0 / NaN, display a dash instead
- // as it means we don't have a price for at least one of the underlying tokens
- -
- )}
- {balanceInDecimal && (
-
- )}
+
+
+ {position.displayProps.title}
+
+ {position.displayProps.description}
+
- )}
-
+ {!hideBalances && (
+
+ {balanceUsd.gt(0) || balanceUsd.lt(0) ? (
+
+ ) : (
+ // If the balance is 0 / NaN, display a dash instead
+ // as it means we don't have a price for at least one of the underlying tokens
+ -
+ )}
+ {balanceInDecimal && (
+
+ )}
+
+ )}
+ >
+
)
}
diff --git a/test/RootStateSchema.json b/test/RootStateSchema.json
index 550615e97d8..036ca3530c7 100644
--- a/test/RootStateSchema.json
+++ b/test/RootStateSchema.json
@@ -3201,6 +3201,9 @@
"imageUrl": {
"type": "string"
},
+ "manageUrl": {
+ "type": "string"
+ },
"title": {
"type": "string"
}
diff --git a/test/values.ts b/test/values.ts
index 91b3cdad5a5..69e3fe52508 100644
--- a/test/values.ts
+++ b/test/values.ts
@@ -1491,6 +1491,7 @@ export const mockPositions: Position[] = [
title: 'MOO / CELO',
description: 'Pool',
imageUrl: '',
+ manageUrl: 'mock-position.com',
},
tokens: [
{