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 ? (