Skip to content

Commit

Permalink
feat: add tab bar header navigation (#5082)
Browse files Browse the repository at this point in the history
### Description

Adds the top navigation for tab navigation screens. In the screen shots
below, the header is the main point of focus as different wallets were
used to take enabled and disabled screenshots. Header icons were
adjusted to allow for ripples on tap on Android and a few changes had to
be made to keep alignment with the existing side `DrawerTopBar.tsx`.

#### Screenshots

| Android Disabled | Android Enabled | iOS Disabled | iOS Enabled |
| ----- | ----- | ----- | ----- |
|
![](https://github.com/valora-inc/wallet/assets/26950305/51005dd6-ceaf-43a3-8e98-a46902cdae63
"Android Tab Nav Disabled") |
![](https://github.com/valora-inc/wallet/assets/26950305/db0630b1-c0bb-44a4-8720-427f2f5599bf
"Android Tab Nav Enabled") |
![](https://github.com/valora-inc/wallet/assets/26950305/cddf91e4-9eab-4d25-8e8d-2629d53ebc13
"iOS Tab Nav Disabled") |
![](https://github.com/valora-inc/wallet/assets/26950305/d2ad89d8-9a9f-4bcd-8621-a298f1916f7c
"iOS Tab Nav Enabled") |

### Test plan

- [x] Tested locally on iOS
- [x] Tested locally on Android
- [x] Unit tests added

### Related issues

- Fixes #ACT-1108

### Backwards compatibility

Yes

### Network scalability

N/A
  • Loading branch information
MuckT committed Mar 20, 2024
1 parent b3b051b commit f529920
Show file tree
Hide file tree
Showing 20 changed files with 352 additions and 77 deletions.
3 changes: 2 additions & 1 deletion locales/base/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -2201,7 +2201,8 @@
"title": "Welcome"
},
"discover": {
"tabName": "Discover"
"tabName": "Discover",
"title": "Discover"
}
},
"jumpstartStatus": {
Expand Down
1 change: 1 addition & 0 deletions src/analytics/Events.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export enum AppEvents {

export enum HomeEvents {
hamburger_tapped = 'hamburger_tapped',
account_circle_tapped = 'account_circle_tapped',
drawer_navigation = 'drawer_navigation',
drawer_address_copy = 'drawer_address_copy',
profile_address_copy = 'profile_address_copy',
Expand Down
1 change: 1 addition & 0 deletions src/analytics/Properties.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ interface AppEventsProperties {

interface HomeEventsProperties {
[HomeEvents.hamburger_tapped]: undefined
[HomeEvents.account_circle_tapped]: undefined
[HomeEvents.drawer_navigation]: {
navigateTo: string
}
Expand Down
1 change: 1 addition & 0 deletions src/analytics/docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export const eventDocs: Record<AnalyticsEventType, string> = {
[AppEvents.multichain_beta_contact_support]: `When the user taps the Contact Support button on the multichain beta screen`,
[AppEvents.handle_deeplink]: `When a deeplink that leads into the app is detected and handled`,
[HomeEvents.hamburger_tapped]: ``,
[HomeEvents.account_circle_tapped]: `When the account circle used in the tab navigation is tapped`,
[HomeEvents.drawer_navigation]: ``,
[HomeEvents.drawer_address_copy]: ``,
[HomeEvents.profile_address_copy]: `When a user copies their wallet address from the profile screen`,
Expand Down
45 changes: 45 additions & 0 deletions src/components/AccountCircleButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from 'react'
import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native'
import { HomeEvents } from 'src/analytics/Events'
import ValoraAnalytics from 'src/analytics/ValoraAnalytics'
import Touchable from 'src/components/Touchable'
import AccountCircle from 'src/icons/AccountCircle'
import { navigate } from 'src/navigator/NavigationService'
import { Screens } from 'src/navigator/Screens'
import { Spacing } from 'src/styles/styles'

interface Props {
style?: StyleProp<ViewStyle>
size?: number
testID?: string
}

export default function AccountCircleButton({ style, size, testID }: Props) {
const onPress = () => {
ValoraAnalytics.track(HomeEvents.account_circle_tapped)
navigate(Screens.ProfileMenu)
}

return (
<View style={styles.container}>
<Touchable
testID={testID}
onPress={onPress}
style={[style, styles.button]}
borderRadius={Spacing.Thick24}
>
<AccountCircle size={size} />
</Touchable>
</View>
)
}

const styles = StyleSheet.create({
container: {
justifyContent: 'center',
alignItems: 'center',
},
button: {
padding: Spacing.Small12,
},
})
31 changes: 23 additions & 8 deletions src/components/QrScanButton.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React from 'react'
import { StyleProp, ViewStyle } from 'react-native'
import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native'
import { QrScreenEvents } from 'src/analytics/Events'
import ValoraAnalytics from 'src/analytics/ValoraAnalytics'
import Touchable from 'src/components/Touchable'
import ScanIcon from 'src/icons/ScanIcon'
import { navigate } from 'src/navigator/NavigationService'
import { Screens } from 'src/navigator/Screens'
import { TopBarIconButton } from 'src/navigator/TopBarButton'
import { Spacing } from 'src/styles/styles'

interface Props {
style?: StyleProp<ViewStyle>
Expand All @@ -20,11 +21,25 @@ export default function QrScanButton({ style, size, testID }: Props) {
}

return (
<TopBarIconButton
testID={testID}
icon={<ScanIcon size={size} />}
onPress={onPress}
style={style}
/>
<View style={styles.container}>
<Touchable
testID={testID}
onPress={onPress}
style={[style, styles.button]}
borderRadius={Spacing.Thick24}
>
<ScanIcon size={size} />
</Touchable>
</View>
)
}

const styles = StyleSheet.create({
container: {
justifyContent: 'center',
alignItems: 'center',
},
button: {
padding: Spacing.Small12,
},
})
2 changes: 1 addition & 1 deletion src/components/TokenBalance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ export function AssetsTokenBalance({
isWalletTab,
}: {
showInfo: boolean
// temporary parameter while we build the tab navigator, should be cleaned up
// TODO(act-1133): temporary parameter while we build the tab navigator, should be cleaned up
// when we remove the DrawerNavigator
isWalletTab: boolean
}) {
Expand Down
78 changes: 60 additions & 18 deletions src/dappsExplorer/DAppsExplorerScreenSearchFilter.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import { NativeStackScreenProps } from '@react-navigation/native-stack'
import React, { useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { RefreshControl, SectionList, SectionListProps, StyleSheet, Text, View } from 'react-native'
import {
LayoutChangeEvent,
RefreshControl,
SectionList,
SectionListProps,
StyleSheet,
Text,
View,
} from 'react-native'
import { ScrollView } from 'react-native-gesture-handler'
import Animated from 'react-native-reanimated'
import Animated, { useAnimatedScrollHandler, useSharedValue } from 'react-native-reanimated'
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'
import { DappExplorerEvents } from 'src/analytics/Events'
import ValoraAnalytics from 'src/analytics/ValoraAnalytics'
Expand Down Expand Up @@ -31,10 +39,11 @@ import { currentLanguageSelector } from 'src/i18n/selectors'
import DrawerTopBar from 'src/navigator/DrawerTopBar'
import { styles as headerStyles } from 'src/navigator/Headers'
import { Screens } from 'src/navigator/Screens'
import useScrollAwareHeader from 'src/navigator/ScrollAwareHeader'
import { StackParamList } from 'src/navigator/types'
import { useDispatch, useSelector } from 'src/redux/hooks'
import colors from 'src/styles/colors'
import fontStyles from 'src/styles/fonts'
import { Colors } from 'src/styles/colors'
import fontStyles, { typeScale } from 'src/styles/fonts'
import { Spacing } from 'src/styles/styles'

const AnimatedSectionList =
Expand All @@ -52,21 +61,24 @@ type Props = NativeStackScreenProps<
Screens.DAppsExplorerScreen | Screens.TabDiscover
>

export function DAppsExplorerScreenSearchFilter({ route }: Props) {
export function DAppsExplorerScreenSearchFilter({ navigation, route }: Props) {
const { t } = useTranslation()

// temporary parameter while we build the tab navigator, should be cleaned up
// TODO(act-1133): temporary parameter while we build the tab navigator, should be cleaned up
// when we remove the drawer
const isTabNavigator = !!route.params?.isTabNavigator

const insets = useSafeAreaInsets()

const sectionListRef = useRef<SectionList>(null)
const scrollPosition = useRef(new Animated.Value(0)).current

// Old scroll handler for Drawer Header
const scrollPositionValue = useRef(new Animated.Value(0)).current
const onScroll = Animated.event([{ nativeEvent: { contentOffset: { y: scrollPositionValue } } }])

const horizontalScrollView = useRef<ScrollView>(null)
const dappRankingsBottomSheetRef = useRef<BottomSheetRefType>(null)

const onScroll = Animated.event([{ nativeEvent: { contentOffset: { y: scrollPosition } } }])
const dispatch = useDispatch()
const loading = useSelector(dappsListLoadingSelector)
const error = useSelector(dappsListErrorSelector)
Expand Down Expand Up @@ -136,6 +148,26 @@ export function DAppsExplorerScreenSearchFilter({ route }: Props) {
})
}

// Scroll Aware Header
const scrollPosition = useSharedValue(0)
const [titleHeight, setTitleHeight] = useState(0)

const handleMeasureTitleHeight = (event: LayoutChangeEvent) => {
setTitleHeight(event.nativeEvent.layout.height)
}

const handleScroll = useAnimatedScrollHandler((event) => {
scrollPosition.value = event.contentOffset.y
})

useScrollAwareHeader({
navigation,
title: isTabNavigator ? t('bottomTabsNavigator.discover.title') : '',
scrollPosition,
startFadeInPosition: titleHeight - titleHeight * 0.33,
animationDistance: titleHeight * 0.33,
})

const sections: SectionData[] = useMemo(() => {
const dappsMatchingFilter = selectedFilter
? nonFavoriteDappsWithCategoryNames.filter((dapp) => selectedFilter.filterFn(dapp))
Expand Down Expand Up @@ -193,7 +225,7 @@ export function DAppsExplorerScreenSearchFilter({ route }: Props) {
<DrawerTopBar
rightElement={<QrScanButton testID={'DAppsExplorerScreen/QRScanButton'} />}
middleElement={<Text style={headerStyles.headerTitle}>{t('dappsScreen.title')}</Text>}
scrollPosition={scrollPosition}
scrollPosition={scrollPositionValue}
/>
)}
<>
Expand All @@ -206,8 +238,8 @@ export function DAppsExplorerScreenSearchFilter({ route }: Props) {
<AnimatedSectionList
refreshControl={
<RefreshControl
tintColor={colors.primary}
colors={[colors.primary]}
tintColor={Colors.primary}
colors={[Colors.primary]}
style={styles.refreshControl}
refreshing={loading}
onRefresh={() => dispatch(fetchDappsList())}
Expand All @@ -221,14 +253,19 @@ export function DAppsExplorerScreenSearchFilter({ route }: Props) {
}
ListHeaderComponent={
<>
{isTabNavigator && (
<Text onLayout={handleMeasureTitleHeight} style={styles.title}>
{t('bottomTabsNavigator.discover.title')}
</Text>
)}
<DappFeaturedActions onPressShowDappRankings={handleShowDappRankings} />
<SearchInput
onChangeText={(text) => {
setSearchTerm(text)
}}
value={searchTerm}
multiline={false}
placeholderTextColor={colors.gray4}
placeholderTextColor={Colors.gray4}
underlineColorAndroid="transparent"
placeholder={t('dappsScreen.searchPlaceHolder') ?? undefined}
showClearButton={true}
Expand All @@ -237,8 +274,8 @@ export function DAppsExplorerScreenSearchFilter({ route }: Props) {
<FilterChipsCarousel
chips={filterChips}
onSelectChip={handleToggleFilterChip}
primaryColor={colors.infoDark}
secondaryColor={colors.infoLight}
primaryColor={Colors.infoDark}
secondaryColor={Colors.infoLight}
style={styles.dappFilterView}
forwardedRef={horizontalScrollView}
/>
Expand All @@ -252,7 +289,7 @@ export function DAppsExplorerScreenSearchFilter({ route }: Props) {
// Workaround iOS setting an incorrect automatic inset at the top
scrollIndicatorInsets={{ top: 0.01 }}
scrollEventThrottle={16}
onScroll={onScroll}
onScroll={isTabNavigator ? handleScroll : onScroll}
sections={sections}
renderItem={({ item: dapp, index, section }) => {
return (
Expand Down Expand Up @@ -322,19 +359,19 @@ const styles = StyleSheet.create({
flexGrow: 1,
},
refreshControl: {
backgroundColor: colors.white,
backgroundColor: Colors.white,
},
sectionList: {
flex: 1,
},
sectionTitle: {
...fontStyles.label,
color: colors.gray4,
color: Colors.gray4,
marginTop: Spacing.Large32,
},
disclaimer: {
...fontStyles.xsmall,
color: colors.gray4,
color: Colors.gray4,
textAlign: 'center',
marginTop: Spacing.Large32,
marginBottom: Spacing.Regular16,
Expand All @@ -343,6 +380,11 @@ const styles = StyleSheet.create({
flex: 1,
justifyContent: 'flex-end',
},
title: {
...typeScale.titleMedium,
color: Colors.black,
marginBottom: Spacing.Large32,
},
})

export default DAppsExplorerScreenSearchFilter
16 changes: 9 additions & 7 deletions src/escrow/EscrowedPaymentListScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@ import { EscrowedPayment } from 'src/escrow/actions'
import EscrowedPaymentListItem from 'src/escrow/EscrowedPaymentListItem'
import { getReclaimableEscrowPayments } from 'src/escrow/reducer'
import i18n from 'src/i18n'
import {
NotificationList,
titleWithBalanceNavigationOptions,
} from 'src/notifications/NotificationList'
import { HeaderTitleWithBalance, headerWithBackButton } from 'src/navigator/Headers'
import { NotificationList } from 'src/notifications/NotificationList'
import { useSelector } from 'src/redux/hooks'
import { Spacing } from 'src/styles/styles'
import { Currency } from 'src/utils/currencies'

export const listItemRenderer = (payment: EscrowedPayment, key: number | undefined = undefined) => {
return (
Expand All @@ -24,9 +23,12 @@ export default function EscrowedPaymentListScreen() {
return <NotificationList items={sentEscrowedPayments} listItemRenderer={listItemRenderer} />
}

EscrowedPaymentListScreen.navigationOptions = titleWithBalanceNavigationOptions(
i18n.t('escrowedPaymentReminder')
)
EscrowedPaymentListScreen.navigationOptions = () => ({
...headerWithBackButton,
headerTitle: () => (
<HeaderTitleWithBalance title={i18n.t('escrowedPaymentReminder')} token={Currency.Dollar} />
),
})

const styles = StyleSheet.create({
listItem: {
Expand Down

0 comments on commit f529920

Please sign in to comment.