From ba172f43e6625419c2ddd8e0b1acf83616b32413 Mon Sep 17 00:00:00 2001 From: Theophile Sandoz Date: Wed, 9 Oct 2024 16:17:37 +0200 Subject: [PATCH 01/11] feat(llm): add the memo tag input --- .../src/locales/en/common.json | 3 ++ .../screens/SendFunds/02-SelectRecipient.tsx | 19 ++++++++++++ .../src/screens/SendFunds/MemoTagInput.tsx | 31 +++++++++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 apps/ledger-live-mobile/src/screens/SendFunds/MemoTagInput.tsx diff --git a/apps/ledger-live-mobile/src/locales/en/common.json b/apps/ledger-live-mobile/src/locales/en/common.json index eb55500075d0..eb7e59b7ffa9 100644 --- a/apps/ledger-live-mobile/src/locales/en/common.json +++ b/apps/ledger-live-mobile/src/locales/en/common.json @@ -3112,6 +3112,9 @@ "input": "Enter address", "inputEns": "Enter address or ENS" }, + "memoTag": { + "input": "Enter Tag / Memo" + }, "send": { "title": "Send", "description": "Send crypto to another wallet." diff --git a/apps/ledger-live-mobile/src/screens/SendFunds/02-SelectRecipient.tsx b/apps/ledger-live-mobile/src/screens/SendFunds/02-SelectRecipient.tsx index 67c66a7c2fb0..3d3601b5ec5b 100644 --- a/apps/ledger-live-mobile/src/screens/SendFunds/02-SelectRecipient.tsx +++ b/apps/ledger-live-mobile/src/screens/SendFunds/02-SelectRecipient.tsx @@ -7,6 +7,7 @@ import { SyncSkipUnderPriority, } from "@ledgerhq/live-common/bridge/react/index"; import useBridgeTransaction from "@ledgerhq/live-common/bridge/useBridgeTransaction"; +import FeatureToggle from "@ledgerhq/live-common/featureFlags/FeatureToggle"; import { useFeature } from "@ledgerhq/live-common/featureFlags/index"; import { isNftTransaction } from "@ledgerhq/live-nft"; import { useDebounce } from "@ledgerhq/live-common/hooks/useDebounce"; @@ -29,6 +30,7 @@ import GenericErrorBottomModal from "~/components/GenericErrorBottomModal"; import KeyboardView from "~/components/KeyboardView"; import LText from "~/components/LText"; import NavigationScrollView from "~/components/NavigationScrollView"; +import { MemoTagInpput } from "~/components/MemoTagInpput"; import RetryButton from "~/components/RetryButton"; import { SendFundsNavigatorStackParamList } from "~/components/RootNavigator/types/SendFundsNavigator"; import { BaseComposite, StackNavigatorProps } from "~/components/RootNavigator/types/helpers"; @@ -36,7 +38,9 @@ import { ScreenName } from "~/const"; import { accountScreenSelector } from "~/reducers/accounts"; import { currencySettingsForAccountSelector } from "~/reducers/settings"; import type { State } from "~/reducers/types"; +import { MEMO_TAG_COINS } from "~/utils/constants"; import DomainServiceRecipientRow from "./DomainServiceRecipientRow"; +import { MemoTagInput } from "./MemoTagInput"; import RecipientRow from "./RecipientRow"; const withoutHiddenError = (error: Error): Error | null => @@ -129,6 +133,14 @@ export default function SendSelectRecipient({ navigation, route }: Props) { [account, parentAccount, setTransaction, transaction], ); + const onChangeMemoTag = useCallback( + (tag: number | undefined) => { + const bridge = getAccountBridge(account, parentAccount); + setTransaction(bridge.updateTransaction(transaction, { tag })); + }, + [account, parentAccount, setTransaction, transaction], + ); + const [bridgeErr, setBridgeErr] = useState(bridgeError); useEffect(() => setBridgeErr(bridgeError), [bridgeError]); @@ -283,6 +295,13 @@ export default function SendSelectRecipient({ navigation, route }: Props) { error={error} /> )} + + + {"id" in currency && MEMO_TAG_COINS.includes(currency.id) && ( + + )} + + {isSomeIncomingTxPending ? ( {t("send.pendingTxWarning")} diff --git a/apps/ledger-live-mobile/src/screens/SendFunds/MemoTagInput.tsx b/apps/ledger-live-mobile/src/screens/SendFunds/MemoTagInput.tsx new file mode 100644 index 000000000000..2987ccccd28d --- /dev/null +++ b/apps/ledger-live-mobile/src/screens/SendFunds/MemoTagInput.tsx @@ -0,0 +1,31 @@ +import React, { memo } from "react"; +import { useTranslation } from "react-i18next"; +import { AnimatedInput } from "@ledgerhq/native-ui"; +import { View } from "react-native-animatable"; + +type Props = { + onChange: (value: number | undefined) => void; +}; + +export const MemoTagInput = memo(({ onChange }: Props) => { + const { t } = useTranslation(); + const [value, setValue] = React.useState(""); + + const handleChange = (text: string) => { + const digitStr = text.replace(/\D/g, ""); + const value = digitStr ? parseInt(digitStr, 10) : undefined; + setValue(value?.toString() ?? ""); + onChange(value); + }; + + return ( + + + + ); +}); From b1f40badec4395d6d6e4be9bc34404b88a56880d Mon Sep 17 00:00:00 2001 From: Theophile Sandoz Date: Wed, 9 Oct 2024 18:00:59 +0200 Subject: [PATCH 02/11] feat(llm): add the memo tag drawer --- .../src/locales/en/common.json | 7 ++- .../MemoTag/components/MemoTagDrawer.tsx | 57 +++++++++++++++++++ .../screens/SendFunds/02-SelectRecipient.tsx | 34 ++++++++--- 3 files changed, 88 insertions(+), 10 deletions(-) create mode 100644 apps/ledger-live-mobile/src/newArch/features/MemoTag/components/MemoTagDrawer.tsx diff --git a/apps/ledger-live-mobile/src/locales/en/common.json b/apps/ledger-live-mobile/src/locales/en/common.json index eb7e59b7ffa9..7ad7375b1a8b 100644 --- a/apps/ledger-live-mobile/src/locales/en/common.json +++ b/apps/ledger-live-mobile/src/locales/en/common.json @@ -3113,7 +3113,12 @@ "inputEns": "Enter address or ENS" }, "memoTag": { - "input": "Enter Tag / Memo" + "input": "Enter Tag / Memo", + "title": "Don’t use Tag/Memo?", + "description": "Your funds will get lost if you don’t add a Tag/Memo when sending them to any centralized exchange or custodial wallet.", + "cta": "Add tag", + "ignore": "Don't add tag", + "learnMore": "Learn more about Tag/Memo" }, "send": { "title": "Send", diff --git a/apps/ledger-live-mobile/src/newArch/features/MemoTag/components/MemoTagDrawer.tsx b/apps/ledger-live-mobile/src/newArch/features/MemoTag/components/MemoTagDrawer.tsx new file mode 100644 index 000000000000..2564e6aa5506 --- /dev/null +++ b/apps/ledger-live-mobile/src/newArch/features/MemoTag/components/MemoTagDrawer.tsx @@ -0,0 +1,57 @@ +import { useTheme } from "styled-components/native"; +import React, { memo } from "react"; +import { useTranslation } from "react-i18next"; +import { Linking } from "react-native"; +import { Flex, Icons, Text } from "@ledgerhq/native-ui"; +import Button from "~/components/Button"; +import Circle from "~/components/Circle"; +import QueuedDrawer from "~/components/QueuedDrawer"; +import { urls } from "~/utils/urls"; + +type Props = { + open: boolean; + onClose: () => void; + onNext: () => void; +}; + +export const MemoTagDrawer = memo(({ open, onClose, onNext }: Props) => { + const { colors } = useTheme(); + const { t } = useTranslation(); + + return ( + + + + + + + + + {t("transfer.receive.memoTag.title")} + + + + {t("transfer.receive.memoTag.description")} + + + + + ); +}); diff --git a/apps/ledger-live-mobile/src/screens/SendFunds/02-SelectRecipient.tsx b/apps/ledger-live-mobile/src/screens/SendFunds/02-SelectRecipient.tsx index 3d3601b5ec5b..5a576df91a36 100644 --- a/apps/ledger-live-mobile/src/screens/SendFunds/02-SelectRecipient.tsx +++ b/apps/ledger-live-mobile/src/screens/SendFunds/02-SelectRecipient.tsx @@ -7,7 +7,6 @@ import { SyncSkipUnderPriority, } from "@ledgerhq/live-common/bridge/react/index"; import useBridgeTransaction from "@ledgerhq/live-common/bridge/useBridgeTransaction"; -import FeatureToggle from "@ledgerhq/live-common/featureFlags/FeatureToggle"; import { useFeature } from "@ledgerhq/live-common/featureFlags/index"; import { isNftTransaction } from "@ledgerhq/live-nft"; import { useDebounce } from "@ledgerhq/live-common/hooks/useDebounce"; @@ -30,7 +29,6 @@ import GenericErrorBottomModal from "~/components/GenericErrorBottomModal"; import KeyboardView from "~/components/KeyboardView"; import LText from "~/components/LText"; import NavigationScrollView from "~/components/NavigationScrollView"; -import { MemoTagInpput } from "~/components/MemoTagInpput"; import RetryButton from "~/components/RetryButton"; import { SendFundsNavigatorStackParamList } from "~/components/RootNavigator/types/SendFundsNavigator"; import { BaseComposite, StackNavigatorProps } from "~/components/RootNavigator/types/helpers"; @@ -39,6 +37,7 @@ import { accountScreenSelector } from "~/reducers/accounts"; import { currencySettingsForAccountSelector } from "~/reducers/settings"; import type { State } from "~/reducers/types"; import { MEMO_TAG_COINS } from "~/utils/constants"; +import { MemoTagDrawer } from "LLM/features/MemoTag/components/MemoTagDrawer"; import DomainServiceRecipientRow from "./DomainServiceRecipientRow"; import { MemoTagInput } from "./MemoTagInput"; import RecipientRow from "./RecipientRow"; @@ -158,7 +157,21 @@ export default function SendSelectRecipient({ navigation, route }: Props) { setTransaction(bridge.updateTransaction(transaction, {})); }, [setTransaction, account, parentAccount, transaction]); - const onPressContinue = useCallback(async () => { + const featureMemoTag = + useFeature("llmMemoTag") && "id" in currency && MEMO_TAG_COINS.includes(currency.id); + + const [memoTagDrawerState, setMemoTagDrawerState] = useState<"INITIAL" | "SHOWING" | "SHOWN">( + "INITIAL", + ); + + const onPressContinue = useCallback(() => { + const tag = "tag" in transaction && transaction.tag; + if (memoTagDrawerState === "INITIAL" && featureMemoTag && typeof tag === "undefined") { + return setMemoTagDrawerState("SHOWING"); + } + + track("SendRecipientContinue"); + // ERC721 transactions are always sending 1 NFT, so amount step is unecessary if (shouldSkipAmount) { return navigation.navigate(ScreenName.SendSummary, { @@ -192,6 +205,8 @@ export default function SendSelectRecipient({ navigation, route }: Props) { navigation, parentAccount?.id, route.params, + featureMemoTag, + memoTagDrawerState, ]); if (!account || !transaction) return null; @@ -296,11 +311,7 @@ export default function SendSelectRecipient({ navigation, route }: Props) { /> )} - - {"id" in currency && MEMO_TAG_COINS.includes(currency.id) && ( - - )} - + {featureMemoTag && } {isSomeIncomingTxPending ? ( @@ -318,7 +329,6 @@ export default function SendSelectRecipient({ navigation, route }: Props) {