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: [ {