From a52bd8e95989ac0913f1a85e18e7a4d6b682f1c0 Mon Sep 17 00:00:00 2001 From: Lucas Werey Date: Tue, 17 Dec 2024 14:05:49 +0100 Subject: [PATCH] feat: :sparkles: (llm)add new accounts list to wallet centric screen --- .changeset/fifty-toes-sit.md | 5 ++ .../RootNavigator/types/AccountsNavigator.ts | 3 +- .../src/locales/en/common.json | 1 + .../components/AccountItem.tsx | 23 +++++-- .../components/AccountsListView/index.tsx | 8 ++- .../useAccountsListViewModel.ts | 4 +- .../components/AddAccountButton/index.tsx | 7 ++- .../Accounts/screens/AccountsList/index.tsx | 55 ++++++++++++++-- .../Accounts/screens/AccountsList/types.ts | 2 + .../Assets/screens/AssetsList/index.tsx | 2 +- .../WalletCentricAsset/AccountsSection.tsx | 63 ++++++++++++++----- .../src/screens/WalletCentricAsset/index.tsx | 1 + .../WalletCentricSections/SectionTitle.tsx | 4 +- 13 files changed, 141 insertions(+), 37 deletions(-) create mode 100644 .changeset/fifty-toes-sit.md diff --git a/.changeset/fifty-toes-sit.md b/.changeset/fifty-toes-sit.md new file mode 100644 index 000000000000..e84866461c18 --- /dev/null +++ b/.changeset/fifty-toes-sit.md @@ -0,0 +1,5 @@ +--- +"live-mobile": minor +--- + +Implementation of the new accounts list inside wallet centric screen. diff --git a/apps/ledger-live-mobile/src/components/RootNavigator/types/AccountsNavigator.ts b/apps/ledger-live-mobile/src/components/RootNavigator/types/AccountsNavigator.ts index 4414d4d8b34d..38f33c0302ab 100644 --- a/apps/ledger-live-mobile/src/components/RootNavigator/types/AccountsNavigator.ts +++ b/apps/ledger-live-mobile/src/components/RootNavigator/types/AccountsNavigator.ts @@ -1,5 +1,5 @@ import { CryptoOrTokenCurrency } from "@ledgerhq/types-cryptoassets"; -import { AccountLike, ProtoNFT } from "@ledgerhq/types-live"; +import { Account, AccountLike, ProtoNFT, TokenAccount } from "@ledgerhq/types-live"; import { ScreenName } from "~/const"; export type AccountsNavigatorParamList = { @@ -39,5 +39,6 @@ export type AccountsNavigatorParamList = { showHeader?: boolean; canAddAccount?: boolean; isSyncEnabled?: boolean; + specificAccounts?: Account[] | TokenAccount[]; }; }; diff --git a/apps/ledger-live-mobile/src/locales/en/common.json b/apps/ledger-live-mobile/src/locales/en/common.json index 30f0df5aeb49..f2b388fef36e 100644 --- a/apps/ledger-live-mobile/src/locales/en/common.json +++ b/apps/ledger-live-mobile/src/locales/en/common.json @@ -3963,6 +3963,7 @@ }, "addAccounts": { "addNew": "Add new", + "seeAllAccounts": "See all accounts", "addNewOrExisting": "Add new or existing account", "supportLinks": { "segwit_or_native_segwit": "Segwit or Native segwit?" diff --git a/apps/ledger-live-mobile/src/newArch/features/Accounts/components/AccountsListView/components/AccountItem.tsx b/apps/ledger-live-mobile/src/newArch/features/Accounts/components/AccountsListView/components/AccountItem.tsx index 0ce13444ce2a..963b35db1494 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Accounts/components/AccountsListView/components/AccountItem.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/Accounts/components/AccountsListView/components/AccountItem.tsx @@ -3,20 +3,31 @@ import { Flex, Tag, Text } from "@ledgerhq/native-ui"; import ParentCurrencyIcon from "~/components/ParentCurrencyIcon"; import BigNumber from "bignumber.js"; import CounterValue from "~/components/CounterValue"; -import { Account, DerivationMode } from "@ledgerhq/types-live"; +import { Account, DerivationMode, TokenAccount } from "@ledgerhq/types-live"; import { useMaybeAccountName } from "~/reducers/wallet"; import { getTagDerivationMode } from "@ledgerhq/coin-framework/lib/derivation"; import { formatAddress } from "LLM/features/Accounts/utils/formatAddress"; +import { + getParentAccount, + isTokenAccount as isTokenAccountChecker, +} from "@ledgerhq/live-common/account/index"; +import { useSelector } from "react-redux"; +import { accountsSelector } from "~/reducers/accounts"; interface AccountItemProps { - account: Account; + account: Account | TokenAccount; balance: BigNumber; } const AccountItem: React.FC = ({ account, balance }) => { - const currency = account.currency; + const allAccount = useSelector(accountsSelector); + const isTokenAccount = isTokenAccountChecker(account); + const currency = isTokenAccount ? account.token.parentCurrency : account.currency; const accountName = useMaybeAccountName(account); - const formattedAddress = formatAddress(account.freshAddress); + const parentAccount = getParentAccount(account, allAccount); + const formattedAddress = formatAddress( + isTokenAccount ? parentAccount.freshAddress : account.freshAddress, + ); const tag = account.type === "Account" && account?.derivationMode !== undefined && @@ -26,7 +37,7 @@ const AccountItem: React.FC = ({ account, balance }) => { return ( <> - + = ({ account, balance }) => { {formattedAddress} - + diff --git a/apps/ledger-live-mobile/src/newArch/features/Accounts/components/AccountsListView/index.tsx b/apps/ledger-live-mobile/src/newArch/features/Accounts/components/AccountsListView/index.tsx index fa4a9bc7334b..fd49bb38f2d7 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Accounts/components/AccountsListView/index.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/Accounts/components/AccountsListView/index.tsx @@ -1,7 +1,7 @@ import React, { useCallback, useMemo } from "react"; import { FlashList, FlashListProps } from "@shopify/flash-list"; import useAccountsListViewModel, { type Props } from "./useAccountsListViewModel"; -import { Account } from "@ledgerhq/types-live"; +import { Account, TokenAccount } from "@ledgerhq/types-live"; import { Flex } from "@ledgerhq/native-ui"; import { Pressable } from "react-native"; import AccountItem from "./components/AccountItem"; @@ -15,11 +15,13 @@ type ViewProps = ReturnType; const View: React.FC = ({ accountsToDisplay, isSyncEnabled, onAccountPress }) => { const List = useMemo(() => { - return isSyncEnabled ? globalSyncRefreshControl>(FlashList) : FlashList; + return isSyncEnabled + ? globalSyncRefreshControl>(FlashList) + : FlashList; }, [isSyncEnabled]); const renderItem = useCallback( - ({ item }: { item: Account }) => ( + ({ item }: { item: Account | TokenAccount }) => ( [ { opacity: pressed ? 0.5 : 1.0, marginVertical: 12 }, diff --git a/apps/ledger-live-mobile/src/newArch/features/Accounts/components/AccountsListView/useAccountsListViewModel.ts b/apps/ledger-live-mobile/src/newArch/features/Accounts/components/AccountsListView/useAccountsListViewModel.ts index ee6e8d83f12d..97ff16ef6350 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Accounts/components/AccountsListView/useAccountsListViewModel.ts +++ b/apps/ledger-live-mobile/src/newArch/features/Accounts/components/AccountsListView/useAccountsListViewModel.ts @@ -20,6 +20,7 @@ export interface Props { sourceScreenName?: ScreenName; isSyncEnabled?: boolean; limitNumberOfAccounts?: number; + specificAccounts?: Account[] | TokenAccount[]; } export type NavigationProp = BaseNavigationComposite< @@ -31,12 +32,13 @@ const useAccountsListViewModel = ({ sourceScreenName, isSyncEnabled = false, limitNumberOfAccounts, + specificAccounts, }: Props) => { const startNavigationTTITimer = useStartProfiler(); const navigation = useNavigation(); const accounts = useSelector(accountsSelector); const walletState = useSelector(walletSelector); - const accountsToDisplay = accounts.slice(0, limitNumberOfAccounts); + const accountsToDisplay = specificAccounts || accounts.slice(0, limitNumberOfAccounts); const refreshAccountsOrdering = useRefreshAccountsOrdering(); useFocusEffect(refreshAccountsOrdering); diff --git a/apps/ledger-live-mobile/src/newArch/features/Accounts/components/AddAccountButton/index.tsx b/apps/ledger-live-mobile/src/newArch/features/Accounts/components/AddAccountButton/index.tsx index ea85222ab161..bbe8fb70489d 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Accounts/components/AddAccountButton/index.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/Accounts/components/AddAccountButton/index.tsx @@ -22,14 +22,19 @@ const StyledPressable = styled(Pressable)` type Props = { sourceScreenName: string; + onClick?: () => void; }; -const AddAccountButton: React.FC = ({ sourceScreenName }) => { +const AddAccountButton: React.FC = ({ sourceScreenName, onClick }) => { const { t } = useTranslation(); const [isAddModalOpened, setAddModalOpened] = useState(false); const openAddModal = () => { track("button_clicked", { button: "Add a new account", page: sourceScreenName }); + if (onClick) { + onClick(); + return; + } setAddModalOpened(true); }; const closeAddModal = () => setAddModalOpened(false); diff --git a/apps/ledger-live-mobile/src/newArch/features/Accounts/screens/AccountsList/index.tsx b/apps/ledger-live-mobile/src/newArch/features/Accounts/screens/AccountsList/index.tsx index 011fa126506a..a09f0cea08ad 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Accounts/screens/AccountsList/index.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/Accounts/screens/AccountsList/index.tsx @@ -1,6 +1,6 @@ -import React from "react"; +import React, { useCallback } from "react"; import AccounstListView from "LLM/features/Accounts/components/AccountsListView"; -import { ScreenName } from "~/const"; +import { NavigatorName, ScreenName } from "~/const"; import { ReactNavigationPerformanceView } from "@shopify/react-native-performance-navigation"; import { StackNavigatorProps } from "~/components/RootNavigator/types/helpers"; import SafeAreaView from "~/components/SafeAreaView"; @@ -14,6 +14,12 @@ import { RefreshMedium } from "@ledgerhq/icons-ui/nativeLegacy"; import { useSelector } from "react-redux"; import Spinning from "~/components/Spinning"; import { isUpToDateSelector } from "~/reducers/accounts"; +import { Account, TokenAccount } from "@ledgerhq/types-live"; +import { + getAccountCurrency, + isTokenAccount as isTokenAccountChecker, +} from "@ledgerhq/live-common/account/index"; +import { useNavigation } from "@react-navigation/native"; type Props = StackNavigatorProps; @@ -24,19 +30,52 @@ export default function AccountsList({ route }: Props) { const showHeader = params?.showHeader; const sourceScreenName = params?.sourceScreenName; const isSyncEnabled = params?.isSyncEnabled; + const specificAccounts = params?.specificAccounts; + const navigation = useNavigation(); + + const isTokenAccount = specificAccounts && isTokenAccountChecker(specificAccounts[0]); + const ticker = specificAccounts + ? isTokenAccount + ? (specificAccounts[0] as TokenAccount).token.ticker + : (specificAccounts[0] as Account).currency.ticker + : undefined; const isUpToDate = useSelector(isUpToDateSelector); const globalSyncState = useGlobalSyncState(); const syncPending = globalSyncState.pending && !isUpToDate; + const onAddAccount = useCallback(() => { + if (!specificAccounts) return; + + const currency = getAccountCurrency(specificAccounts?.[0]); + + if (currency && currency.type === "TokenCurrency") { + navigation.navigate(NavigatorName.AddAccounts, { + screen: undefined, + params: { + token: currency, + }, + }); + } else { + navigation.navigate(NavigatorName.AddAccounts, { + screen: undefined, + currency, + }); + } + }, [navigation, specificAccounts]); + + const onClick = specificAccounts ? onAddAccount : undefined; + return ( <> - + {showHeader && ( - {t("accounts.title")} + {ticker + ? t("accounts.cryptoAccountsTitle", { currencyTicker: ticker }) + : t("accounts.title")} )} {syncPending && ( @@ -49,8 +88,12 @@ export default function AccountsList({ route }: Props) { )} - {canAddAccount && } - + {canAddAccount && } + diff --git a/apps/ledger-live-mobile/src/newArch/features/Accounts/screens/AccountsList/types.ts b/apps/ledger-live-mobile/src/newArch/features/Accounts/screens/AccountsList/types.ts index 21c7f5948651..405cac907e22 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Accounts/screens/AccountsList/types.ts +++ b/apps/ledger-live-mobile/src/newArch/features/Accounts/screens/AccountsList/types.ts @@ -1,3 +1,4 @@ +import { Account, TokenAccount } from "@ledgerhq/types-live"; import { ScreenName } from "~/const"; export type AccountsListNavigator = { @@ -6,5 +7,6 @@ export type AccountsListNavigator = { showHeader?: boolean; canAddAccount?: boolean; isSyncEnabled?: boolean; + specificAccounts?: Account[] | TokenAccount[]; }; }; diff --git a/apps/ledger-live-mobile/src/newArch/features/Assets/screens/AssetsList/index.tsx b/apps/ledger-live-mobile/src/newArch/features/Assets/screens/AssetsList/index.tsx index 55c90f259b5f..67c36da72d7b 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Assets/screens/AssetsList/index.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/Assets/screens/AssetsList/index.tsx @@ -31,7 +31,7 @@ export default function AssetsList({ route }: Props) { <> - + {showHeader && ( {t("assets.title")} diff --git a/apps/ledger-live-mobile/src/screens/WalletCentricAsset/AccountsSection.tsx b/apps/ledger-live-mobile/src/screens/WalletCentricAsset/AccountsSection.tsx index 45ca15c9f373..a9e4d310eae5 100644 --- a/apps/ledger-live-mobile/src/screens/WalletCentricAsset/AccountsSection.tsx +++ b/apps/ledger-live-mobile/src/screens/WalletCentricAsset/AccountsSection.tsx @@ -10,6 +10,9 @@ import { NavigatorName, ScreenName } from "~/const"; import { track } from "~/analytics"; import { AccountsNavigatorParamList } from "~/components/RootNavigator/types/AccountsNavigator"; import { BaseComposite, StackNavigatorProps } from "~/components/RootNavigator/types/helpers"; +import AccountsList from "LLM/features/Accounts/components/AccountsListView"; +import { FeatureToggle, useFeature } from "@ledgerhq/live-common/featureFlags/index"; +import AddAccountButton from "~/newArch/features/Accounts/components/AddAccountButton"; const NB_MAX_ACCOUNTS_TO_DISPLAY = 3; @@ -19,11 +22,13 @@ type ListProps = { accounts: Account[] | TokenAccount[]; currencyId: string; currencyTicker: string; + onAddAccount: () => void; }; -const AccountsSection = ({ accounts, currencyId, currencyTicker }: ListProps) => { +const AccountsSection = ({ accounts, currencyId, currencyTicker, onAddAccount }: ListProps) => { const navigation = useNavigation(); const { t } = useTranslation(); + const llmAccountListUI = useFeature("llmAccountListUI"); const accountsToDisplay = useMemo( () => accounts.slice(0, NB_MAX_ACCOUNTS_TO_DISPLAY), @@ -46,26 +51,50 @@ const AccountsSection = ({ accounts, currencyId, currencyTicker }: ListProps) => track("button_clicked", { button: "See All", }); - navigation.navigate(NavigatorName.Accounts, { - screen: ScreenName.Accounts, - params: { - currencyId, - currencyTicker, - }, - }); - }, [navigation, currencyId, currencyTicker]); + if (llmAccountListUI) { + navigation.navigate(NavigatorName.Accounts, { + screen: ScreenName.AccountsList, + params: { + sourceScreenName: ScreenName.Asset, + showHeader: true, + canAddAccount: true, + isSyncEnabled: true, + specificAccounts: accounts as Account[], + }, + }); + } else { + navigation.navigate(NavigatorName.Accounts, { + screen: ScreenName.Accounts, + params: { + currencyId, + currencyTicker, + }, + }); + } + }, [llmAccountListUI, navigation, accounts, currencyId, currencyTicker]); return ( <> - - data={accountsToDisplay} - renderItem={renderItem} - keyExtractor={item => item.id} - contentContainerStyle={{ flex: 1 }} - /> + + data={accountsToDisplay} + renderItem={renderItem} + keyExtractor={item => item.id} + contentContainerStyle={{ flex: 1 }} + /> + } + > + + + {accounts.length > NB_MAX_ACCOUNTS_TO_DISPLAY ? ( - ) : null} diff --git a/apps/ledger-live-mobile/src/screens/WalletCentricAsset/index.tsx b/apps/ledger-live-mobile/src/screens/WalletCentricAsset/index.tsx index cf50cfb9d7c4..064b3f2efe34 100644 --- a/apps/ledger-live-mobile/src/screens/WalletCentricAsset/index.tsx +++ b/apps/ledger-live-mobile/src/screens/WalletCentricAsset/index.tsx @@ -167,6 +167,7 @@ const AssetScreen = ({ route }: NavigationProps) => { accounts={cryptoAccounts as Account[] | TokenAccount[]} currencyId={currency.id} currencyTicker={currency.ticker} + onAddAccount={onAddAccount} /> , , diff --git a/apps/ledger-live-mobile/src/screens/WalletCentricSections/SectionTitle.tsx b/apps/ledger-live-mobile/src/screens/WalletCentricSections/SectionTitle.tsx index 09ae71d5a7db..74ccf147de3e 100644 --- a/apps/ledger-live-mobile/src/screens/WalletCentricSections/SectionTitle.tsx +++ b/apps/ledger-live-mobile/src/screens/WalletCentricSections/SectionTitle.tsx @@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next"; import proxyStyled from "@ledgerhq/native-ui/components/styled"; import { FlexBoxProps } from "@ledgerhq/native-ui/components/Layout/Flex/index"; import { NavigationProp } from "@react-navigation/native"; +import { useFeature } from "@ledgerhq/live-common/featureFlags/index"; type Props = { title: React.ReactNode; @@ -36,6 +37,7 @@ const SectionTitle = ({ containerProps, }: Props) => { const { t } = useTranslation(); + const llmAccountListUI = useFeature("llmAccountListUI"); const onLinkPress = useCallback(() => { if (onSeeAllPress) { onSeeAllPress(); @@ -55,7 +57,7 @@ const SectionTitle = ({ {title} - {onSeeAllPress || navigatorName ? ( + {(onSeeAllPress || navigatorName) && !llmAccountListUI?.enabled ? ( {seeMoreText || t("common.seeAll")}