Skip to content

Commit

Permalink
feat: add tab navigator (#5027)
Browse files Browse the repository at this point in the history
### Description

Adds the tab navigator and associated icons. Behavior is enabled with
the `use_tab_navigator` feature gate. Please disregard the header
display as this will be covered in ACT-1108.

| Android | iOS 15 Pro Max - Large Font | iOS SE (3rd Generation) | 
| ----- | ----- | ----- | 
|
![](https://github.com/valora-inc/wallet/assets/26950305/e65dbbd2-c449-4b17-8b7b-904bfa3463f9
"Android Assets") |
![](https://github.com/valora-inc/wallet/assets/26950305/d74ae00e-4ae8-4ae2-9a4b-bb54377da375
"iOS 15 Pro Max Assets") |
![](https://github.com/valora-inc/wallet/assets/26950305/ee815632-29ee-46de-9145-8a9c420ac01a
"iOS SE (3rd Generation) Assets") |

### Test plan

- [x] Tested locally on iOS with feature flag enabled and disabled
- [x] Tested locally on Android with feature flag enabled and disabled
- [x] Unit tests updated

### Related issues

- Fixes ACT-1103

### Backwards compatibility

Yes - behind the `use_tab_navigator` feature gate.

### Network scalability

N/A

---------

Co-authored-by: Satish Ravi <satish.ravi@valoraapp.com>
  • Loading branch information
MuckT and satish-ravi committed Mar 8, 2024
1 parent df00a4d commit d2ae03d
Show file tree
Hide file tree
Showing 17 changed files with 207 additions and 6 deletions.
12 changes: 12 additions & 0 deletions locales/base/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -2167,5 +2167,17 @@
"description": "No more messy wallet addresses! Send crypto to other Celo users with just a phone number",
"startButtonLabel": "Link Phone Number",
"later": "I'll do this later"
},
"bottomTabsNavigator": {
"wallet": {
"tabName": "Wallet",
"title": "My Wallet"
},
"home": {
"tabName": "Home"
},
"discover": {
"tabName": "Discover"
}
}
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
"@react-native-firebase/remote-config": "^17.5.0",
"@react-native-masked-view/masked-view": "^0.3.1",
"@react-native-picker/picker": "^2.6.1",
"@react-navigation/bottom-tabs": "^6.5.16",
"@react-navigation/devtools": "^6.0.23",
"@react-navigation/drawer": "^6.6.11",
"@react-navigation/elements": "^1.3.26",
Expand Down
17 changes: 17 additions & 0 deletions src/icons/navigator/Discover.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import * as React from 'react'
import Svg, { Path } from 'react-native-svg'

interface Props {
color?: string
size?: number
}

const Discover = ({ color, size }: Props) => (
<Svg width={size} height={size} viewBox="0 0 20 20" fill="none">
<Path
fill={color}
d="M15.417 14.492a1.672 1.672 0 0 0-1.584-1.159H13v-2.5a.833.833 0 0 0-.833-.833h-5V8.333h1.666a.833.833 0 0 0 .834-.833V5.833h1.666A1.667 1.667 0 0 0 13 4.167v-.342a6.654 6.654 0 0 1 2.417 10.667Zm-5.75 2.116A6.657 6.657 0 0 1 3.833 10c0-.517.067-1.017.175-1.492L8 12.5v.833A1.667 1.667 0 0 0 9.667 15M10.5 1.667a8.333 8.333 0 1 0 0 16.666 8.333 8.333 0 0 0 0-16.666Z"
/>
</Svg>
)
export default Discover
17 changes: 17 additions & 0 deletions src/icons/navigator/Home.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import * as React from 'react'
import Svg, { Path } from 'react-native-svg'

interface Props {
color?: string
size?: number
}

const Home = ({ color, size }: Props) => (
<Svg width={size} height={size} viewBox="0 0 20 20" fill="none">
<Path
fill={color}
d="M11.844 15.809c.603-4.704 2.814-7.38 6.156-9.807L16.291 3.75c-2.186 1.676-4.572 4.053-5.678 7.33-.904-2.677-2.789-5.028-5.83-7.33L3 6.052c3.794 2.702 5.704 5.729 6.231 9.757h2.613Z"
/>
</Svg>
)
export default Home
17 changes: 17 additions & 0 deletions src/icons/navigator/Wallet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import * as React from 'react'
import Svg, { Path } from 'react-native-svg'

