Skip to content

Commit

Permalink
feat: migrate all entry screen, ctas to new add account (uncompleted …
Browse files Browse the repository at this point in the history
…v2) flow
  • Loading branch information
themooneer committed Dec 18, 2024
1 parent b8f7abb commit 1660676
Show file tree
Hide file tree
Showing 32 changed files with 1,358 additions and 461 deletions.
6 changes: 6 additions & 0 deletions .changeset/moody-mugs-grin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"live-mobile": minor
"@ledgerhq/live-common": minor
---

When llmNetworkBasedAddAccount feature flag is enabled, all the ctas that open an add account process will be redirected to the new flow and support currency and related route params.
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,7 @@ export default function SwapNavigator(
title: t("transfer.swap2.form.title"),
headerLeft: () => null,
}}
initialParams={{
...params,
}}
initialParams={params as Partial<SwapNavigatorParamList[ScreenName.SwapTab]>}
/>

<Stack.Screen
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -321,9 +321,11 @@ export type BaseNavigatorStackParamList = {
NavigatorScreenParams<DeviceSelectionNavigatorParamsList>
>;
[NavigatorName.AssetSelection]?: Partial<
NavigatorScreenParams<AssetSelectionNavigatorParamsList>
> &
CommonAddAccountNavigatorParamsList;
NavigatorScreenParams<AssetSelectionNavigatorParamsList> & {
context?: "addAccounts" | "receiveFunds";
token?: TokenCurrency;
} // in some cases we need to pass directly the context to the navigator and let it handle the logic
>;
[NavigatorName.Assets]?: Partial<NavigatorScreenParams<AssetsNavigatorParamsList>>;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Observable } from "rxjs";
import { NavigatorScreenParams } from "@react-navigation/native";
import { NavigatorName, ScreenName } from "~/const";
import { AddAccountsNavigatorParamList } from "./AddAccountsNavigator";
import { DeviceSelectionNavigatorParamsList } from "LLM/features/DeviceSelection/types";

