Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(llm): new add account drawer on assets screen with WS entrypoint #7064

Merged
merged 10 commits into from
Jun 19, 2024
Merged
5 changes: 5 additions & 0 deletions .changeset/cool-dingos-explode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"live-mobile": patch
---

Create a new drawer component on add account. This new drawer has new UI and an entry point to walletSync. This entrypoint is hidden under a feature flag. Also update translations related to WS.
29 changes: 29 additions & 0 deletions apps/ledger-live-mobile/__mocks__/MockedExpoCamera.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from "react";

type Props = {
children: React.ReactNode;
};

export class MockedExpoCamera extends React.Component<Props> {
static useCameraPermissions = () => [
{ canAskAgain: false, expires: "never", granted: true, status: "granted" },
() => {
return new Promise(resolve => {
resolve(jest.fn());
});
},
() => {
return new Promise(resolve => {
resolve(jest.fn());
});
},
];
render() {
return <>{this.props.children}</>;
}
}

export const MockedCameraType = {
back: "back",
front: "front",
};
1 change: 1 addition & 0 deletions apps/ledger-live-mobile/__mocks__/react-native-config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export default {
DEVICE_PROXY_URL: null,
MOCK: true,
};
28 changes: 28 additions & 0 deletions apps/ledger-live-mobile/__tests__/jest-setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import "@mocks/console";
import { ALLOWED_UNHANDLED_REQUESTS } from "./handlers";
import { server } from "./server";
import { NativeModules } from "react-native";
import { MockedExpoCamera, MockedCameraType } from "../__mocks__/MockedExpoCamera";

// Needed for react-reanimated https://docs.swmansion.com/react-native-reanimated/docs/next/guide/testing/
jest.useFakeTimers();
Expand All @@ -28,6 +29,33 @@ const mockAnalytics = jest.genMockFromModule("@segment/analytics-react-native");

jest.mock("@segment/analytics-react-native", () => mockAnalytics);

jest.mock("react-native-launch-arguments", () => ({}));

NativeModules.BluetoothHelperModule = {
E_BLE_CANCELLED: "BLE_UNKNOWN_STATE",
};

jest.mock("react-native-share", () => ({
default: jest.fn(),
}));

jest.mock("expo-camera", () => {
return {
Camera: MockedExpoCamera,
CameraType: MockedCameraType,
};
});

jest.mock("expo-barcode-scanner", () => ({
BarCodeScanner: {
Constants: {
BarCodeType: {
qr: "qr",
},
},
},
}));

