-
Notifications
You must be signed in to change notification settings - Fork 327
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 Stellar memo input on the recipient selection step #8178
Changes from 4 commits
3033b8d
5b4c88a
0364227
4047431
aca7347
1d45fd6
cf79e19
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
--- | ||
"live-mobile": minor | ||
"@ledgerhq/coin-stellar": patch | ||
"@ledgerhq/live-common": patch | ||
--- | ||
|
||
Add Stellar memo input on the recipient selection step |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import React, { useState } from "react"; | ||
import { useTranslation } from "react-i18next"; | ||
|
||
import { isMemoValid } from "@ledgerhq/live-common/families/stellar/bridge/logic"; | ||
import { | ||
StellarWrongMemoFormat, | ||
type Transaction as StellarTransaction, | ||
} from "@ledgerhq/live-common/families/stellar/types"; | ||
import { AnimatedInputSelect } from "@ledgerhq/native-ui"; | ||
import type { MemoTagInputProps } from "LLM/features/MemoTag/types"; | ||
import { MemoTypeDrawer, MEMO_TYPES } from "./MemoTypeDrawer"; | ||
|
||
export default ({ onChange }: MemoTagInputProps<StellarTransaction>) => { | ||
const { t } = useTranslation(); | ||
|
||
const [memoType, setMemoType] = useState<MemoType>("NO_MEMO"); | ||
const [memoValue, setMemoValue] = React.useState(""); | ||
const [isOpen, setIsOpen] = useState(false); | ||
|
||
const handleChange = (type: MemoType, value: string) => { | ||
const error = isMemoValid(type, value) ? undefined : new StellarWrongMemoFormat(); | ||
const patch = { memoType: type, memoValue: value }; | ||
onChange({ value, patch, error }); | ||
}; | ||
|
||
const handleChangeType = (type: MemoType) => { | ||
const value = type === "NO_MEMO" ? "" : memoValue; | ||
handleChange(type, value); | ||
|
||
setMemoType(type); | ||
if (value !== memoValue) setMemoValue(value); | ||
setIsOpen(false); | ||
}; | ||
|
||
const handleChangeValue = (value: string) => { | ||
const type = memoType === "NO_MEMO" && value ? "MEMO_TEXT" : memoType; | ||
handleChange(type, value); | ||
|
||
setMemoValue(value); | ||
if (type !== memoType) setMemoType(type); | ||
}; | ||
|
||
return ( | ||
<> | ||
<AnimatedInputSelect | ||
placeholder={t("send.summary.memo.value")} | ||
value={memoValue} | ||
onChange={handleChangeValue} | ||
selectProps={{ | ||
text: t(MEMO_TYPES.get(memoType) ?? "send.summary.memo.type"), | ||
onPressSelect: () => setIsOpen(true), | ||
}} | ||
/> | ||
|
||
<MemoTypeDrawer | ||
isOpen={isOpen} | ||
closeModal={() => setIsOpen(false)} | ||
value={memoType} | ||
onChange={handleChangeType} | ||
/> | ||
</> | ||
); | ||
}; | ||
|
||
type MemoType = Parameters<(typeof MEMO_TYPES)["get"]>[0]; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import React from "react"; | ||
import { useTranslation } from "react-i18next"; | ||
import { TouchableOpacity, TouchableOpacityProps } from "react-native"; | ||
|
||
import type { StellarMemoType } from "@ledgerhq/live-common/families/stellar/types"; | ||
import { Icons, Text } from "@ledgerhq/native-ui"; | ||
import Circle from "~/components/Circle"; | ||
import QueuedDrawer from "~/components/QueuedDrawer"; | ||
|
||
export const MEMO_TYPES = new Map<MemoType, string>([ | ||
["NO_MEMO", "stellar.memoType.NO_MEMO"], | ||
["MEMO_TEXT", "stellar.memoType.MEMO_TEXT"], | ||
["MEMO_ID", "stellar.memoType.MEMO_ID"], | ||
["MEMO_HASH", "stellar.memoType.MEMO_HASH"], | ||
["MEMO_RETURN", "stellar.memoType.MEMO_RETURN"], | ||
]); | ||
|
||
type Props = { | ||
isOpen: boolean; | ||
closeModal: () => void; | ||
value: MemoType; | ||
onChange: (value: MemoType) => void; | ||
}; | ||
|
||
export function MemoTypeDrawer({ isOpen, closeModal, value, onChange }: Props) { | ||
const { t } = useTranslation(); | ||
return ( | ||
<QueuedDrawer | ||
title={ | ||
<Text variant="h4" textTransform="none"> | ||
{t("send.summary.memo.type")} | ||
</Text> | ||
} | ||
isRequestingToBeOpened={isOpen} | ||
onClose={closeModal} | ||
> | ||
{Array.from(MEMO_TYPES).map(([type, label]) => ( | ||
<Option | ||
key={type} | ||
label={t(label)} | ||
selected={type === value} | ||
onPress={() => onChange(type)} | ||
/> | ||
))} | ||
</QueuedDrawer> | ||
); | ||
} | ||
|
||
type OptionProps = TouchableOpacityProps & { label: string; selected?: boolean }; | ||
function Option({ label, selected = false, onPress }: OptionProps) { | ||
return ( | ||
<TouchableOpacity | ||
style={{ padding: 16, display: "flex", flexDirection: "row", alignItems: "center" }} | ||
onPress={onPress} | ||
> | ||
<Text fontSize="body" fontWeight="semiBold" color={selected ? "primary.c80" : "neutral.c100"}> | ||
{label} | ||
</Text> | ||
{selected && ( | ||
<Circle size={24} style={{ position: "absolute", right: 0 }}> | ||
<Icons.CheckmarkCircleFill size="M" color="primary.c80" /> | ||
</Circle> | ||
)} | ||
</TouchableOpacity> | ||
); | ||
} | ||
|
||
type MemoType = (typeof StellarMemoType)[number]; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,6 +32,7 @@ import NavigationScrollView from "~/components/NavigationScrollView"; | |
import RetryButton from "~/components/RetryButton"; | ||
import { SendFundsNavigatorStackParamList } from "~/components/RootNavigator/types/SendFundsNavigator"; | ||
import { BaseComposite, StackNavigatorProps } from "~/components/RootNavigator/types/helpers"; | ||
import TranslatedError from "~/components/TranslatedError"; | ||
import { ScreenName } from "~/const"; | ||
import { accountScreenSelector } from "~/reducers/accounts"; | ||
import { currencySettingsForAccountSelector } from "~/reducers/settings"; | ||
|
@@ -316,6 +317,9 @@ export default function SendSelectRecipient({ navigation, route }: Props) { | |
placeholder={t("send.summary.memo.title")} | ||
onChange={memoTag.handleChange} | ||
/> | ||
<LText mt={4} pl={2} color="alert"> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You should use |
||
<TranslatedError error={memoTag.error} /> | ||
</LText> | ||
</View> | ||
)} | ||
|
||
|
@@ -337,7 +341,7 @@ export default function SendSelectRecipient({ navigation, route }: Props) { | |
testID="recipient-continue-button" | ||
type="primary" | ||
title={<Trans i18nKey="common.continue" />} | ||
disabled={debouncedBridgePending || !!status.errors.recipient} | ||
disabled={debouncedBridgePending || !!status.errors.recipient || memoTag?.error} | ||
pending={debouncedBridgePending} | ||
onPress={onPressContinue} | ||
/> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,7 +32,13 @@ export enum NetworkCongestionLevel { | |
HIGH = "HIGH", | ||
} | ||
|
||
export const StellarMemoType = ["NO_MEMO", "MEMO_TEXT", "MEMO_ID", "MEMO_HASH", "MEMO_RETURN"]; | ||
export const StellarMemoType = [ | ||
"NO_MEMO", | ||
"MEMO_TEXT", | ||
"MEMO_ID", | ||
"MEMO_HASH", | ||
"MEMO_RETURN", | ||
] as const; | ||
Comment on lines
+35
to
+41
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe each elem could be part of an enum to be more accurate ? and more easy ti use in other component ? like in
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah initially I created a type for this but it required more changes than I was confortable making to the Stellar code. I'm not sure whether there's a good reason for this but rather than risking breaking things I decided not to touch the type of the Stellar transaction and just base my changes on this |
||
|
||
export type StellarTransactionMode = "send" | "changeTrust"; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from "@ledgerhq/coin-stellar/bridge/logic"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why don't use enum for "NO_MEMO" etc? :)
More easier to use and to be sure we have all options!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also wanted to use an enum at first but then I realised that typescript enum can't really be iterated on (I think they lose their type with
Object.values
) without this the code becomes pretty verbose. Plus Mounir mentioned that it would be an anti pattern. Also IMO inferring the type fromStellarMemoType
(defined in Stellar'stypes/bridge.ts
) provides a better type safety.