export type RequestAccountNavigatorParamList = {
[ScreenName.RequestAccountsSelectCrypto]: {
Expand All @@ -30,4 +31,14 @@ export type RequestAccountNavigatorParamList = {
analyticsPropertyFlow?: string;
onSuccess?: (account: AccountLike, parentAccount?: Account) => void;
}>;
[NavigatorName.DeviceSelection]: Partial<
NavigatorScreenParams<DeviceSelectionNavigatorParamsList> &
Partial<{
token?: TokenCurrency;
inline?: boolean;
returnToSwap?: boolean;
analyticsPropertyFlow?: string;
onSuccess?: (account: AccountLike, parentAccount?: Account) => void;
}>
>;
};
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ import type { Transaction as CasperTransaction } from "@ledgerhq/live-common/fam
import type { Transaction as TonTransaction } from "@ledgerhq/live-common/families/ton/types";
import BigNumber from "bignumber.js";
import { Account, Operation } from "@ledgerhq/types-live";
import { ScreenName } from "~/const";
import { NavigatorName, ScreenName } from "~/const";
import { NavigatorScreenParams } from "@react-navigation/core";
import { AssetSelectionNavigatorParamsList } from "LLM/features/AssetSelection/types";

type Target = "from" | "to";

Expand Down Expand Up @@ -329,4 +331,7 @@ export type SwapNavigatorParamList = {
| ScreenName.SendSelectDevice
| ScreenName.SwapForm;
};
[NavigatorName.AssetSelection]?: Partial<
NavigatorScreenParams<AssetSelectionNavigatorParamsList>
>;
};
8 changes: 8 additions & 0 deletions apps/ledger-live-mobile/src/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -7123,5 +7123,13 @@
}
}
}
},
"assetSelection": {
"selectCrypto": {
"title": "Select Asset"
},
"selectNetwork": {
"title": "Select the blockchain network the asset belongs to"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export default function Navigator() {
options={{
headerTitle: "",
}}
initialParams={route.params}
/>

{/* Scan accounts from device */}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,22 @@ const useSelectAddAccountMethodViewModel = ({
const hasCurrency = !!currency;

const navigationParams = useMemo(() => {
return hasCurrency
? currency.type === "TokenCurrency"
? { token: currency }
: { currency }
: {};
}, [hasCurrency, currency]);
if (hasCurrency) {
if (currency?.type === "TokenCurrency") {
return {
token: currency,
...(llmNetworkBasedAddAccountFlow?.enabled && { context: "addAccounts" }),
};
} else {
return {
currency,
...(llmNetworkBasedAddAccountFlow?.enabled && { context: "addAccounts" }),
};
}
} else {
return llmNetworkBasedAddAccountFlow?.enabled ? { context: "addAccounts" } : {};
}
}, [hasCurrency, currency, llmNetworkBasedAddAccountFlow?.enabled]);

const trackButtonClick = useCallback((button: string) => {
track("button_clicked", {
Expand All @@ -58,6 +68,9 @@ const useSelectAddAccountMethodViewModel = ({
const EntryNavigatorName = llmNetworkBasedAddAccountFlow?.enabled
? NavigatorName.AssetSelection
: NavigatorName.AddAccounts;
// to delete after llmNetworkBasedAddAccountFlow is fully enabled (ts inference not working well based on navigationParams)
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
navigation.navigate(EntryNavigatorName, navigationParams);
}, [
navigation,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@ import { CryptoCurrency, TokenCurrency } from "@ledgerhq/types-cryptoassets";
import { ScreenName } from "~/const";
import { Device } from "@ledgerhq/types-devices";

type CommonParams = {
context?: "addAccounts" | "receiveFunds";
onSuccess?: () => void;
};
export type NetworkBasedAddAccountNavigator = {
[ScreenName.SelectAccounts]: {
[ScreenName.SelectAccounts]: CommonParams & {
currency: CryptoCurrency | TokenCurrency;
createTokenAccount?: boolean;
};
[ScreenName.ScanDeviceAccounts]: {
[ScreenName.ScanDeviceAccounts]: CommonParams & {
currency: CryptoCurrency | TokenCurrency;
device: Device;
onSuccess?: (_?: unknown) => void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Platform } from "react-native";
import { createStackNavigator } from "@react-navigation/stack";
import { useTheme } from "styled-components/native";
import { useRoute } from "@react-navigation/native";
import { ScreenName } from "~/const";
import { NavigatorName, ScreenName } from "~/const";
import { getStackNavigatorConfig } from "~/navigation/navigatorConfig";
import { track } from "~/analytics";
import { Flex } from "@ledgerhq/native-ui";
Expand All @@ -16,13 +16,19 @@ import SelectNetwork from "LLM/features/AssetSelection/screens/SelectNetwork";
import { NavigationHeaderCloseButtonAdvanced } from "~/components/NavigationHeaderCloseButton";
import { NavigationHeaderBackButton } from "~/components/NavigationHeaderBackButton";
import { AssetSelectionNavigatorParamsList } from "./types";
import { BaseComposite, StackNavigatorProps } from "~/components/RootNavigator/types/helpers";

type NavigationProps = BaseComposite<
StackNavigatorProps<AssetSelectionNavigatorParamsList, NavigatorName.AssetSelection>
>;

export default function Navigator() {
const { colors } = useTheme();
const route = useRoute();

const route = useRoute<NavigationProps["route"]>();
const hasClosedNetworkBanner = useSelector(hasClosedNetworkBannerSelector);

const { token, currency } = route.params || {};

const onClose = useCallback(() => {
track("button_clicked", {
button: "Close",
Expand All @@ -44,15 +50,19 @@ export default function Navigator() {
...stackNavigationConfig,
gestureEnabled: Platform.OS === "ios",
}}
initialRouteName={
token || currency ? ScreenName.SelectNetwork : ScreenName.AddAccountsSelectCrypto
}
>
<Stack.Screen
name={ScreenName.AddAccountsSelectCrypto}
component={SelectCrypto}
options={{
headerLeft: () => <NavigationHeaderBackButton />,
headerTitle: "",
title: "",
headerRight: () => <NavigationHeaderCloseButtonAdvanced onClose={onClose} />,
}}
initialParams={route.params}
/>

<Stack.Screen
Expand All @@ -70,6 +80,7 @@ export default function Navigator() {
</Flex>
),
}}
initialParams={route.params}
/>
</Stack.Navigator>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import React from "react";
import { render, screen } from "@tests/test-renderer";
import AssetSelectionNavigator from "../Navigator";
import { useRoute, useNavigation } from "@react-navigation/native";
import { useGroupedCurrenciesByProvider } from "@ledgerhq/live-common/deposit/index";
import {
mockGroupedCurrenciesBySingleProviderData,
mockGroupedCurrenciesWithMultipleProviderData,
} from "./mockData";

const MockUseRoute = useRoute as jest.Mock;
const MockUseGroupedCurrenciesByProvider = useGroupedCurrenciesByProvider as jest.Mock;
const mockNavigate = jest.fn();

(useNavigation as jest.Mock).mockReturnValue({
navigate: mockNavigate,
});

jest.mock("../components/NetworkBanner", () => {
return {
__esModule: true,
default: () => <div data-testid="network-banner">Mock Network Banner</div>,
};
});

jest.mock("@ledgerhq/live-common/deposit/index", () => ({
useGroupedCurrenciesByProvider: jest.fn(),
}));

jest.mock("@react-navigation/native", () => ({
...jest.requireActual("@react-navigation/native"),
useRoute: jest.fn(),
useNavigation: jest.fn(),
}));

jest.useFakeTimers();

describe("Asset Selection test suite", () => {
it("should render crypto selection screen when no currency is defined in the navigation route showing a loader", () => {
MockUseRoute.mockReturnValue({
params: {
context: "addAccounts",
},
});

MockUseGroupedCurrenciesByProvider.mockReturnValue({
result: { currenciesByProvider: [], sortedCryptoCurrencies: [] },
loadingStatus: "pending",
});

render(<AssetSelectionNavigator />);

const screenTitle = screen.getByText(/select asset/i);
const selectCryptoViewArea = screen.getByTestId("select-crypto-view-area");
const loader = screen.getByTestId("loader");
expect(screenTitle).toBeVisible();
expect(screenTitle).toHaveProp("testID", "select-crypto-header-step1-title");
expect(selectCryptoViewArea).toBeVisible();
expect(loader).toBeVisible();
});

it("should render crypto selection screen with empty list when useGroupedCurrenciesByProvider finish loading with empty result", () => {
MockUseRoute.mockReturnValue({
params: {
context: "addAccounts",
},
});

MockUseGroupedCurrenciesByProvider.mockReturnValue({
result: { currenciesByProvider: [], sortedCryptoCurrencies: [] },
loadingStatus: "success",
});

render(<AssetSelectionNavigator />);

const emptyList = screen.getByText(/no crypto assets found/i);
expect(emptyList).toBeVisible();
});
it("should display a list of cryptocurrencies when useGroupedCurrenciesByProvider successfully loads data", () => {
MockUseRoute.mockReturnValue({
params: {
context: "addAccounts",
},
});

MockUseGroupedCurrenciesByProvider.mockReturnValue({
result: mockGroupedCurrenciesBySingleProviderData,
loadingStatus: "success",
});

render(<AssetSelectionNavigator />);

const selectCryptoViewArea = screen.getByTestId("select-crypto-view-area");
const cryptoCurrencyRow = screen.getByText(/bitcoin/i);
expect(selectCryptoViewArea).toBeVisible();
expect(cryptoCurrencyRow).toBeVisible();
});
it("should navigate to network selection when currency has more than one network provider", () => {
MockUseRoute.mockReturnValue({
params: {
context: "addAccounts",
currency: "ethereum",
},
});

MockUseGroupedCurrenciesByProvider.mockReturnValue({
result: mockGroupedCurrenciesWithMultipleProviderData,
loadingStatus: "success",
});

render(<AssetSelectionNavigator />);

const selectNetwork = screen.getByTestId("select-network-view-area");
const title = screen.getByText(/Select the blockchain network the asset belongs to/i);
const arbitrum = screen.getByText(/arbitrum/i);
const blast = screen.getByText(/blast/i);
const boba = screen.getByText(/boba/i);
expect(selectNetwork).toBeVisible();
expect(title).toBeVisible();
[arbitrum, blast, boba].forEach(network => {
expect(network).toBeVisible();
});
});
});
Loading

0 comments on commit 1660676

Please sign in to comment.