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): 🏷️ add memo/tag input in some coins send flow #8155

Merged
merged 11 commits into from
Oct 23, 2024
5 changes: 5 additions & 0 deletions .changeset/strong-spiders-do.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"live-mobile": minor
---

Add memo/tag input in some coins send flow
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const targets = [
"ConnectDevice",
"NoAssociatedAccounts",
"EditOperationPanel",
"MemoTagInput",
];

async function genTarget(target) {
Expand Down
12 changes: 12 additions & 0 deletions apps/ledger-live-mobile/src/families/algorand/MemoTagInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from "react";

import type { Transaction as AlgorandTransaction } from "@ledgerhq/live-common/families/algorand/types";
import type { MemoTagInputProps } from "LLM/features/MemoTag/types";
import { GenericMemoTagInput } from "LLM/features/MemoTag/components/GenericMemoTagInput";

export default (props: MemoTagInputProps) => (
<GenericMemoTagInput<AlgorandTransaction>
{...props}
valueToTxPatch={value => ({ memo: value || undefined })}
/>
);
12 changes: 12 additions & 0 deletions apps/ledger-live-mobile/src/families/cardano/MemoTagInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from "react";

import type { Transaction as CardanoTransaction } from "@ledgerhq/live-common/families/cardano/types";
import type { MemoTagInputProps } from "LLM/features/MemoTag/types";
import { GenericMemoTagInput } from "LLM/features/MemoTag/components/GenericMemoTagInput";

export default (props: MemoTagInputProps) => (
<GenericMemoTagInput<CardanoTransaction>
{...props}
valueToTxPatch={value => ({ memo: value || undefined })}
/>
);
18 changes: 18 additions & 0 deletions apps/ledger-live-mobile/src/families/casper/MemoTagInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from "react";
import { useTranslation } from "react-i18next";

import type { Transaction as CasperTransaction } from "@ledgerhq/live-common/families/casper/types";
import type { MemoTagInputProps } from "LLM/features/MemoTag/types";
import { GenericMemoTagInput } from "LLM/features/MemoTag/components/GenericMemoTagInput";

export default (props: MemoTagInputProps) => {
const { t } = useTranslation();
return (
<GenericMemoTagInput<CasperTransaction>
{...props}
textToValue={text => text.replace(/\D/g, "")}
valueToTxPatch={value => ({ transferId: value || undefined })}
placeholder={t("send.summary.transferId")}
/>
);
};
12 changes: 12 additions & 0 deletions apps/ledger-live-mobile/src/families/cosmos/MemoTagInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from "react";

import type { Transaction as CosmosTransaction } from "@ledgerhq/live-common/families/cosmos/types";
import type { MemoTagInputProps } from "LLM/features/MemoTag/types";
import { GenericMemoTagInput } from "LLM/features/MemoTag/components/GenericMemoTagInput";

export default (props: MemoTagInputProps) => (
<GenericMemoTagInput<CosmosTransaction>
{...props}
valueToTxPatch={value => ({ memo: value || undefined })}
/>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from "react";

import type { Transaction as CryptoOrgTransaction } from "@ledgerhq/live-common/families/crypto_org/types";
import type { MemoTagInputProps } from "LLM/features/MemoTag/types";
import { GenericMemoTagInput } from "LLM/features/MemoTag/components/GenericMemoTagInput";

export default (props: MemoTagInputProps) => (
<GenericMemoTagInput<CryptoOrgTransaction>
{...props}
valueToTxPatch={value => ({ memo: value || undefined })}
/>
);
12 changes: 12 additions & 0 deletions apps/ledger-live-mobile/src/families/hedera/MemoTagInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from "react";

import type { Transaction as HederaTransaction } from "@ledgerhq/live-common/families/hedera/types";
import type { MemoTagInputProps } from "LLM/features/MemoTag/types";
import { GenericMemoTagInput } from "LLM/features/MemoTag/components/GenericMemoTagInput";

export default (props: MemoTagInputProps) => (
<GenericMemoTagInput<HederaTransaction>
{...props}
valueToTxPatch={value => ({ memo: value || undefined })}
/>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from "react";

import { Transaction as ICPTransaction } from "@ledgerhq/live-common/families/internet_computer/types";
import type { MemoTagInputProps } from "LLM/features/MemoTag/types";
import { GenericMemoTagInput } from "LLM/features/MemoTag/components/GenericMemoTagInput";

export default (props: MemoTagInputProps) => (
<GenericMemoTagInput<ICPTransaction>
{...props}
textToValue={text => text.replace(/\D/g, "")}
valueToTxPatch={value => ({ memo: value || undefined })}
/>
);
12 changes: 12 additions & 0 deletions apps/ledger-live-mobile/src/families/solana/MemoTagInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from "react";

import { Transaction as SolanaTransaction } from "@ledgerhq/live-common/generated/types";
import type { MemoTagInputProps } from "LLM/features/MemoTag/types";
import { GenericMemoTagInput } from "LLM/features/MemoTag/components/GenericMemoTagInput";

export default (props: MemoTagInputProps) => (
<GenericMemoTagInput<SolanaTransaction>
{...props}
valueToTxPatch={value => ({ memo: value || undefined })}
/>
);
12 changes: 12 additions & 0 deletions apps/ledger-live-mobile/src/families/stacks/MemoTagInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from "react";

import type { Transaction as StacksTransaction } from "@ledgerhq/live-common/families/stacks/types";
import type { MemoTagInputProps } from "LLM/features/MemoTag/types";
import { GenericMemoTagInput } from "LLM/features/MemoTag/components/GenericMemoTagInput";

export default (props: MemoTagInputProps) => (
<GenericMemoTagInput<StacksTransaction>
{...props}
valueToTxPatch={value => ({ memo: value || undefined })}
/>
);
19 changes: 19 additions & 0 deletions apps/ledger-live-mobile/src/families/ton/MemoTagInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from "react";
import { useTranslation } from "react-i18next";

import type { Transaction as TonTransaction } from "@ledgerhq/live-common/families/ton/types";
import type { MemoTagInputProps } from "LLM/features/MemoTag/types";
import { GenericMemoTagInput } from "LLM/features/MemoTag/components/GenericMemoTagInput";

export default (props: MemoTagInputProps) => {
const { t } = useTranslation();
return (
<GenericMemoTagInput<TonTransaction>
{...props}
valueToTxPatch={value =>
value ? { comment: { isEncrypted: false, text: value } } : { comment: undefined }
}
placeholder={t("send.summary.comment")}
/>
);
};
18 changes: 18 additions & 0 deletions apps/ledger-live-mobile/src/families/xrp/MemoTagInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from "react";
import { useTranslation } from "react-i18next";

import type { Transaction as RippleTransaction } from "@ledgerhq/live-common/families/xrp/types";
import type { MemoTagInputProps } from "LLM/features/MemoTag/types";
import { GenericMemoTagInput } from "LLM/features/MemoTag/components/GenericMemoTagInput";

export default (props: MemoTagInputProps) => {
const { t } = useTranslation();
return (
<GenericMemoTagInput<RippleTransaction>
{...props}
textToValue={text => text.replace(/\D/g, "")}
valueToTxPatch={value => ({ tag: value ? Number(value) : undefined })}
placeholder={t("send.summary.tag")}
/>
);
};
8 changes: 8 additions & 0 deletions apps/ledger-live-mobile/src/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -3112,6 +3112,14 @@
"input": "Enter address",
"inputEns": "Enter address or ENS"
},
"memoTag": {
"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",
"description": "Send crypto to another wallet."
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from "react";

import type { Transaction } from "@ledgerhq/live-common/generated/types";
import { AnimatedInput } from "@ledgerhq/native-ui";
import { MemoTagInputProps } from "../types";

type Props<T extends Transaction = Transaction> = MemoTagInputProps<T> & {
textToValue?: (text: string) => string;
valueToTxPatch: (text: string) => Partial<T>;
};

export function GenericMemoTagInput<T extends Transaction>({
onChange,
valueToTxPatch,
textToValue,
...inputProps
}: Props<T>) {
const [value, setValue] = React.useState("");

const handleChange = (text: string) => {
const value = textToValue?.(text) ?? text;
const patch = valueToTxPatch(value);
setValue(value);
onChange({ value, patch });
};

return <AnimatedInput {...inputProps} value={value} onChangeText={handleChange} />;
}
Original file line number Diff line number Diff line change
@@ -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 (
<QueuedDrawer isRequestingToBeOpened={open} onClose={onClose}>
<Flex alignItems="center" mb={7}>
<Circle size={72} bg={colors.opacityDefault.c05}>
<Icons.InformationFill size="L" color="primary.c80" />
</Circle>
</Flex>

<Text variant="h4" textAlign="center" mb={6}>
{t("transfer.receive.memoTag.title")}
</Text>

<Text variant="bodyLineHeight" textAlign="center" color="neutral.c80" mb={8}>
{t("transfer.receive.memoTag.description")}
</Text>

<Button type="primary" title={t("transfer.memoTag.cta")} onPress={onClose} mb={3} />

<Button
type="tertiary"
title={t("transfer.memoTag.ignore")}
onPress={onNext}
outline
mb={3}
/>

<Button
type="accent"
size="large"
Icon={() => <Icons.ExternalLink size="S" color="primary.c80" />}
onPress={() => Linking.openURL(urls.memoTag)}
>
{t("transfer.memoTag.learnMore")}
</Button>
</QueuedDrawer>
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { FC, useCallback, useState } from "react";

import { useFeature } from "@ledgerhq/live-common/featureFlags/index";
import { Transaction } from "@ledgerhq/live-common/generated/types";
import { CryptoCurrency } from "@ledgerhq/types-cryptoassets";
import perFamily from "~/generated/MemoTagInput";
import { MemoTagInputProps } from "../types";

export const useMemoTagInput = (
family: CryptoCurrency["family"],
updateTransaction: (patch: Partial<Transaction>) => void,
) => {
const featureMemoTag = useFeature("llmMemoTag");
const Input: FC<MemoTagInputProps> | null =
(featureMemoTag?.enabled &&
family in perFamily &&
perFamily[family as keyof typeof perFamily]) ||
null;

const [isEmpty, setIsEmpty] = useState(true);
const handleChange = useCallback<MemoTagInputProps["onChange"]>(
({ patch, value }) => {
setIsEmpty(!value);
updateTransaction(patch);
},
[updateTransaction],
);

return Input && { Input, isEmpty, handleChange };
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { Transaction } from "@ledgerhq/live-common/generated/types";
import type { AnimatedInputProps } from "@ledgerhq/native-ui/components/Form/Input/AnimatedInput";

export type MemoTagInputProps<T extends Transaction = Transaction> = Omit<
AnimatedInputProps,
"value" | "onChangeText" | "onChange"
> & { onChange: (update: { patch: Partial<T>; value: string }) => void };
Loading
Loading