Skip to content

Commit

Permalink
feat: ✨ (llm) new accounts and assets lists portfolio (#8663)
Browse files Browse the repository at this point in the history
  • Loading branch information
LucasWerey authored Dec 17, 2024
1 parent 042e25a commit 8223e21
Show file tree
Hide file tree
Showing 26 changed files with 1,435 additions and 159 deletions.
6 changes: 6 additions & 0 deletions .changeset/curvy-ladybugs-collect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"live-mobile": minor
"@ledgerhq/native-ui": minor
---

Create a new tabSelector component inside native ui. Rename the existing one in DrawerTabSelector since it's used only in a drawer. Integration of the new assets/accounts lists in wallet screen
5 changes: 5 additions & 0 deletions apps/ledger-live-mobile/__tests__/jest-setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,11 @@ jest.mock("react-native-reanimated", () => {
// Silence the warning: Animated: `useNativeDriver` is not supported because the native animated module is missing
jest.mock("react-native/Libraries/Animated/NativeAnimatedHelper");

jest.mock("~/analytics", () => ({
...jest.requireActual("~/analytics"),
track: jest.fn(),
}));

jest.mock("@react-native-firebase/messaging", () => ({
messaging: jest.fn(() => ({
hasPermission: jest.fn(() => Promise.resolve(true)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ export type BaseNavigatorStackParamList = {

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

// WALLET SYNC
[NavigatorName.WalletSync]: NavigatorScreenParams<WalletSyncNavigatorStackParamList>;
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 @@ -529,6 +529,7 @@ export enum ScreenName {
LedgerSyncDeepLinkHandler = "LedgerSyncDeepLinkHandler",

MockedAddAssetButton = "MockedAddAssetButton",
MockedWalletScreen = "MockedWalletScreen",
GenericLandingPage = "GenericLandingPage",

// Web3Hub
Expand Down
3 changes: 2 additions & 1 deletion apps/ledger-live-mobile/src/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -2342,7 +2342,8 @@
"seeMarket": "See all"
},
"walletBalance": "Wallet balance",
"seelAllAssets": "See All Assets",
"seeAllAssets": "See all assets",
"seeAllAccounts": "See all accounts",
"marketPriceSection": {
"title": "{{currencyTicker}} market price",
"currencyPrice": "1 {{currencyTicker}} price",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { Flex } from "@ledgerhq/native-ui";
import { Pressable } from "react-native";
import AccountItem from "./components/AccountItem";
import globalSyncRefreshControl from "~/components/globalSyncRefreshControl";
import { withDiscreetMode } from "~/context/DiscreetModeContext";
import isEqual from "lodash/isEqual";

const ESTIMED_ITEM_SIZE = 150;

Expand Down Expand Up @@ -50,4 +52,4 @@ const AccountsListView: React.FC<Props> = props => {
return <View {...viewModel} />;
};

export default AccountsListView;
export default React.memo(withDiscreetMode(AccountsListView), isEqual);
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,16 @@ const StyledPressable = styled(Pressable)`
column-gap: 12px;
`;

const AddAccountButton: React.FC = () => {
type Props = {
sourceScreenName: string;
};

const AddAccountButton: React.FC<Props> = ({ sourceScreenName }) => {
const { t } = useTranslation();
const [isAddModalOpened, setAddModalOpened] = useState<boolean>(false);

const openAddModal = () => {
track("button_clicked", { button: "add a new account", page: "Accounts" });
track("button_clicked", { button: "Add a new account", page: sourceScreenName });
setAddModalOpened(true);
};
const closeAddModal = () => setAddModalOpened(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default function AccountsList({ route }: Props) {
<>
<TrackScreen event="Accounts" />
<ReactNavigationPerformanceView screenName={ScreenName.AccountsList} interactive>
<SafeAreaView edges={["bottom", "left", "right"]} isFlex style={{ marginHorizontal: 16 }}>
<SafeAreaView edges={["left", "right"]} isFlex style={{ marginHorizontal: 16 }}>
{showHeader && (
<Text variant="h1Inter" fontSize={28} paddingY={2}>
{t("accounts.title")}
Expand All @@ -49,7 +49,7 @@ export default function AccountsList({ route }: Props) {
</Text>
</Flex>
)}
{canAddAccount && <AddAccountButton />}
{canAddAccount && <AddAccountButton sourceScreenName="Accounts" />}
<AccounstListView sourceScreenName={sourceScreenName} isSyncEnabled={isSyncEnabled} />
</SafeAreaView>
</ReactNavigationPerformanceView>
Expand Down
18 changes: 10 additions & 8 deletions apps/ledger-live-mobile/src/newArch/features/Assets/Navigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,33 @@ import React, { useCallback, useMemo } from "react";
import { Platform } from "react-native";
import { createStackNavigator } from "@react-navigation/stack";
import { useTheme } from "styled-components/native";
import { useRoute } from "@react-navigation/native";
import { useNavigation, useRoute } from "@react-navigation/native";
import { ScreenName } from "~/const";
import { getStackNavigatorConfig } from "~/navigation/navigatorConfig";
import { track } from "~/analytics";
import { NavigationHeaderCloseButtonAdvanced } from "~/components/NavigationHeaderCloseButton";
import { AssetsNavigatorParamsList } from "./types";
import AssetsList from "./screens/AssetsList";
import { NavigationHeaderBackButton } from "~/components/NavigationHeaderBackButton";

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

const onClose = useCallback(() => {
const goBack = useCallback(() => {
track("button_clicked", {
button: "Close",
screen: route.name,
button: "Back",
page: route.name,
});
}, [route]);
navigation.goBack();
}, [navigation, route]);

const stackNavigationConfig = useMemo(
() => ({
...getStackNavigatorConfig(colors, true),
headerRight: () => <NavigationHeaderCloseButtonAdvanced onClose={onClose} />,
headerLeft: () => <NavigationHeaderBackButton onPress={goBack} />,
}),
[colors, onClose],
[colors, goBack],
);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import AssetItem from "./components/AssetItem";
import { Asset } from "~/types/asset";
import BigNumber from "bignumber.js";
import globalSyncRefreshControl from "~/components/globalSyncRefreshControl";
import { withDiscreetMode } from "~/context/DiscreetModeContext";
import isEqual from "lodash/isEqual";

const ESTIMED_ITEM_SIZE = 150;

Expand Down Expand Up @@ -53,4 +55,4 @@ const AssetsListView: React.FC<Props> = props => {
return <View {...viewModel} />;
};

export default AssetsListView;
export default React.memo(withDiscreetMode(AssetsListView), isEqual);
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export default function AssetsList({ route }: Props) {
<>
<TrackScreen event="Accounts" />
<ReactNavigationPerformanceView screenName={ScreenName.AssetsList} interactive>
<SafeAreaView edges={["bottom", "left", "right"]} isFlex style={{ marginHorizontal: 16 }}>
<SafeAreaView edges={["left", "right"]} isFlex style={{ marginHorizontal: 16 }}>
{showHeader && (
<Text variant="h1Inter" fontSize={28} paddingY={2}>
{t("assets.title")}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react";
import { Flex, TabSelector } from "@ledgerhq/native-ui";
import { DrawerTabSelector, Flex } from "@ledgerhq/native-ui";
import QrCode from "LLM/features/WalletSync/components/Synchronize/QrCode";
import ScanQrCode from "../../components/Synchronize/ScanQrCode";
import { Options, OptionsType } from "LLM/features/WalletSync/types/Activation";
Expand Down Expand Up @@ -59,7 +59,7 @@ const QrCodeMethod = ({

return (
<Flex flexDirection={"column"} alignItems={"center"} rowGap={24} width={"100%"} height={"100%"}>
<TabSelector
<DrawerTabSelector
options={[Options.SCAN, Options.SHOW_QR]}
selectedOption={currentOption}
handleSelectOption={handleSelectOption}
Expand Down
65 changes: 52 additions & 13 deletions apps/ledger-live-mobile/src/screens/Portfolio/PortfolioAssets.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
import { Button, IconsLegacy } from "@ledgerhq/native-ui";
import { useNavigation } from "@react-navigation/native";
import React, { useCallback, useMemo } from "react";
import { useTranslation } from "react-i18next";
import { useSelector } from "react-redux";
import { useStartProfiler } from "@shopify/react-native-performance";
import { GestureResponderEvent } from "react-native";
import { useNavigation } from "@react-navigation/native";
import { Button, IconsLegacy, Box } from "@ledgerhq/native-ui";
import { useDistribution } from "~/actions/general";
import { TrackScreen } from "~/analytics";
import { track, TrackScreen } from "~/analytics";
import { NavigatorName, ScreenName } from "~/const";
import { Box } from "@ledgerhq/native-ui";
import { blacklistedTokenIdsSelector, discreetModeSelector } from "~/reducers/settings";
import Assets from "./Assets";
import PortfolioQuickActionsBar from "./PortfolioQuickActionsBar";
import { useFeature } from "@ledgerhq/live-common/featureFlags/index";
import AddAccountButton from "LLM/features/Accounts/components/AddAccountButton";
import useListsAnimation from "./useListsAnimation";
import TabSection, { TAB_OPTIONS } from "./TabSection";

type Props = {
hideEmptyTokenAccount: boolean;
openAddModal: () => void;
};

const maxAssetsToDisplay = 5;
const maxItemsToDysplay = 5;

const PortfolioAssets = ({ hideEmptyTokenAccount, openAddModal }: Props) => {
const { t } = useTranslation();
Expand All @@ -45,13 +47,35 @@ const PortfolioAssets = ({ hideEmptyTokenAccount, openAddModal }: Props) => {
!blacklistedTokenIdsSet.has(asset.currency.id)
);
})
.slice(0, maxAssetsToDisplay),
.slice(0, maxItemsToDysplay),
[distribution, blacklistedTokenIdsSet],
);

const goToAssets = useCallback(
const { selectedTab, handleToggle, handleLayout, assetsAnimatedStyle, accountsAnimatedStyle } =
useListsAnimation(TAB_OPTIONS.Assets);

const showAssets = selectedTab === TAB_OPTIONS.Assets;
const showAccounts = selectedTab === TAB_OPTIONS.Accounts;

const onPressButton = useCallback(
(uiEvent: GestureResponderEvent) => {
startNavigationTTITimer({ source: ScreenName.Portfolio, uiEvent });
track("button_clicked", {
button: showAssets ? "See all assets" : "See all accounts",
page: "Wallet",
});
if (!showAssets && isAccountListUIEnabled) {
navigation.navigate(NavigatorName.Accounts, {
screen: ScreenName.AccountsList,
params: {
sourceScreenName: ScreenName.Portfolio,
showHeader: true,
canAddAccount: true,
isSyncEnabled: true,
},
});
return;
}
if (isAccountListUIEnabled) {
navigation.navigate(NavigatorName.Assets, {
screen: ScreenName.AssetsList,
Expand All @@ -67,21 +91,36 @@ const PortfolioAssets = ({ hideEmptyTokenAccount, openAddModal }: Props) => {
});
}
},
[startNavigationTTITimer, isAccountListUIEnabled, navigation],
[startNavigationTTITimer, showAssets, isAccountListUIEnabled, navigation],
);

const showAddAccountButton =
isAccountListUIEnabled && showAccounts && distribution.list.length >= maxItemsToDysplay;

return (
<>
<TrackScreen
category="Wallet"
accountsLength={distribution.list && distribution.list.length}
discreet={discreetMode}
/>
<Box mb={24} mt={18}>
<Box my={24}>
<PortfolioQuickActionsBar />
</Box>
<Assets assets={assetsToDisplay} />
{distribution.list.length < maxAssetsToDisplay ? (
{isAccountListUIEnabled ? (
<TabSection
t={t}
handleToggle={handleToggle}
handleLayout={handleLayout}
assetsAnimatedStyle={assetsAnimatedStyle}
accountsAnimatedStyle={accountsAnimatedStyle}
maxItemsToDysplay={maxItemsToDysplay}
/>
) : (
<Assets assets={assetsToDisplay} />
)}
{showAddAccountButton ? <AddAccountButton sourceScreenName="Wallet" /> : null}
{distribution.list.length < maxItemsToDysplay ? (
<Button
type="shade"
size="large"
Expand All @@ -94,8 +133,8 @@ const PortfolioAssets = ({ hideEmptyTokenAccount, openAddModal }: Props) => {
{t("account.emptyState.addAccountCta")}
</Button>
) : (
<Button type="shade" size="large" outline mt={6} onPress={goToAssets}>
{t("portfolio.seelAllAssets")}
<Button type="shade" size="large" outline onPress={onPressButton}>
{showAssets ? t("portfolio.seeAllAssets") : t("portfolio.seeAllAccounts")}
</Button>
)}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ function ReadOnlyPortfolio({ navigation }: NavigationProps) {
<Box background={colors.background.main} px={6} mt={6} key="Assets">
<Assets assets={assetsToDisplay} />
<Button type="shade" size="large" outline mt={6} onPress={goToAssets}>
{t("portfolio.seelAllAssets")}
{t("portfolio.seeAllAssets")}
</Button>
</Box>,
...(!hasOrderedNano
Expand Down
66 changes: 66 additions & 0 deletions apps/ledger-live-mobile/src/screens/Portfolio/TabSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React, { memo } from "react";
import { Box, Flex } from "@ledgerhq/native-ui";
import Animated from "react-native-reanimated";
import { TabSelector } from "@ledgerhq/native-ui";
import AssetsListView from "LLM/features/Assets/components/AssetsListView";
import AccountsListView from "LLM/features/Accounts/components/AccountsListView";
import { ScreenName } from "~/const";
import { LayoutChangeEvent } from "react-native";

export const TAB_OPTIONS = {
Assets: "Assets",
Accounts: "Accounts",
} as const;

type BaseAnimationStyle = {
transform: {
translateX: number;
}[];
opacity: number;
};

type TabSectionProps = {
t: (key: string) => string;
handleToggle: (value: string) => void;
handleLayout: (event: LayoutChangeEvent) => void;
assetsAnimatedStyle: BaseAnimationStyle;
accountsAnimatedStyle: BaseAnimationStyle;
maxItemsToDysplay: number;
};

const TabSection: React.FC<TabSectionProps> = ({
t,
handleToggle,
handleLayout,
assetsAnimatedStyle,
accountsAnimatedStyle,
maxItemsToDysplay,
}) => (
<>
<Box height={40} mb={16}>
<TabSelector labels={[t("assets.title"), t("accounts.title")]} onToggle={handleToggle} />
</Box>
<Flex
flexDirection="row"
overflow="hidden"
onLayout={handleLayout}
width="200%"
testID="portfolio-assets-layout"
>
<Animated.View style={[{ flex: 1 }, assetsAnimatedStyle]}>
<AssetsListView
sourceScreenName={ScreenName.Portfolio}
limitNumberOfAssets={maxItemsToDysplay}
/>
</Animated.View>
<Animated.View style={[{ flex: 1 }, accountsAnimatedStyle]}>
<AccountsListView
sourceScreenName={ScreenName.Portfolio}
limitNumberOfAccounts={maxItemsToDysplay}
/>
</Animated.View>
</Flex>
</>
);

export default memo(TabSection);
Loading

0 comments on commit 8223e21

Please sign in to comment.