Skip to content

Commit

Permalink
chore: add feature gate for hide balances, fix eye icon alignment (#4431
Browse files Browse the repository at this point in the history
)

### Description

Add feature gate for the hide balances toggle, also make naming
consistent and fix alignment issue with eye icon.

### Test plan

Updated unit tests

Feature gate false:

![feature_gate_false](https://github.com/valora-inc/wallet/assets/140328381/b2a962f9-d655-493b-a256-ddac907f3d7e)


Feature gate true:

![feature_gate_true](https://github.com/valora-inc/wallet/assets/140328381/be4cdcfd-f305-4fa4-9fd8-d06abbd2692c)

Eye icon for single token (now aligned with total):
![single
token](https://github.com/valora-inc/wallet/assets/140328381/56fd318b-394c-4367-b65c-a79ef7b0d87a)


Eye icon for multiple balances combined into total (same as before):
![multiple
tokens](https://github.com/valora-inc/wallet/assets/140328381/ded164bc-929d-4994-a944-9eb9b85f6fec)


### Related issues

N/A

### Backwards compatibility

Yes
  • Loading branch information
finnian0826 authored Nov 8, 2023
1 parent 6c99205 commit 8242a73
Show file tree
Hide file tree
Showing 10 changed files with 149 additions and 51 deletions.
12 changes: 6 additions & 6 deletions src/app/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export enum Actions {
PUSH_NOTIFICATIONS_PERMISSION_CHANGED = 'APP/PUSH_NOTIFICATIONS_PERMISSION_CHANGED',
IN_APP_REVIEW_REQUESTED = 'APP/IN_APP_REVIEW_REQUESTED',
NOTIFICATION_SPOTLIGHT_SEEN = 'APP/NOTIFICATION_SPOTLIGHT_SEEN',
TOGGLE_HIDE_BALANCES = 'APP/TOGGLE_HIDE_BALANCES',
TOGGLE_HIDE_HOME_BALANCES = 'APP/TOGGLE_HIDE_HOME_BALANCES',
}

export interface SetAppState {
Expand Down Expand Up @@ -170,8 +170,8 @@ export interface NotificationSpotlightSeen {
type: Actions.NOTIFICATION_SPOTLIGHT_SEEN
}

interface ToggleHideBalances {
type: Actions.TOGGLE_HIDE_BALANCES
interface ToggleHideHomeBalances {
type: Actions.TOGGLE_HIDE_HOME_BALANCES
}

export type ActionTypes =
Expand Down Expand Up @@ -201,7 +201,7 @@ export type ActionTypes =
| PushNotificationsPermissionChanged
| inAppReviewRequested
| NotificationSpotlightSeen
| ToggleHideBalances
| ToggleHideHomeBalances

export const setAppState = (state: string): SetAppState => ({
type: Actions.SET_APP_STATE,
Expand Down Expand Up @@ -369,8 +369,8 @@ export const notificationSpotlightSeen = (): NotificationSpotlightSeen => {
}
}

export const toggleHideBalances = (): ToggleHideBalances => {
export const toggleHideHomeBalances = (): ToggleHideHomeBalances => {
return {
type: Actions.TOGGLE_HIDE_BALANCES,
type: Actions.TOGGLE_HIDE_HOME_BALANCES,
}
}
2 changes: 1 addition & 1 deletion src/app/reducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ export const appReducer = (
...state,
inAppReviewLastInteractionTimestamp: action.inAppReviewLastInteractionTimestamp,
}
case Actions.TOGGLE_HIDE_BALANCES:
case Actions.TOGGLE_HIDE_HOME_BALANCES:
return {
...state,
hideHomeBalances: !state.hideHomeBalances,
Expand Down
42 changes: 42 additions & 0 deletions src/components/TokenBalance.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,48 @@ describe('FiatExchangeTokenBalance and HomeTokenBalance', () => {
expect(tree.getByTestId('EyeIcon')).toBeTruthy()
})

it('renders correctly when feature flag is off, hideBalance is false', async () => {
jest
.mocked(getFeatureGate)
.mockImplementation(
(featureGate) =>
featureGate !== StatsigFeatureGates.SHOW_ASSET_DETAILS_SCREEN &&
featureGate !== StatsigFeatureGates.SHOW_HIDE_HOME_BALANCES_TOGGLE
)
const store = createMockStore(defaultStore)

const tree = render(
<Provider store={store}>
<HomeTokenBalance />
</Provider>
)

expect(getElementText(tree.getByTestId('TotalTokenBalance'))).toEqual('$8.41')
expect(tree.queryByTestId('EyeIcon')).toBeFalsy()
expect(tree.queryByTestId('HiddenEyeIcon')).toBeFalsy()
})

it('renders correctly when feature flag is off, hideBalance is true', async () => {
jest
.mocked(getFeatureGate)
.mockImplementation(
(featureGate) =>
featureGate !== StatsigFeatureGates.SHOW_ASSET_DETAILS_SCREEN &&
featureGate !== StatsigFeatureGates.SHOW_HIDE_HOME_BALANCES_TOGGLE
)
const store = createMockStore({ ...defaultStore, app: { hideHomeBalances: true } })

const tree = render(
<Provider store={store}>
<HomeTokenBalance />
</Provider>
)

expect(getElementText(tree.getByTestId('TotalTokenBalance'))).toEqual('$8.41')
expect(tree.queryByTestId('EyeIcon')).toBeFalsy()
expect(tree.queryByTestId('HiddenEyeIcon')).toBeFalsy()
})

it('tracks analytics event when eye icon is pressed', async () => {
const store = createMockStore(defaultStore)

Expand Down
83 changes: 43 additions & 40 deletions src/components/TokenBalance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { useDispatch, useSelector } from 'react-redux'
import { hideAlert, showToast } from 'src/alert/actions'
import { AssetsEvents, FiatExchangeEvents, HomeEvents } from 'src/analytics/Events'
import ValoraAnalytics from 'src/analytics/ValoraAnalytics'
import { toggleHideBalances } from 'src/app/actions'
import { toggleHideHomeBalances } from 'src/app/actions'
import { hideHomeBalancesSelector } from 'src/app/selectors'
import Dialog from 'src/components/Dialog'
import { formatValueToDisplay } from 'src/components/TokenDisplay'
Expand Down Expand Up @@ -55,11 +55,11 @@ import { getSupportedNetworkIdsForTokenBalances } from 'src/tokens/utils'
function TokenBalance({
style = styles.balance,
singleTokenViewEnabled = true,
hideBalance = false,
showHideHomeBalancesToggle = false,
}: {
style?: StyleProp<TextStyle>
singleTokenViewEnabled?: boolean
hideBalance?: boolean
showHideHomeBalancesToggle?: boolean
}) {
const supportedNetworkIds = getSupportedNetworkIdsForTokenBalances()
const tokensWithUsdValue = useTokensWithUsdValue(supportedNetworkIds)
Expand All @@ -77,16 +77,25 @@ function TokenBalance({
: undefined
const { decimalSeparator } = getNumberFormatSettings()

const hideHomeBalanceState = useSelector(hideHomeBalancesSelector)
const hideBalance = showHideHomeBalancesToggle && hideHomeBalanceState
const balanceDisplay = hideBalance ? `XX${decimalSeparator}XX` : totalBalanceLocal?.toFormat(2)

if (tokenFetchError || tokenFetchLoading || tokensAreStale) {
// Show '-' if we haven't fetched the tokens yet or prices are stale
const TotalTokenBalance = ({ balanceDisplay }: { balanceDisplay: string }) => {
return (
<Text style={style} testID={'TotalTokenBalance'}>
{localCurrencySymbol}
{'-'}
</Text>
<View style={styles.row}>
<Text style={style} testID={'TotalTokenBalance'}>
{!hideBalance && localCurrencySymbol}
{balanceDisplay}
</Text>
{showHideHomeBalancesToggle && <HideBalanceButton hideBalance={hideBalance} />}
</View>
)
}

if (tokenFetchError || tokenFetchLoading || tokensAreStale) {
// Show '-' if we haven't fetched the tokens yet or prices are stale
return <TotalTokenBalance balanceDisplay={'-'} />
} else if (
singleTokenViewEnabled &&
tokensWithUsdValue.length === 1 &&
Expand All @@ -97,10 +106,7 @@ function TokenBalance({
<View style={styles.oneBalance}>
<Image source={{ uri: tokensWithUsdValue[0].imageUrl }} style={styles.tokenImg} />
<View style={styles.column}>
<Text style={style} testID={'TotalTokenBalance'}>
{!hideBalance && localCurrencySymbol}
{balanceDisplay ?? '-'}
</Text>
<TotalTokenBalance balanceDisplay={balanceDisplay ?? '-'} />
{!hideBalance && (
<Text style={styles.tokenBalance}>
{formatValueToDisplay(tokenBalance)} {tokensWithUsdValue[0].symbol}
Expand All @@ -110,13 +116,21 @@ function TokenBalance({
</View>
)
} else {
return (
<Text style={style} testID={'TotalTokenBalance'}>
{!hideBalance && localCurrencySymbol}
{balanceDisplay ?? new BigNumber(0).toFormat(2)}
</Text>
)
return <TotalTokenBalance balanceDisplay={balanceDisplay ?? new BigNumber(0).toFormat(2)} />
}
}

function HideBalanceButton({ hideBalance }: { hideBalance: boolean }) {
const dispatch = useDispatch()
const eyeIconOnPress = () => {
ValoraAnalytics.track(hideBalance ? HomeEvents.show_balances : HomeEvents.hide_balances)
dispatch(toggleHideHomeBalances())
}
return (
<Touchable onPress={eyeIconOnPress} hitSlop={variables.iconHitslop}>
{hideBalance ? <HiddenEyeIcon /> : <EyeIcon />}
</Touchable>
)
}

function useErrorMessageWithRefresh() {
Expand Down Expand Up @@ -218,7 +232,6 @@ export function AssetsTokenBalance({ showInfo }: { showInfo: boolean }) {

export function HomeTokenBalance() {
const { t } = useTranslation()
const dispatch = useDispatch()

const totalBalance = useTotalTokenBalance()
const tokenBalances = useTokensWithTokenBalance()
Expand All @@ -242,13 +255,6 @@ export function HomeTokenBalance() {

const [infoVisible, setInfoVisible] = useState(false)

const hideBalance = useSelector(hideHomeBalancesSelector)

const eyeIconOnPress = () => {
ValoraAnalytics.track(hideBalance ? HomeEvents.show_balances : HomeEvents.hide_balances)
dispatch(toggleHideBalances())
}

return (
<View style={styles.container} testID="HomeTokenBalance">
<View style={styles.title}>
Expand Down Expand Up @@ -278,19 +284,16 @@ export function HomeTokenBalance() {
</TouchableOpacity>
)}
</View>
<View style={styles.row}>
<TokenBalance
style={
getFeatureGate(StatsigFeatureGates.SHOW_ASSET_DETAILS_SCREEN)
? styles.totalBalance
: styles.balance
}
hideBalance={hideBalance}
/>
<Touchable onPress={eyeIconOnPress} hitSlop={variables.iconHitslop}>
{hideBalance ? <HiddenEyeIcon /> : <EyeIcon />}
</Touchable>
</View>
<TokenBalance
style={
getFeatureGate(StatsigFeatureGates.SHOW_ASSET_DETAILS_SCREEN)
? styles.totalBalance
: styles.balance
}
showHideHomeBalancesToggle={getFeatureGate(
StatsigFeatureGates.SHOW_HIDE_HOME_BALANCES_TOGGLE
)}
/>
</View>
)
}
Expand Down
1 change: 1 addition & 0 deletions src/statsig/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export const FeatureGates = {
[StatsigFeatureGates.USE_VIEM_FOR_WALLETCONNECT_TRANSACTIONS]: false,
[StatsigFeatureGates.USE_NEW_RECIPIENT_SCREEN]: false,
[StatsigFeatureGates.USE_NEW_SEND_FLOW]: false,
[StatsigFeatureGates.SHOW_HIDE_HOME_BALANCES_TOGGLE]: false,
}

export const ExperimentConfigs = {
Expand Down
1 change: 1 addition & 0 deletions src/statsig/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export enum StatsigFeatureGates {
USE_VIEM_FOR_WALLETCONNECT_TRANSACTIONS = 'use_viem_for_walletconnect_transactions',
USE_NEW_RECIPIENT_SCREEN = 'use_new_recipient_screen',
USE_NEW_SEND_FLOW = 'use_new_send_flow',
SHOW_HIDE_HOME_BALANCES_TOGGLE = 'show_hide_home_balances_toggle',
}

export enum StatsigExperiments {
Expand Down
30 changes: 29 additions & 1 deletion src/transactions/feed/SwapFeedItem.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { render } from '@testing-library/react-native'
import React from 'react'
import { Provider } from 'react-redux'
import { RootState } from 'src/redux/reducers'
import { getFeatureGate } from 'src/statsig'
import SwapFeedItem from 'src/transactions/feed/SwapFeedItem'
import {
Fee,
Expand All @@ -16,6 +17,8 @@ import { mockCeurTokenId, mockCusdTokenId } from 'test/values'

const MOCK_TX_HASH = '0x006b866d20452a24d1d90c7514422188cc7c5d873e2f1ed661ec3f810ad5331c'

jest.mock('src/statsig')

jest.mock('src/web3/networkConfig', () => {
const originalModule = jest.requireActual('src/web3/networkConfig')
return {
Expand All @@ -33,6 +36,10 @@ jest.mock('src/web3/networkConfig', () => {
})

describe('SwapFeedItem', () => {
beforeEach(() => {
jest.clearAllMocks()
jest.mocked(getFeatureGate).mockReturnValue(true)
})
function renderScreen({
storeOverrides = {},
inAmount,
Expand Down Expand Up @@ -117,7 +124,28 @@ describe('SwapFeedItem', () => {
expect(getElementText(getByTestId('SwapFeedItem/outgoingAmount'))).toEqual('-17.54 cEUR')
})

it('hides balance when flag is set', async () => {
it('still shows balances when feature gate false, hide balances root state true', async () => {
jest.mocked(getFeatureGate).mockReturnValue(false)
const { getByTestId } = renderScreen({
inAmount: {
tokenId: mockCeurTokenId,
value: 2.93,
},
outAmount: {
tokenId: mockCusdTokenId,
value: 2.87,
},
})

expect(getElementText(getByTestId('SwapFeedItem/title'))).toEqual('swapScreen.title')
expect(getElementText(getByTestId('SwapFeedItem/subtitle'))).toEqual(
'feedItemSwapPath, {"token1":"cUSD","token2":"cEUR"}'
)
expect(getElementText(getByTestId('SwapFeedItem/incomingAmount'))).toEqual('+2.93 cEUR')
expect(getElementText(getByTestId('SwapFeedItem/outgoingAmount'))).toEqual('-2.87 cUSD')
})

it('hides balance when feature gate true, root state hide home balances flag is set', async () => {
const { queryByTestId } = renderScreen({
inAmount: {
tokenId: mockCeurTokenId,
Expand Down
7 changes: 6 additions & 1 deletion src/transactions/feed/SwapFeedItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import TokenDisplay from 'src/components/TokenDisplay'
import Touchable from 'src/components/Touchable'
import { navigate } from 'src/navigator/NavigationService'
import { Screens } from 'src/navigator/Screens'
import { getFeatureGate } from 'src/statsig'
import { StatsigFeatureGates } from 'src/statsig/types'
import colors from 'src/styles/colors'
import fontStyles from 'src/styles/fonts'
import variables from 'src/styles/variables'
Expand All @@ -29,7 +31,10 @@ function SwapFeedItem({ exchange }: Props) {
navigate(Screens.TransactionDetailsScreen, { transaction: exchange })
ValoraAnalytics.track(HomeEvents.transaction_feed_item_select)
}
const hideBalance = useSelector(hideHomeBalancesSelector)

const hideHomeBalanceState = useSelector(hideHomeBalancesSelector)
const hideBalance =
getFeatureGate(StatsigFeatureGates.SHOW_HIDE_HOME_BALANCES_TOGGLE) && hideHomeBalanceState

return (
<Touchable testID="SwapFeedItem" onPress={handleTransferDetails}>
Expand Down
16 changes: 15 additions & 1 deletion src/transactions/feed/TransferFeedItem.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import FiatConnectQuote from 'src/fiatExchanges/quotes/FiatConnectQuote'
import { CICOFlow } from 'src/fiatExchanges/utils'
import { LocalCurrencyCode } from 'src/localCurrency/consts'
import { RootState } from 'src/redux/reducers'
import { getFeatureGate } from 'src/statsig'
import TransferFeedItem from 'src/transactions/feed/TransferFeedItem'
import {
Fee,
Expand Down Expand Up @@ -41,6 +42,8 @@ const MOCK_CONTACT = {
address: MOCK_ADDRESS,
}

jest.mock('src/statsig')

jest.mock('src/web3/networkConfig', () => {
const originalModule = jest.requireActual('src/web3/networkConfig')
return {
Expand All @@ -58,6 +61,10 @@ jest.mock('src/web3/networkConfig', () => {
})

describe('TransferFeedItem', () => {
beforeEach(() => {
jest.clearAllMocks()
jest.mocked(getFeatureGate).mockReturnValue(true)
})
function renderScreen({
storeOverrides = {},
type = TokenTransactionTypeV2.Sent,
Expand Down Expand Up @@ -618,7 +625,14 @@ describe('TransferFeedItem', () => {
})
})

it('hides balance when flag is set', async () => {
it('shows balance when feature gate false, root state hide home balances flag is set', async () => {
jest.mocked(getFeatureGate).mockReturnValue(false)
const { getByTestId } = renderScreen({ storeOverrides: { app: { hideHomeBalances: true } } })
expect(getByTestId('TransferFeedItem/amount')).toBeTruthy()
expect(getByTestId('TransferFeedItem/tokenAmount')).toBeTruthy()
})

it('hides balance when feature gate true, root state hide home balances flag is set', async () => {
const { queryByTestId } = renderScreen({ storeOverrides: { app: { hideHomeBalances: true } } })
expect(queryByTestId('TransferFeedItem/amount')).toBeNull()
expect(queryByTestId('TransferFeedItem/tokenAmount')).toBeNull()
Expand Down
Loading

0 comments on commit 8242a73

Please sign in to comment.