Skip to content

Commit 48a3ef1

Browse files
Merge pull request #14504 from LedgerHQ/feat/LIVE-25665-add-wallet-40-header-and-tabs
feat(LIVE-25665): make Swap mobile wallet 40 ready
2 parents ac80d59 + f7f4baf commit 48a3ef1

35 files changed

+1681
-343
lines changed

.changeset/shiny-seas-invite.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
"live-mobile": minor
3+
---
4+
5+
Swap Wallet 4.0 implementation
6+
7+
- separate logic for screen state params etc from webview component
8+
- add new webview wrapper for Swap with Wallet 4.0 requirements
9+
- create subscreen navigation for Swap screens
10+
- use black as background for all Swap screens
11+
- make TopBar customizable so it can be used for Swap as well

apps/ledger-live-mobile/src/components/RootNavigator/BaseNavigator.tsx

Lines changed: 81 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import AccountSettingsNavigator from "./AccountSettingsNavigator";
3030
import PasswordAddFlowNavigator from "./PasswordAddFlowNavigator";
3131
import PasswordModifyFlowNavigator from "./PasswordModifyFlowNavigator";
3232
import SwapNavigator from "./SwapNavigator";
33+
import SwapSubScreensNavigator from "./SwapSubScreensNavigator";
3334
import PerpsNavigator from "./PerpsNavigator";
3435
import NotificationCenterNavigator from "./NotificationCenterNavigator";
3536
import AnalyticsAllocation from "~/screens/Analytics/Allocation";
@@ -98,6 +99,65 @@ import { AppState } from "react-native";
9899

99100
const Stack = createNativeStackNavigator<BaseNavigatorStackParamList>();
100101

