Skip to content

Commit fd24251

Browse files
authored
fix: cp-7.55.0 amount input related fixes (MetaMask#19380)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** PR addressed different amount display related issues. ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: ## **Related issues** Fixes: MetaMask#18976 Fixes: MetaMask#19004 Fixes: MetaMask#19005 Fixes: MetaMask#19006 Fixes: MetaMask#18963 ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** https://github.com/user-attachments/assets/8393d736-d310-46d0-9378-57dac788fd21 ## **Pre-merge author checklist** - [X] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [X] I've completed the PR template to the best of my ability - [X] I’ve included tests if applicable - [X] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [X] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.
1 parent cbc87c4 commit fd24251

10 files changed

Lines changed: 224 additions & 60 deletions

File tree

app/components/Views/confirmations/components/edit-amount-keyboard/edit-amount-keyboard.test.tsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,4 +129,42 @@ describe('EditAmountKeyboard', () => {
129129

130130
expect(getByText('Additional Row')).toBeDefined();
131131
});
132+
133+
it('calls onchange with "0 if enableEmptyValueString is false and back button is pressed to make value empty', () => {
134+
const onChangeMock = jest.fn();
135+
136+
const { getByTestId } = render(
137+
<EditAmountKeyboard
138+
enableEmptyValueString
139+
onChange={onChangeMock}
140+
onDonePress={noop}
141+
onPercentagePress={noop}
142+
value={'0'}
143+
/>,
144+
);
145+
146+
const backButton = getByTestId('keypad-delete-button');
147+
fireEvent.press(backButton);
148+
149+
expect(onChangeMock).toHaveBeenCalledWith('');
150+
});
151+
152+
it('calls onchange with empty string if enableEmptyValueString is true and back button is pressed to make value empty', () => {
153+
const onChangeMock = jest.fn();
154+
155+
const { getByTestId } = render(
156+
<EditAmountKeyboard
157+
enableEmptyValueString
158+
onChange={onChangeMock}
159+
onDonePress={noop}
160+
onPercentagePress={noop}
161+
value={'0'}
162+
/>,
163+
);
164+
165+
const backButton = getByTestId('keypad-delete-button');
166+
fireEvent.press(backButton);
167+
168+
expect(onChangeMock).toHaveBeenCalledWith('');
169+
});
132170
});

app/components/Views/confirmations/components/edit-amount-keyboard/edit-amount-keyboard.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import React, { ReactNode, useCallback } from 'react';
22
import { View } from 'react-native';
33

