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 accounts and assets lists portfolio #8663

Merged
merged 1 commit into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 });
themooneer marked this conversation as resolved.
Show resolved Hide resolved
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
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
Loading