102+
type OperationDetailsRouteProp = RouteProp<
103+
BaseNavigatorStackParamList,
104+
ScreenName.OperationDetails
105+
>;
106+
107+
const renderNullHeader = () => null;
108+
109+
function ScanRecipientHeaderRight() {
110+
const { colors } = useTheme();
111+
112+
return (
113+
<NavigationHeaderCloseButtonAdvanced
114+
color={colors.constant.white}
115+
preferDismiss={false}
116+
rounded
117+
/>
118+
);
119+
}
120+
121+
function FlowHeaderCloseButton() {
122+
return <NavigationHeaderCloseButtonAdvanced preferDismiss={false} />;
123+
}
124+
125+
function OperationDetailsHeaderLeft() {
126+
return <NavigationHeaderBackButton />;
127+
}
128+
129+
function OperationDetailsHeaderTitle() {
130+
const { t } = useTranslation();
131+
const route = useRoute<OperationDetailsRouteProp>();
132+
const operationType = route.params?.operation?.type;
133+
134+
return (
135+
<StepHeader
136+
subtitle={t("operationDetails.title")}
137+
title={operationType ? t(`operations.types.${operationType}`) : ""}
138+
testID="operationDetails-title"
139+
/>
140+
);
141+
}
142+
143+
function OperationDetailsHeaderRight() {
144+
const route = useRoute<OperationDetailsRouteProp>();
145+
146+
return route.params?.isSubOperation ? <NavigationHeaderCloseButton /> : null;
147+
}
148+
149+
function FirmwareUpdateHeaderTitle() {
150+
return null;
151+
}
152+
153+
function FirmwareUpdateHeaderLeft() {
154+
return null;
155+
}
156+
157+
function FirmwareUpdateHeaderRight() {
158+
return <NavigationHeaderCloseButton />;
159+
}
160+
101161
export default function BaseNavigator() {
102162
const { t } = useTranslation();
103163
const route = useRoute<
@@ -289,6 +349,13 @@ export default function BaseNavigator() {
289349
component={SwapNavigator}
290350
options={{ headerShown: false }}
291351
/>
352+
353+
<Stack.Screen
354+
name={NavigatorName.SwapSubScreens}
355+
component={SwapSubScreensNavigator}
356+
options={{ headerShown: false }}
357+
/>
358+
292359
<Stack.Screen
293360
name={NavigatorName.Perps}
294361
component={PerpsNavigator}
@@ -361,24 +428,11 @@ export default function BaseNavigator() {
361428
<Stack.Screen
362429
name={ScreenName.OperationDetails}
363430
component={OperationDetails}
364-
options={({ route }) => {
365-
return {
366-
headerTitle: () => (
367-
<StepHeader
368-
subtitle={t("operationDetails.title")}
369-
title={
370-
route.params?.operation?.type
371-
? t(`operations.types.${route.params.operation.type}`)
372-
: ""
373-
}
374-
testID="operationDetails-title"
375-
/>
376-
),
377-
headerLeft: () => <NavigationHeaderBackButton />,
378-
headerRight: () =>
379-
route.params?.isSubOperation ? <NavigationHeaderCloseButton /> : null,
380-
animation: "slide_from_bottom",
381-
};
431+
options={{
432+
headerTitle: OperationDetailsHeaderTitle,
433+
headerLeft: OperationDetailsHeaderLeft,
434+
headerRight: OperationDetailsHeaderRight,
435+
animation: "slide_from_bottom",
382436
}}
383437
/>
384438
<Stack.Screen
@@ -451,14 +505,8 @@ export default function BaseNavigator() {
451505
options={{
452506
...TransparentHeaderNavigationOptions,
453507
title: t("send.scan.title"),
454-
headerRight: () => (
455-
<NavigationHeaderCloseButtonAdvanced
456-
color={colors.constant.white}
457-
preferDismiss={false}
458-
rounded
459-
/>
460-
),
461-
headerLeft: () => null,
508+
headerRight: ScanRecipientHeaderRight,
509+
headerLeft: renderNullHeader,
462510
}}
463511
/>
464512
<Stack.Screen
@@ -547,17 +595,17 @@ export default function BaseNavigator() {
547595
component={NoFundsFlowNavigator}
548596
options={{
549597
...TransparentHeaderNavigationOptions,
550-
headerRight: () => <NavigationHeaderCloseButtonAdvanced preferDismiss={false} />,
551-
headerLeft: () => null,
598+
headerRight: FlowHeaderCloseButton,
599+
headerLeft: renderNullHeader,
552600
}}
553601
/>
554602
<Stack.Screen
555603
name={NavigatorName.StakeFlow}
556604
component={StakeFlowNavigator}
557605
options={{
558606
...TransparentHeaderNavigationOptions,
559-
headerRight: () => <NavigationHeaderCloseButtonAdvanced preferDismiss={false} />,
560-
headerLeft: () => null,
607+
headerRight: FlowHeaderCloseButton,
608+
headerLeft: renderNullHeader,
561609
}}
562610
/>
563611
<Stack.Screen
@@ -580,10 +628,10 @@ export default function BaseNavigator() {
580628
component={FirmwareUpdateScreen}
581629
options={{
582630
gestureEnabled: false,
583-
headerTitle: () => null,
631+
headerTitle: FirmwareUpdateHeaderTitle,
584632
title: "",
585-
headerLeft: () => null,
586-
headerRight: () => <NavigationHeaderCloseButton />,
633+
headerLeft: FirmwareUpdateHeaderLeft,
634+
headerRight: FirmwareUpdateHeaderRight,
587635
}}
588636
/>
589637
<Stack.Screen

apps/ledger-live-mobile/src/components/RootNavigator/MainNavigator/Wallet40TabNavigator.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ import EarnLiveAppNavigator from "../EarnLiveAppNavigator";
66
import CardLandingNavigator from "LLM/features/Card";
77
import { Tab } from "./tabNavigator";
88
import type { Wallet40TabNavigatorProps } from "./types";
9+
import { SwapWallet40Header } from "~/screens/Swap/LiveApp/components/SwapWallet40Header";
10+
import { resetSwapWallet40HeaderState } from "~/screens/Swap/LiveApp/navigationHandlers/wallet40/useSwapWallet40HeaderState";
11+
12+
function Wallet40SwapTabHeader() {
13+
return <SwapWallet40Header />;
14+
}
915

1016
export function Wallet40TabNavigator({
1117
tabBar,
@@ -22,8 +28,15 @@ export function Wallet40TabNavigator({
2228
<Tab.Screen
2329
name={NavigatorName.Swap}
2430
component={SwapNavigator}
31+
options={{
32+
header: Wallet40SwapTabHeader,
33+
}}
2534
listeners={() => ({
26-
tabPress: rebornFlowListener,
35+
tabPress: e => {
36+
// Prevent stale opaque header state when re-entering the Swap tab.
37+
resetSwapWallet40HeaderState();
38+
rebornFlowListener(e);
39+
},
2740
})}
2841
/>
2942
<Tab.Screen name={NavigatorName.Earn} component={EarnLiveAppNavigator} />

apps/ledger-live-mobile/src/components/RootNavigator/MainNavigator/useTabBar.tsx

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import type { ColorPalette } from "@ledgerhq/native-ui";
44
import type { EdgeInsets } from "react-native-safe-area-context";
55
import customTabBar from "~/components/TabBar/CustomTabBar";
66
import { MainTabBar } from "LLM/components/MainTabBar";
7+
import { NavigatorName } from "~/const";
8+
import { useSwapWallet40HeaderState } from "~/screens/Swap/LiveApp/navigationHandlers/wallet40/useSwapWallet40HeaderState";
79

810
type Params = {
911
shouldDisplayWallet40MainNav: boolean;
@@ -18,19 +20,33 @@ export function useTabBar({
1820
colors,
1921
insets,
2022
}: Params): (props: BottomTabBarProps) => React.JSX.Element {
23+
const swapWallet40HeaderState = useSwapWallet40HeaderState();
24+
2125
return useMemo(
2226
() =>
23-
({ ...props }: BottomTabBarProps): React.ReactElement =>
24-
shouldDisplayWallet40MainNav ? (
25-
<MainTabBar {...props} hideTabBar={!isMainNavigatorVisible} />
27+
({ ...props }: BottomTabBarProps): React.ReactElement => {
28+
const isSwapTabFocused = props.state.routes[props.state.index]?.name === NavigatorName.Swap;
29+
const hideSwapWallet40TabBar =
30+
isSwapTabFocused && swapWallet40HeaderState.headerStyle !== "transparent";
31+
const hideTabBar = !isMainNavigatorVisible || hideSwapWallet40TabBar;
32+
33+
return shouldDisplayWallet40MainNav ? (
34+
<MainTabBar {...props} hideTabBar={hideTabBar} />
2635
) : (
2736
customTabBar({
2837
...props,
2938
colors,
3039
insets,
3140
hideTabBar: !isMainNavigatorVisible,
3241
})
33-
),
34-
[colors, insets, isMainNavigatorVisible, shouldDisplayWallet40MainNav],
42+
);
43+
},
44+
[
45+
colors,
46+
insets,
47+
isMainNavigatorVisible,
48+
shouldDisplayWallet40MainNav,
49+
swapWallet40HeaderState.headerStyle,
50+
],
3551
);
3652
}

0 commit comments

Comments
 (0)