diff --git a/.changeset/perfect-shrimps-type.md b/.changeset/perfect-shrimps-type.md new file mode 100644 index 000000000000..c284281a39a4 --- /dev/null +++ b/.changeset/perfect-shrimps-type.md @@ -0,0 +1,6 @@ +--- +"live-mobile": minor +"@ledgerhq/native-ui": minor +--- + +[LIVE-8347] Add quick actions on the wallet diff --git a/apps/ledger-live-mobile/ios/ledgerlivemobile/Info.plist b/apps/ledger-live-mobile/ios/ledgerlivemobile/Info.plist index 48c2fb14530c..2ea724d8f742 100644 --- a/apps/ledger-live-mobile/ios/ledgerlivemobile/Info.plist +++ b/apps/ledger-live-mobile/ios/ledgerlivemobile/Info.plist @@ -22,7 +22,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 3.16.1 + 3.29.1 CFBundleSignature ???? CFBundleURLTypes diff --git a/apps/ledger-live-mobile/src/components/FabActions/actionsList/account/index.tsx b/apps/ledger-live-mobile/src/components/FabActions/actionsList/account/index.tsx index 318d739fe482..50e981bc1bad 100644 --- a/apps/ledger-live-mobile/src/components/FabActions/actionsList/account/index.tsx +++ b/apps/ledger-live-mobile/src/components/FabActions/actionsList/account/index.tsx @@ -32,15 +32,15 @@ export const FabAccountMainActionsComponent: React.FC = "two_columns_" + index} + id="two_columns" + key="two_columns" /> ) : ( "three_columns_" + index} + id="three_columns" + key="three_columns" /> )} diff --git a/apps/ledger-live-mobile/src/components/FabActions/actionsList/asset/index.tsx b/apps/ledger-live-mobile/src/components/FabActions/actionsList/asset/index.tsx index ce3f1b78248d..b63b2f24b862 100644 --- a/apps/ledger-live-mobile/src/components/FabActions/actionsList/asset/index.tsx +++ b/apps/ledger-live-mobile/src/components/FabActions/actionsList/asset/index.tsx @@ -27,15 +27,15 @@ const FabAssetActionsComponent: React.FC = ({ currency, accounts, default "asset_two_columns_" + index} + id="asset_two_columns" + key="asset_two_columns" /> ) : ( "asset_three_columns_" + index} + id="asset_three_columns" + key="asset_three_columns" /> )} diff --git a/apps/ledger-live-mobile/src/components/TabBar/TransferDrawer.tsx b/apps/ledger-live-mobile/src/components/TabBar/TransferDrawer.tsx index 105ae7e2a260..5b4efa65faea 100644 --- a/apps/ledger-live-mobile/src/components/TabBar/TransferDrawer.tsx +++ b/apps/ledger-live-mobile/src/components/TabBar/TransferDrawer.tsx @@ -1,24 +1,23 @@ import React, { useCallback, useMemo } from "react"; -import { useNavigation, useRoute } from "@react-navigation/native"; +import { useNavigation } from "@react-navigation/native"; import { useTranslation } from "react-i18next"; import { useSelector } from "react-redux"; import { ScrollView } from "react-native-gesture-handler"; -import { Flex, IconsLegacy, Text, Box } from "@ledgerhq/native-ui"; +import { Flex, Text, Box } from "@ledgerhq/native-ui"; import { StyleProp, ViewStyle } from "react-native"; import { snakeCase } from "lodash"; import { StackNavigationProp } from "@react-navigation/stack"; import { IconType } from "@ledgerhq/native-ui/components/Icon/type"; import useFeature from "@ledgerhq/live-common/featureFlags/useFeature"; -import { NavigatorName, ScreenName } from "../../const"; -import { accountsCountSelector, areAccountsEmptySelector } from "../../reducers/accounts"; +import { NavigatorName } from "../../const"; import { hasOrderedNanoSelector, readOnlyModeEnabledSelector } from "../../reducers/settings"; import { Props as ModalProps } from "../QueuedDrawer"; import TransferButton from "../TransferButton"; import BuyDeviceBanner, { IMAGE_PROPS_SMALL_NANO } from "../BuyDeviceBanner"; import SetupDeviceBanner from "../SetupDeviceBanner"; import { track, useAnalytics } from "../../analytics"; -import { sharedSwapTracking } from "../../screens/Swap/utils"; import { useToasts } from "@ledgerhq/live-common/notifications/ToastProvider/index"; +import useQuickActions from "../../hooks/useQuickActions"; import { PTX_SERVICES_TOAST_ID } from "../../constants"; type ButtonItem = { @@ -37,19 +36,16 @@ type ButtonItem = { export default function TransferDrawer({ onClose }: Omit) { const navigation = useNavigation(); - const route = useRoute(); + const { + quickActionsList: { SEND, RECEIVE, BUY, SELL, SWAP, STAKE, WALLET_CONNECT }, + } = useQuickActions(); const { t } = useTranslation(); const { pushToast, dismissToast } = useToasts(); - const { page, track } = useAnalytics(); + const { page } = useAnalytics(); const readOnlyModeEnabled = useSelector(readOnlyModeEnabledSelector); - const accountsCount: number = useSelector(accountsCountSelector); const hasOrderedNano = useSelector(hasOrderedNanoSelector); - const areAccountsEmpty = useSelector(areAccountsEmptySelector); - - const walletConnectEntryPoint = useFeature("walletConnectEntryPoint"); - const stakePrograms = useFeature("stakePrograms"); const ptxServiceCtaExchangeDrawer = useFeature("ptxServiceCtaExchangeDrawer"); @@ -64,64 +60,11 @@ export default function TransferDrawer({ onClose }: Omit - onNavigate(NavigatorName.SendFunds, { - screen: ScreenName.SendCoin, - }), - [onNavigate], - ); - const onReceiveFunds = useCallback(() => onNavigate(NavigatorName.ReceiveFunds), [onNavigate]); - - const onStake = useCallback(() => { - track("button_clicked", { - button: "exchange", - page, - flow: "stake", - }); - onNavigate(NavigatorName.StakeFlow, { - screen: ScreenName.Stake, - params: { parentRoute: route }, - }); - }, [onNavigate, page, track, route]); - - const onWalletConnect = useCallback( - () => - onNavigate(NavigatorName.WalletConnect, { - screen: ScreenName.WalletConnectConnect, - }), - [onNavigate], - ); - - const onSwap = useCallback(() => { - track("button_clicked", { - ...sharedSwapTracking, - button: "swap", - page, - }); - onNavigate(NavigatorName.Swap, { - screen: ScreenName.SwapForm, - }); - }, [onNavigate, page, track]); - - const onBuy = useCallback( - () => onNavigate(NavigatorName.Exchange, { screen: ScreenName.ExchangeBuy }), - [onNavigate], - ); - - const onSell = useCallback( - () => onNavigate(NavigatorName.Exchange, { screen: ScreenName.ExchangeSell }), - [onNavigate], - ); - const buttonsList: ButtonItem[] = [ { eventProperties: { @@ -131,9 +74,9 @@ export default function TransferDrawer({ onClose }: Omit 0 && !readOnlyModeEnabled && !areAccountsEmpty ? onSendFunds : null, - Icon: IconsLegacy.ArrowTopMedium, - disabled: !accountsCount || readOnlyModeEnabled || areAccountsEmpty, + onPress: () => onNavigate(...SEND.route), + Icon: SEND.icon, + disabled: SEND.disabled, testID: "transfer-send-button", }, { @@ -144,9 +87,9 @@ export default function TransferDrawer({ onClose }: Omit onNavigate(...RECEIVE.route), + Icon: RECEIVE.icon, + disabled: RECEIVE.disabled, testID: "transfer-receive-button", }, { @@ -158,8 +101,8 @@ export default function TransferDrawer({ onClose }: Omit onNavigate(...BUY.route), onDisabledPress: () => { if (isPtxServiceCtaExchangeDrawerDisabled) { onClose?.(); @@ -172,7 +115,7 @@ export default function TransferDrawer({ onClose }: Omit 0 && !readOnlyModeEnabled && !areAccountsEmpty ? onSell : null, + Icon: SELL.icon, + onPress: () => onNavigate(...SELL.route), onDisabledPress: () => { if (isPtxServiceCtaExchangeDrawerDisabled) { onClose?.(); @@ -197,15 +140,11 @@ export default function TransferDrawer({ onClose }: Omit onNavigate(...STAKE.route), + disabled: STAKE.disabled, testID: "transfer-stake-button", }, ] @@ -230,8 +169,8 @@ export default function TransferDrawer({ onClose }: Omit 0 && !readOnlyModeEnabled && !areAccountsEmpty ? onSwap : null, + Icon: SWAP.icon, + onPress: () => onNavigate(...SWAP.route), onDisabledPress: () => { if (isPtxServiceCtaExchangeDrawerDisabled) { onClose?.(); @@ -244,15 +183,11 @@ export default function TransferDrawer({ onClose }: Omit onNavigate(...WALLET_CONNECT.route), + disabled: WALLET_CONNECT.disabled, testID: "transfer-walletconnect-button", }, ] diff --git a/apps/ledger-live-mobile/src/hooks/useQuickActions.ts b/apps/ledger-live-mobile/src/hooks/useQuickActions.ts new file mode 100644 index 000000000000..4d2f7f2ba7cc --- /dev/null +++ b/apps/ledger-live-mobile/src/hooks/useQuickActions.ts @@ -0,0 +1,132 @@ +import { useMemo } from "react"; +import { useRoute } from "@react-navigation/native"; +import { useSelector } from "react-redux"; +import { IconsLegacy } from "@ledgerhq/native-ui"; +import { type ParamListBase } from "@react-navigation/native"; +import { IconType } from "@ledgerhq/native-ui/components/Icon/type"; +import useFeature from "@ledgerhq/live-common/featureFlags/useFeature"; +import { NavigatorName, ScreenName } from "../const"; +import { accountsCountSelector, areAccountsEmptySelector } from "../reducers/accounts"; +import { readOnlyModeEnabledSelector } from "../reducers/settings"; + +export type QuickAction = { + disabled: boolean; + route: [NavigatorName, { screen: ScreenName; params?: ParamListBase }]; + icon: IconType; +}; + +export type QuickActionsList = { + SEND: QuickAction; + RECEIVE: QuickAction; + BUY: QuickAction; + SELL: QuickAction; + SWAP: QuickAction; + STAKE?: QuickAction; + WALLET_CONNECT?: QuickAction; +}; + +function useQuickActions() { + const route = useRoute(); + + const readOnlyModeEnabled = useSelector(readOnlyModeEnabledSelector); + const accountsCount: number = useSelector(accountsCountSelector); + const areAccountsEmpty = useSelector(areAccountsEmptySelector); + + const walletConnectEntryPoint = useFeature("walletConnectEntryPoint"); + const stakePrograms = useFeature("stakePrograms"); + + const ptxServiceCtaExchangeDrawer = useFeature("ptxServiceCtaExchangeDrawer"); + + const isPtxServiceCtaExchangeDrawerDisabled = useMemo( + () => !(ptxServiceCtaExchangeDrawer?.enabled ?? true), + [ptxServiceCtaExchangeDrawer], + ); + + const quickActionsList = useMemo(() => { + const list: QuickActionsList = { + SEND: { + disabled: !accountsCount || readOnlyModeEnabled || areAccountsEmpty, + route: [ + NavigatorName.SendFunds, + { + screen: ScreenName.SendCoin, + }, + ], + icon: IconsLegacy.ArrowTopMedium, + }, + RECEIVE: { + disabled: readOnlyModeEnabled, + route: [NavigatorName.ReceiveFunds, { screen: ScreenName.ReceiveSelectCrypto }], + icon: IconsLegacy.ArrowBottomMedium, + }, + BUY: { + disabled: isPtxServiceCtaExchangeDrawerDisabled || readOnlyModeEnabled, + route: [NavigatorName.Exchange, { screen: ScreenName.ExchangeBuy }], + icon: IconsLegacy.PlusMedium, + }, + SELL: { + disabled: + isPtxServiceCtaExchangeDrawerDisabled || + !accountsCount || + readOnlyModeEnabled || + areAccountsEmpty, + route: [NavigatorName.Exchange, { screen: ScreenName.ExchangeSell }], + icon: IconsLegacy.MinusMedium, + }, + SWAP: { + disabled: + isPtxServiceCtaExchangeDrawerDisabled || + !accountsCount || + readOnlyModeEnabled || + areAccountsEmpty, + route: [ + NavigatorName.Swap, + { + screen: ScreenName.SwapForm, + }, + ], + icon: IconsLegacy.BuyCryptoMedium, + }, + }; + + if (stakePrograms?.enabled) { + list.STAKE = { + disabled: readOnlyModeEnabled, + route: [ + NavigatorName.StakeFlow, + { + screen: ScreenName.Stake, + params: { parentRoute: route }, + }, + ], + icon: IconsLegacy.ClaimRewardsMedium, + }; + } + + if (walletConnectEntryPoint?.enabled) { + list.WALLET_CONNECT = { + disabled: readOnlyModeEnabled, + route: [ + NavigatorName.WalletConnect, + { + screen: ScreenName.WalletConnectConnect, + }, + ], + icon: IconsLegacy.WalletConnectMedium, + }; + } + return list; + }, [ + accountsCount, + areAccountsEmpty, + isPtxServiceCtaExchangeDrawerDisabled, + readOnlyModeEnabled, + route, + stakePrograms?.enabled, + walletConnectEntryPoint?.enabled, + ]); + + return { quickActionsList }; +} + +export default useQuickActions; diff --git a/apps/ledger-live-mobile/src/locales/en/common.json b/apps/ledger-live-mobile/src/locales/en/common.json index 30d3894d6845..70b2e14d00a2 100644 --- a/apps/ledger-live-mobile/src/locales/en/common.json +++ b/apps/ledger-live-mobile/src/locales/en/common.json @@ -2172,6 +2172,13 @@ "title": "{{currencyTicker}} market price", "currencyPrice": "1 {{currencyTicker}} price", "currencyPriceChange": "Last 24h change" + }, + "quickActions": { + "buy": "Buy", + "swap": "Swap", + "send": "Send", + "deposit": "Deposit", + "stake": "Stake" } }, "addAccountsModal": { diff --git a/apps/ledger-live-mobile/src/screens/Portfolio/PortfolioAssets.tsx b/apps/ledger-live-mobile/src/screens/Portfolio/PortfolioAssets.tsx index 17588ce417cb..411b970cad50 100644 --- a/apps/ledger-live-mobile/src/screens/Portfolio/PortfolioAssets.tsx +++ b/apps/ledger-live-mobile/src/screens/Portfolio/PortfolioAssets.tsx @@ -8,8 +8,11 @@ import { GestureResponderEvent } from "react-native"; import { useDistribution } from "../../actions/general"; import { TrackScreen } from "../../analytics"; import { NavigatorName, ScreenName } from "../../const"; +import { Box } from "@ledgerhq/native-ui"; +import { useFeature } from "@ledgerhq/live-common/featureFlags/index"; import { blacklistedTokenIdsSelector, discreetModeSelector } from "../../reducers/settings"; import Assets from "./Assets"; +import PortfolioQuickActionsBar from "./PortfolioQuickActionsBar"; type Props = { hideEmptyTokenAccount: boolean; @@ -20,6 +23,7 @@ const maxAssetsToDisplay = 5; const PortfolioAssets = ({ hideEmptyTokenAccount, openAddModal }: Props) => { const { t } = useTranslation(); + const llmWalletQuickActions = useFeature("llmWalletQuickActions"); const navigation = useNavigation(); const startNavigationTTITimer = useStartProfiler(); const distribution = useDistribution({ @@ -60,6 +64,11 @@ const PortfolioAssets = ({ hideEmptyTokenAccount, openAddModal }: Props) => { accountsLength={distribution.list && distribution.list.length} discreet={discreetMode} /> + {llmWalletQuickActions?.enabled ? ( + + + + ) : null} {distribution.list.length < maxAssetsToDisplay ? (