interface Props {
color?: string
size?: number
}

const Wallet = ({ color, size }: Props) => (
<Svg width={size} height={size} viewBox="0 0 20 20" fill="none">
<Path
fill={color}
d="M13.333 11.25c.361 0 .66-.118.896-.354s.354-.535.354-.896-.118-.66-.354-.896a1.214 1.214 0 0 0-.896-.354c-.36 0-.66.118-.896.354a1.214 1.214 0 0 0-.354.896c0 .361.118.66.354.896.237.236.535.354.896.354ZM4.167 17.5c-.459 0-.851-.163-1.178-.49a1.602 1.602 0 0 1-.489-1.177V4.167c0-.459.163-.851.49-1.178.327-.326.719-.49 1.177-.489h11.666c.459 0 .851.163 1.178.49.326.327.49.719.489 1.177V6.25h-1.667V4.167H4.167v11.666h11.666V13.75H17.5v2.083c0 .459-.163.851-.49 1.178-.327.326-.719.49-1.177.489H4.167Zm6.666-3.333c-.458 0-.85-.164-1.177-.49a1.602 1.602 0 0 1-.49-1.177v-5c0-.458.164-.85.49-1.178.327-.326.72-.49 1.177-.489h5.834c.458 0 .85.164 1.177.49.327.327.49.72.49 1.177v5c0 .458-.164.85-.49 1.178-.327.326-.72.49-1.177.489h-5.834Zm5.834-1.667v-5h-5.834v5h5.834Z"
/>
</Svg>
)
export default Wallet
8 changes: 6 additions & 2 deletions src/navigator/NavigationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import {
requestPincodeInput,
} from 'src/pincode/authentication'
import { store } from 'src/redux/store'
import { getFeatureGate } from 'src/statsig'
import { StatsigFeatureGates } from 'src/statsig/types'
import { isUserCancelledError } from 'src/storage/keychain'
import { ensureError } from 'src/utils/ensureError'
import Logger from 'src/utils/Logger'
Expand Down Expand Up @@ -224,7 +226,7 @@ export async function isBottomSheetVisible(screen: Screens) {
}

interface NavigateHomeOptions {
params?: StackParamList[Screens.DrawerNavigator]
params?: StackParamList[Screens.DrawerNavigator] | StackParamList[Screens.TabNavigator]
}