4-
import KeypadComponent, { KeypadChangeData } from '../../../../Base/Keypad';
4+
import KeypadComponent, {
5+
KeypadChangeData,
6+
Keys,
7+
} from '../../../../Base/Keypad';
58
import { useStyles } from '../../../../hooks/useStyles';
69
import styleSheet from './edit-amount-keyboard.styles';
710
import Button, {
@@ -26,6 +29,7 @@ export interface EditAmountKeyboardProps {
2629
hideDoneButton?: boolean;
2730
showAdditionalKeyboard?: boolean;
2831
additionalRow?: ReactNode;
32+
enableEmptyValueString?: boolean;
2933
}
3034

3135
export function EditAmountKeyboard({
@@ -37,14 +41,20 @@ export function EditAmountKeyboard({
3741
hideDoneButton = false,
3842
showAdditionalKeyboard = true,
3943
additionalRow,
44+
enableEmptyValueString = false,
4045
}: Readonly<EditAmountKeyboardProps>) {
4146
const { styles } = useStyles(styleSheet, {});
4247

4348
const handleChange = useCallback(
4449
(data: KeypadChangeData) => {
45-
onChange(data.value);
50+
const { pressedKey, value } = data;
51+
if (pressedKey === Keys.Back && value === '0' && enableEmptyValueString) {
52+
onChange('');
53+
return;
54+
}
55+
onChange(value);
4656
},
47-
[onChange],
57+
[enableEmptyValueString, onChange],
4858
);
4959

5060
return (

app/components/Views/confirmations/components/send/amount/amount-keyboard/amount-keyboard.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ export const AmountKeyboard = ({
108108
/>
109109
) : undefined
110110
}
111+
enableEmptyValueString
111112
hideDoneButton
112113
onChange={updateToNewAmount}
113114
onPercentagePress={updateToPercentageAmount}

app/components/Views/confirmations/components/send/amount/amount.styles.ts

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,32 @@ import {
77
JustifyContent,
88
} from '../../../../../UI/Box/box.types';
99

10-
export const getFontSizeForInputLength = (contentLength: number) => {
11-
if (contentLength < 10) {
10+
export const getFontSizeForInputLength = (
11+
inputLength: number,
12+
symbolLength: number,
13+
) => {
14+
if (inputLength < 5 && symbolLength < 6) {
1215
return 60;
1316
}
14-
if (contentLength < 12) {
17+
if (inputLength < 6 && symbolLength < 7) {
1518
return 48;
1619
}
17-
if (contentLength < 18) {
20+
if (inputLength < 9 && symbolLength < 9) {
1821
return 32;
1922
}
20-
if (contentLength < 24) {
23+
if (inputLength < 12 && symbolLength < 12) {
2124
return 24;
2225
}
23-
return 18;
26+
if (inputLength < 16 && symbolLength < 16) {
27+
return 18;
28+
}
29+
return 12;
2430
};
2531

2632
export const styleSheet = (params: {
2733
theme: Theme;
2834
vars: {
35+
fiatMode: boolean;
2936
inputError: boolean;
3037
inputLength: number;
3138
isNFT: boolean;
@@ -34,7 +41,7 @@ export const styleSheet = (params: {
3441
}) => {
3542
const {
3643
theme,
37-
vars: { inputError, inputLength, isNFT, symbolLength },
44+
vars: { fiatMode, inputError, inputLength, isNFT, symbolLength },
3845
} = params;
3946
return StyleSheet.create({
4047
balanceSection: {
@@ -62,42 +69,48 @@ export const styleSheet = (params: {
6269
color: inputError
6370
? theme.colors.error.default
6471
: theme.colors.text.default,
65-
fontSize: getFontSizeForInputLength(inputLength + symbolLength),
66-
minWidth: '30%',
72+
fontSize: getFontSizeForInputLength(inputLength, symbolLength),
6773
includeFontPadding: false,
6874
textAlignVertical: 'center',
6975
paddingTop: Platform.OS === 'ios' ? 0 : 2,
7076
// Dynamic height for large fonts:
7177
height: Math.max(
7278
50,
73-
getFontSizeForInputLength(inputLength + symbolLength) + 10,
79+
getFontSizeForInputLength(inputLength, symbolLength) + 10,
7480
),
7581
},
7682
inputSection: {
7783
flexDirection: FlexDirection.Row,
78-
justifyContent:
79-
inputLength < 5 ? JustifyContent.center : JustifyContent.flexEnd,
84+
justifyContent: JustifyContent.center,
8085
marginTop: isNFT ? 0 : 80,
8186
width: '100%',
8287
},
8388
inputWrapper: {
8489
alignItems: AlignItems.center,
8590
flexDirection: FlexDirection.Row,
86-
justifyContent: JustifyContent.flexEnd,
91+
justifyContent: fiatMode
92+
? JustifyContent.flexStart
93+
: JustifyContent.flexEnd,
94+
width: '50%',
8795
},
8896
nftImage: { alignSelf: 'center', height: 100, width: 100 },
8997
nftImageWrapper: {
9098
alignItems: AlignItems.center,
9199
marginTop: 32,
92100
width: '100%',
93101
},
102+
tokenSymbolWrapper: {
103+
justifyContent: fiatMode
104+
? JustifyContent.flexEnd
105+
: JustifyContent.flexStart,
106+
width: '50%',
107+
},
94108
tokenSymbol: {
95109
alignItems: AlignItems.center,
96-
alignSelf: 'flex-end',
97-
fontSize: getFontSizeForInputLength(inputLength + symbolLength),
110+
alignSelf: fiatMode ? AlignItems.flexEnd : AlignItems.flexStart,
111+
fontSize: getFontSizeForInputLength(inputLength, symbolLength),
98112
lineHeight: 75,
99113
paddingLeft: 2,
100-
textAlign: 'left',
101114
},
102115
topSection: {
103116
paddingHorizontal: 8,

app/components/Views/confirmations/components/send/amount/amount.test.tsx

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ jest.mock('@react-navigation/native', () => ({
7171
}));
7272

7373
import { useAmountSelectionMetrics } from '../../../hooks/send/metrics/useAmountSelectionMetrics';
74+
import { getFontSizeForInputLength } from './amount.styles';
7475
const mockedUseAmountSelectionMetrics = jest.mocked(useAmountSelectionMetrics);
7576

7677
const renderComponent = (mockState?: ProviderValues['state']) => {
@@ -146,7 +147,7 @@ describe('Amount', () => {
146147
act(() => {
147148
fireEvent.changeText(getByTestId('send_amount'), '1');
148149
});
149-
expect(getByText('$ 3890.00')).toBeTruthy();
150+
expect(getByText('$ 3890')).toBeTruthy();
150151
});
151152

152153
it('display fiat conversion of amount entered for solana asset', () => {
@@ -160,7 +161,7 @@ describe('Amount', () => {
160161
act(() => {
161162
fireEvent.changeText(getByTestId('send_amount'), '1');
162163
});
163-
expect(getByText('$ 175.00')).toBeTruthy();
164+
expect(getByText('$ 175')).toBeTruthy();
164165
});
165166

166167
it('if fiatmode is enabled display native conversion of amount entered', () => {
@@ -212,11 +213,11 @@ describe('Amount', () => {
212213
act(() => {
213214
fireEvent.press(getByTestId('fiat_toggle'));
214215
});
215-
expect(mockSetAmountInputTypeToken).toHaveBeenCalled();
216+
expect(mockSetAmountInputTypeFiat).toHaveBeenCalled();
216217
act(() => {
217218
fireEvent.press(getByTestId('fiat_toggle'));
218219
});
219-
expect(mockSetAmountInputTypeFiat).toHaveBeenCalled();
220+
expect(mockSetAmountInputTypeToken).toHaveBeenCalled();
220221
});
221222

222223
it('fiatmode is not avaialble for NFT', () => {
@@ -526,3 +527,25 @@ describe('Amount', () => {
526527
// expect(mockCaptureAmountSelected).toHaveBeenCalled();
527528
// });
528529
});
530+
531+
describe('getFontSizeForInputLength', () => {
532+
it('renders correct font size using input and symbol length', () => {
533+
expect(getFontSizeForInputLength(1, 1)).toEqual(60);
534+
expect(getFontSizeForInputLength(5, 1)).toEqual(48);
535+
expect(getFontSizeForInputLength(1, 6)).toEqual(48);
536+
expect(getFontSizeForInputLength(5, 6)).toEqual(48);
537+
expect(getFontSizeForInputLength(6, 6)).toEqual(32);
538+
expect(getFontSizeForInputLength(5, 7)).toEqual(32);
539+
expect(getFontSizeForInputLength(6, 7)).toEqual(32);
540+
expect(getFontSizeForInputLength(9, 7)).toEqual(24);
541+
expect(getFontSizeForInputLength(6, 9)).toEqual(24);
542+
expect(getFontSizeForInputLength(9, 9)).toEqual(24);
543+
expect(getFontSizeForInputLength(12, 9)).toEqual(18);
544+
expect(getFontSizeForInputLength(9, 12)).toEqual(18);
545+
expect(getFontSizeForInputLength(12, 12)).toEqual(18);
546+
expect(getFontSizeForInputLength(16, 12)).toEqual(12);
547+
expect(getFontSizeForInputLength(12, 16)).toEqual(12);
548+
expect(getFontSizeForInputLength(16, 16)).toEqual(12);
549+
expect(getFontSizeForInputLength(18, 18)).toEqual(12);
550+
});
551+
});

app/components/Views/confirmations/components/send/amount/amount.tsx

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,18 @@ export const Amount = () => {
3939
const [fiatMode, setFiatMode] = useState(primaryCurrency === 'Fiat');
4040
const {
4141
fiatCurrencySymbol,
42+
getFiatValue,
4243
getFiatDisplayValue,
43-
getNativeDisplayValue,
4444
getNativeValue,
45+
getNativeDisplayValue,
4546
} = useCurrencyConversions();
4647
const isNFT = asset?.standard === TokenStandard.ERC1155;
4748
const assetSymbol = isNFT
4849
? undefined
4950
: (asset as AssetType)?.ticker ?? (asset as AssetType)?.symbol;
5051
const assetDisplaySymbol = assetSymbol ?? (isNFT ? 'NFT' : '');
5152
const { styles, theme } = useStyles(styleSheet, {
53+
fiatMode,
5254
inputError: Boolean(amountError),
5355
inputLength: amount.length,
5456
isNFT,
@@ -87,21 +89,25 @@ export const Amount = () => {
8789
);
8890

8991
const toggleFiatMode = useCallback(() => {
90-
if (!fiatMode) {
91-
setAmountInputTypeToken();
92-
} else {
92+
const newFiatMode = !fiatMode;
93+
if (newFiatMode) {
9394
setAmountInputTypeFiat();
95+
} else {
96+
setAmountInputTypeToken();
97+
}
98+
setFiatMode(newFiatMode);
99+
if (amount !== undefined) {
100+
setAmount(newFiatMode ? getFiatValue(amount) : getNativeValue(amount));
94101
}
95-
setFiatMode(!fiatMode);
96-
setAmount('');
97-
updateValue('');
98102
}, [
103+
amount,
99104
fiatMode,
105+
getFiatValue,
106+
getNativeValue,
100107
setAmount,
101108
setAmountInputTypeFiat,
102109
setAmountInputTypeToken,
103110
setFiatMode,
104-
updateValue,
105111
]);
106112

107113
const balanceUnit =
@@ -128,26 +134,42 @@ export const Amount = () => {
128134
</View>
129135
)}
130136
<View style={styles.inputSection}>
137+
{fiatMode && (
138+
<View style={styles.tokenSymbolWrapper}>
139+
<Text
140+
color={amountError ? TextColor.Error : TextColor.Alternative}
141+
numberOfLines={1}
142+
style={styles.tokenSymbol}
143+
variant={TextVariant.DisplayLG}
144+
>
145+
{fiatCurrencySymbol}
146+
</Text>
147+
</View>
148+
)}
131149
<View style={styles.inputWrapper}>
132150
<Input
133151
cursorColor={theme.colors.primary.default}
134152
onChangeText={updateToNewAmount}
135153
style={styles.input}
136154
testID="send_amount"
137-
textAlign="right"
155+
textAlign={fiatMode ? 'left' : 'right'}
138156
textVariant={TextVariant.DisplayLG}
139157
value={amount}
140158
showSoftInputOnFocus={false}
141159
/>
142160
</View>
143-
<Text
144-
color={amountError ? TextColor.Error : TextColor.Alternative}
145-
numberOfLines={1}
146-
style={styles.tokenSymbol}
147-
variant={TextVariant.DisplayLG}
148-
>
149-
{fiatMode ? fiatCurrencySymbol : assetDisplaySymbol}
150-
</Text>
161+
{!fiatMode && (
162+
<View style={styles.tokenSymbolWrapper}>
163+
<Text
164+
color={amountError ? TextColor.Error : TextColor.Alternative}
165+
numberOfLines={1}
166+
style={styles.tokenSymbol}
167+
variant={TextVariant.DisplayLG}
168+
>
169+
{assetDisplaySymbol}
170+
</Text>
171+
</View>
172+
)}
151173
</View>
152174
{!isNFT && (
153175
<TagBase shape={TagShape.Pill} style={styles.currencyTag}>

app/components/Views/confirmations/hooks/send/useCurrencyConversions.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ describe('getFiatValueFn', () => {
3131
amount: '10',
3232
decimals: 2,
3333
}),
34-
).toStrictEqual(38905.56);
34+
).toStrictEqual('38905.56');
3535
});
3636

3737
it('return 0 if input is empty string', () => {
@@ -42,7 +42,7 @@ describe('getFiatValueFn', () => {
4242
amount: '',
4343
decimals: 2,
4444
}),
45-
).toStrictEqual(0);
45+
).toStrictEqual('0');
4646
});
4747

4848
it('use conversionRate 1 if conversionRate is not passed', () => {
@@ -53,7 +53,7 @@ describe('getFiatValueFn', () => {
5353
amount: '10',
5454
decimals: 2,
5555
}),
56-
).toStrictEqual(38905.56);
56+
).toStrictEqual('38905.56');
5757
});
5858
});
5959

0 commit comments

Comments
 (0)