// Mock of Native Modules
jest.mock("react-native-localize", () => ({
getTimeZone: jest.fn(),
Expand Down
4 changes: 4 additions & 0 deletions apps/ledger-live-mobile/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ const transformIncludePatterns = [
"react-native-startup-time",
"@segment/analytics-react-native",
"uuid",
"react-native-ble-plx",
"react-native-android-location-services-dialog-box",
"react-native-vector-icons",
];

/** @type {import('ts-jest').JestConfigWithTsJest} */
Expand Down Expand Up @@ -53,5 +56,6 @@ module.exports = {
"^react-native$": "<rootDir>/node_modules/react-native",
"styled-components":
"<rootDir>/node_modules/styled-components/native/dist/styled-components.native.cjs.js",
"^react-redux": "<rootDir>/node_modules/react-redux",
},
};
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Flex } from "@ledgerhq/native-ui";
import { Flex, Icons } from "@ledgerhq/native-ui";
import { useNavigation } from "@react-navigation/native";
import React from "react";
import Touchable from "./Touchable";
import { ArrowLeft } from "@ledgerhq/native-ui/assets/icons";

type Props = {
/**
* Function called when user presses on the back arrow.
Expand All @@ -13,7 +13,7 @@ type Props = {

export const NavigationHeaderBackImage = () => (
<Flex p={6}>
<ArrowLeft size={"M"} />
<Icons.ArrowLeft />
</Flex>
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ export type BaseNavigatorStackParamList = {
[ScreenName.RedirectToOnboardingRecoverFlow]: undefined;

[NavigatorName.AnalyticsOptInPrompt]: NavigatorScreenParams<AnalyticsOptInPromptNavigatorParamList>;
[ScreenName.MockedAddAssetButton]: undefined;

[ScreenName.FirmwareUpdate]: {
deviceInfo?: DeviceInfo | null;
Expand Down
1 change: 1 addition & 0 deletions apps/ledger-live-mobile/src/const/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,7 @@ export enum ScreenName {

AnalyticsOptInPromptMain = "AnalyticsOptInPromptMain",
AnalyticsOptInPromptDetails = "AnalyticsOptInPromptDetails",
MockedAddAssetButton = "MockedAddAssetButton",
}

export enum NavigatorName {
Expand Down
18 changes: 17 additions & 1 deletion apps/ledger-live-mobile/src/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -1336,7 +1336,7 @@
"subtitle": "Select how you’d like to access your wallet:",
"connect": "Connect your Ledger",
"sync": "Sync with Ledger Live desktop",
"walletSync": "Sync with another Ledger Live"
"walletSync": "Sync with another Ledger Live app"
},
"discoverLive": {
"exploreWithoutADevice": "Explore without a device",
Expand Down Expand Up @@ -2288,6 +2288,22 @@
"import": {
"title": "Import from desktop",
"description": "Import asset from Ledger Live Desktop"
},
"drawer": {
"drawerTitleHasAccount": "Add another account",
"drawerSubTitle": "Add your assets using your Ledger, or import them directly from your Ledger Live Desktop app.",
"import": {
"title": "Import from desktop",
"description": "Import asset from Ledger Live Desktop"
},
"add": {
"title": "Add with your Ledger",
"description": "Create or import your assets using your Ledger device"
},
"walletSync": {
"title": "Import via another Ledger Live app",
"description": "Activate Ledger Sync"
}
}
},
"byteSize": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Flex, Text } from "@ledgerhq/native-ui";
import React from "react";
import Touchable from "~/components/Touchable";
import styled from "styled-components/native";

type ActionRowProps = {
title: string;
description?: string;
icon: React.ReactNode;
testID?: string;
onPress?: () => void;
};
const TouchableCard = styled(Touchable)`
background-color: ${p => p.theme.colors.opacityDefault.c05};
border-radius: 8px;
padding: 16px;
align-items: center;
gap: 12px;
flex-direction: row;
align-self: stretch;
`;

const CardTitle = styled(Text)`
font-size: 16px;
color: ${p => p.theme.colors.neutral.c100};
`;

const CardDescription = styled(Text)`
font-size: 14px;
color: ${p => p.theme.colors.neutral.c70};
line-height: 18.2px;
`;

const ActionRow: React.FC<ActionRowProps> = ({
title,
description,
icon,
testID,
onPress,
}: ActionRowProps) => {
return (
<TouchableCard onPress={onPress} testID={testID}>
{icon}
<Flex flexDirection={"column"} rowGap={4} flex={1}>
<CardTitle>{title}</CardTitle>
{description && <CardDescription>{description}</CardDescription>}
</Flex>
</TouchableCard>
);
};
export default ActionRow;
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ type Props = {
handleClose: () => void;
};

const DummyDrawer = ({ isOpen, handleClose }: Props) => {
const Drawer = ({ isOpen, handleClose }: Props) => {
return (
<QueuedDrawer isRequestingToBeOpened={isOpen} onClose={handleClose}>
<Text>{"Dummy Drawer"}</Text>
</QueuedDrawer>
);
};

export default DummyDrawer;
export default Drawer;
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import React from "react";
import { screen } from "@testing-library/react-native";
import { render, act } from "@tests/test-renderer";
import { TestButtonPage } from "./shared";
import { State } from "~/reducers/types";

describe("AddAccount", () => {
/**====== Import with ledger device test =======*/
it("Should open select add account method drawer with WS feature flag and navigate to import with your Ledger", async () => {
const { user } = render(<TestButtonPage />, {
overrideInitialState: (state: State) => ({
...state,
settings: {
...state.settings,
readOnlyModeEnabled: false,
overriddenFeatureFlags: { llmWalletSync: { enabled: true } },
},
}),
});

const addAssetButton = await screen.findByText(/Add asset/i);

// Check if the add asset button is visible
await expect(addAssetButton).toBeVisible();
// Open drawer
await act(async () => {
await user.press(addAssetButton);
});
// Wait for the drawer to open
await expect(await screen.findByText(/add another account/i));
await expect(await screen.findByText(/add with your ledger/i));
await expect(await screen.findByText(/import via another ledger live app/i));
// On press add with another ledger live app
await act(async () => {
await user.press(await screen.getByText(/add with your ledger/i));
});
await expect(await screen.findByText(/crypto asset/i)).toBeVisible();
// On click back
await act(async () => {
await user.press(await screen.findByTestId(/navigation-header-back-button/i));
});
await expect(addAssetButton).toBeVisible();
});

/**====== Import with WS test =======*/
it("Should open select add account method drawer with WS feature flag and navigate to import with wallet sync", async () => {
const { user } = render(<TestButtonPage />, {
overrideInitialState: (state: State) => ({
...state,
settings: {
...state.settings,
readOnlyModeEnabled: false,
overriddenFeatureFlags: { llmWalletSync: { enabled: true } },
},
}),
});

const addAssetButton = await screen.findByText(/Add asset/i);
// Check if the add asset button is visible
await expect(addAssetButton).toBeVisible();
// Open drawer
await act(async () => {
await user.press(addAssetButton);
});
// Wait for the drawer to open
await expect(await screen.findByText(/add another account/i));
await expect(await screen.findByText(/add with your ledger/i));
await expect(await screen.findByText(/import via another ledger live app/i));
// On press add with wallet sync
await act(async () => {
await user.press(await screen.getByText(/import via another ledger live app/i));
});
await expect(await screen.findByText(/dummy drawer/i)).toBeVisible();
});

/**====== Import from desktop Test =======*/
it("Should open select add account method drawer without WS feature flag and navigate to import from desktop", async () => {
const { user } = render(<TestButtonPage />, {
overrideInitialState: (state: State) => ({
...state,
settings: {
...state.settings,
readOnlyModeEnabled: false,
overriddenFeatureFlags: { llmWalletSync: { enabled: false } },
},
}),
});

const addAssetButton = await screen.findByText(/add asset/i);
// Check if the add asset button is visible
await expect(addAssetButton).toBeVisible();
// Open drawer
await user.press(addAssetButton);
// Wait for the drawer to open
await expect(await screen.findByText(/add another account/i));
await expect(await screen.findByText(/add with your ledger/i));
await expect(await screen.findByText(/import from desktop/i));
// On press add from desktop
await user.press(await screen.getByText(/import from desktop/i));
await expect(
await screen.findByText(/Scan and import your accounts from Ledger Live desktop/i),
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import * as React from "react";
import { createStackNavigator } from "@react-navigation/stack";
import { NavigatorName, ScreenName } from "~/const";
import { Button, IconsLegacy } from "@ledgerhq/native-ui";
import { useTranslation } from "react-i18next";
import AddAccountDrawer from "LLM/features/Accounts/screens/AddAccount";
import { BaseNavigatorStackParamList } from "~/components/RootNavigator/types/BaseNavigator";
import AddAccountsNavigator from "~/components/RootNavigator/AddAccountsNavigator";
import ImportAccountsNavigator from "~/components/RootNavigator/ImportAccountsNavigator";

const MockComponent = () => {
const { t } = useTranslation();
const [isAddModalOpened, setAddModalOpened] = React.useState<boolean>(false);

const openAddModal = () => setAddModalOpened(true);
const closeAddModal = () => setAddModalOpened(false);
const reopenAddModal = () => setAddModalOpened(true);

return (
<>
<Button
type="shade"
size="large"
outline
mt={6}
mb={8}
iconPosition="left"
Icon={IconsLegacy.PlusMedium}
onPress={openAddModal}
>
{t("portfolio.emptyState.buttons.import")}
</Button>
<AddAccountDrawer
isOpened={isAddModalOpened}
onClose={closeAddModal}
reopenDrawer={reopenAddModal}
/>
</>
);
};

const Stack = createStackNavigator<BaseNavigatorStackParamList>();

export function TestButtonPage() {
return (
<Stack.Navigator initialRouteName={ScreenName.MockedAddAssetButton}>
<Stack.Screen name={ScreenName.MockedAddAssetButton} component={MockComponent} />
<Stack.Screen name={NavigatorName.AddAccounts} component={AddAccountsNavigator} />
<Stack.Screen name={NavigatorName.ImportAccounts} component={ImportAccountsNavigator} />
</Stack.Navigator>
);
}
Loading
Loading