/***
Expand All @@ -238,7 +240,9 @@ export function navigateHome(options?: NavigateHomeOptions) {
setTimeout(() => {
navigationRef.current?.reset({
index: 0,
routes: [{ name: Screens.DrawerNavigator, params }],
routes: getFeatureGate(StatsigFeatureGates.USE_TAB_NAVIGATOR)
? [{ name: Screens.TabNavigator, params }]
: [{ name: Screens.DrawerNavigator, params }],
})
}, timeout)
}
Expand Down
2 changes: 2 additions & 0 deletions src/navigator/Navigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ import {
} from 'src/navigator/Headers'
import QRNavigator from 'src/navigator/QRNavigator'
import { Screens } from 'src/navigator/Screens'
import TabNavigator from 'src/navigator/TabNavigator'
import { getInitialRoute } from 'src/navigator/initialRoute'
import { StackParamList } from 'src/navigator/types'
import NftsInfoCarousel from 'src/nfts/NftsInfoCarousel'
Expand Down Expand Up @@ -602,6 +603,7 @@ export function MainStackScreen() {

return (
<Stack.Navigator initialRouteName={initialRouteName} screenOptions={emptyHeader}>
<Stack.Screen name={Screens.TabNavigator} component={TabNavigator} options={noHeader} />
<Stack.Screen name={Screens.DrawerNavigator} component={DrawerNavigator} options={noHeader} />
{commonScreens(Stack)}
{sendScreens(Stack)}
Expand Down
4 changes: 4 additions & 0 deletions src/navigator/Screens.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ export enum Screens {
Support = 'Support',
SupportContact = 'SupportContact',
SwapScreenWithBack = 'SwapScreenWithBack',
TabNavigator = 'TabNavigator',
TabDiscover = 'TabDiscover',
TabHome = 'TabHome',
TabWallet = 'TabWallet',
TokenDetails = 'TokenDetails',
TokenDetailsMoreActions = 'TokenDetailsMoreActions',
TokenImport = 'TokenImport',
Expand Down
85 changes: 85 additions & 0 deletions src/navigator/TabNavigator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
import { NativeStackScreenProps } from '@react-navigation/native-stack'
import React from 'react'
import { useTranslation } from 'react-i18next'
import { StyleSheet } from 'react-native'
import DAppsExplorerScreenSearchFilter from 'src/dappsExplorer/DAppsExplorerScreenSearchFilter'
import WalletHome from 'src/home/WalletHome'
import Discover from 'src/icons/navigator/Discover'
import Home from 'src/icons/navigator/Home'
import Wallet from 'src/icons/navigator/Wallet'
import { Screens } from 'src/navigator/Screens'
import { StackParamList } from 'src/navigator/types'
import Colors from 'src/styles/colors'
import { typeScale } from 'src/styles/fonts'
import { Spacing } from 'src/styles/styles'
import variables from 'src/styles/variables'
import AssetsScreen from 'src/tokens/Assets'

const Tab = createBottomTabNavigator()

type Props = NativeStackScreenProps<StackParamList, Screens.TabNavigator>

export default function TabNavigator({ route }: Props) {
const initialScreen = route.params?.initialScreen ?? Screens.TabHome
const { t } = useTranslation()

return (
<Tab.Navigator
initialRouteName={initialScreen}
screenOptions={{
headerShown: true,
headerShadowVisible: false,
headerTitleAllowFontScaling: false,
tabBarActiveTintColor: Colors.black,
tabBarInactiveTintColor: Colors.gray3,
tabBarLabelStyle: styles.label,
tabBarItemStyle: styles.tabBarItem,
tabBarAllowFontScaling: false,
tabBarStyle: {
height: variables.height * 0.1,
},
}}
>
<Tab.Screen
// TODO(act-1104) new assets screen
name={Screens.TabWallet}
// @ts-expect-error Type '{}' is missing the following properties from type 'Props': navigation, route
component={AssetsScreen}
options={{
tabBarLabel: t('bottomTabsNavigator.wallet.tabName') as string,
tabBarIcon: Wallet,
}}
/>
<Tab.Screen
// TODO(act-1105) new home tab screen
name={Screens.TabHome}
component={WalletHome}
options={{
freezeOnBlur: false,
lazy: false,
tabBarLabel: t('bottomTabsNavigator.home.tabName') as string,
tabBarIcon: Home,
}}
/>
<Tab.Screen
// TODO(act-1106) discover tab screen
name={Screens.TabDiscover}
component={DAppsExplorerScreenSearchFilter}
options={{
tabBarLabel: t('bottomTabsNavigator.discover.tabName') as string,
tabBarIcon: Discover,
}}
/>
</Tab.Navigator>
)
}

const styles = StyleSheet.create({
label: {
...typeScale.labelSemiBoldSmall,
},
tabBarItem: {
paddingVertical: Spacing.Smallest8,
},
})
16 changes: 14 additions & 2 deletions src/navigator/initialRoute.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { MultichainBetaStatus } from 'src/app/actions'
import { getInitialRoute } from 'src/navigator/initialRoute'
import { Screens } from 'src/navigator/Screens'
import { getFeatureGate } from 'src/statsig'
import { StatsigFeatureGates } from 'src/statsig/types'

jest.mock('src/statsig/index')

Expand Down Expand Up @@ -98,16 +99,27 @@ describe('initialRoute', () => {
})

it('returns drawer navigator if all onboarding complete, multichain beta is opted in and feature gate is on', () => {
jest.mocked(getFeatureGate).mockReturnValue(true)
jest
.mocked(getFeatureGate)
.mockImplementation((featureGate) => featureGate !== StatsigFeatureGates.USE_TAB_NAVIGATOR)
expect(
getInitialRoute({ ...defaultArgs, multichainBetaStatus: MultichainBetaStatus.OptedIn })
).toEqual(Screens.DrawerNavigator)
})

it('returns drawer navigator if all onboarding complete, multichain beta is opted out and feature gate is on', () => {
jest.mocked(getFeatureGate).mockReturnValue(true)
jest
.mocked(getFeatureGate)
.mockImplementation((featureGate) => featureGate !== StatsigFeatureGates.USE_TAB_NAVIGATOR)
expect(
getInitialRoute({ ...defaultArgs, multichainBetaStatus: MultichainBetaStatus.OptedOut })
).toEqual(Screens.DrawerNavigator)
})

it('returns tab navigator if all onboarding complete, multichain beta is opted in and use_tab_navigator feature gate is on', () => {
jest.mocked(getFeatureGate).mockReturnValue(true)
expect(
getInitialRoute({ ...defaultArgs, multichainBetaStatus: MultichainBetaStatus.OptedIn })
).toEqual(Screens.TabNavigator)
})
})
6 changes: 5 additions & 1 deletion src/navigator/initialRoute.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ export function getInitialRoute({
) {
return Screens.MultichainBeta
} else {
return Screens.DrawerNavigator
if (getFeatureGate(StatsigFeatureGates.USE_TAB_NAVIGATOR)) {
return Screens.TabNavigator
} else {
return Screens.DrawerNavigator
}
}
}
11 changes: 11 additions & 0 deletions src/navigator/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,17 @@ export type StackParamList = {
}
| undefined
[Screens.SwapScreenWithBack]: { fromTokenId: string } | undefined
[Screens.TabDiscover]: undefined
[Screens.TabHome]: undefined
[Screens.TabWallet]:
| {
activeTab: AssetTabType
}
| undefined
[Screens.TabNavigator]: {
initialScreen?: Screens
fromModal?: boolean
}
[Screens.TokenDetails]: { tokenId: string }
[Screens.TokenImport]: undefined
[Screens.TransactionDetailsScreen]: {
Expand Down
1 change: 1 addition & 0 deletions src/statsig/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const FeatureGates = {
[StatsigFeatureGates.SHOW_NFT_CELEBRATION]: false,
[StatsigFeatureGates.SHOW_NFT_REWARD]: false,
[StatsigFeatureGates.SHOW_JUMPSTART_SEND]: false,
[StatsigFeatureGates.USE_TAB_NAVIGATOR]: 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 @@ -30,6 +30,7 @@ export enum StatsigFeatureGates {
SHOW_NFT_CELEBRATION = 'show_nft_celebration',
SHOW_NFT_REWARD = 'show_nft_reward',
SHOW_JUMPSTART_SEND = 'show_jumpstart_send',
USE_TAB_NAVIGATOR = 'use_tab_navigator',
}

export enum StatsigExperiments {
Expand Down
2 changes: 1 addition & 1 deletion src/tokens/Assets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import { useTokenPricesAreStale, useTotalTokenBalance } from 'src/tokens/hooks'
import { AssetTabType } from 'src/tokens/types'
import { getSupportedNetworkIdsForTokenBalances } from 'src/tokens/utils'

type Props = NativeStackScreenProps<StackParamList, Screens.Assets>
type Props = NativeStackScreenProps<StackParamList, Screens.Assets | Screens.TabWallet>

// offset relative to the bottom of the non sticky header component, where the
// screen header opacity animation starts
Expand Down
4 changes: 4 additions & 0 deletions test/RootStateSchema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2671,6 +2671,10 @@
"Support",
"SupportContact",
"SwapScreenWithBack",
"TabDiscover",
"TabHome",
"TabNavigator",
"TabWallet",
"TokenDetails",
"TokenDetailsMoreActions",
"TokenImport",
Expand Down
9 changes: 9 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2986,6 +2986,15 @@
resolved "https://registry.yarnpkg.com/@react-native/polyfills/-/polyfills-2.0.0.tgz#4c40b74655c83982c8cf47530ee7dc13d957b6aa"
integrity sha512-K0aGNn1TjalKj+65D7ycc1//H9roAQ51GJVk5ZJQFb2teECGmzd86bYDC0aYdbRf7gtovescq4Zt6FR0tgXiHQ==

"@react-navigation/bottom-tabs@^6.5.16":
version "6.5.16"
resolved "https://registry.yarnpkg.com/@react-navigation/bottom-tabs/-/bottom-tabs-6.5.16.tgz#8b9b2fb482cdcdfcad5146a77878a48090fb64b5"
integrity sha512-zw6hlP1YI4APbOoeJSg1JS9h3OPZIp4O2ccAkiIPgw7T6wyKs8dGJyrkkUtTryXoRUqL1D14xp6K1Etgqm7F2A==
dependencies:
"@react-navigation/elements" "^1.3.26"
color "^4.2.3"
warn-once "^0.1.0"

"@react-navigation/core@^6.4.13":
version "6.4.13"
resolved "https://registry.yarnpkg.com/@react-navigation/core/-/core-6.4.13.tgz#27cf33d963f59aeeb36f27514a9744ab818097b3"
Expand Down

0 comments on commit d2ae03d

Please sign in to comment.