Skip to content

Commit 6f23790

Browse files
authored
chore(runway): cherry-pick fix: cp-7.47.0 bridge input field behaving erratically (#15622)
- fix: cp-7.47.0 bridge input field behaving erratically (#15524) <!-- 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** <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> This PR: - Fixes an issue where the source token input would jump around erratically when inputting an amount. - Limits the max length to 18 - Scales the font size of the input field with the length of the input ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to Bridge 2. Enter in many digits for source token 3. Observe that it's limited to 18 digits and 5 decimals, and the size changes with the length ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> https://github.com/user-attachments/assets/eb1b8e33-66e4-474c-a80b-fd5b4be97140 ## **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 - [ ] I’ve included tests if applicable - [ ] 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. [ca71397](ca71397)
2 parents bc8a523 + 2ca6ce9 commit 6f23790

4 files changed

Lines changed: 153 additions & 20 deletions

File tree

app/components/UI/Bridge/Views/BridgeView/__snapshots__/BridgeView.test.tsx.snap

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -399,12 +399,6 @@ exports[`BridgeView Bottom Content blurs input when opening QuoteExpiredModal 1`
399399
onBlur={[Function]}
400400
onFocus={[Function]}
401401
placeholder="0"
402-
selection={
403-
{
404-
"end": 0,
405-
"start": 0,
406-
}
407-
}
408402
showSoftInputOnFocus={false}
409403
style={
410404
{
@@ -1365,12 +1359,6 @@ exports[`BridgeView renders 1`] = `
13651359
onBlur={[Function]}
13661360
onFocus={[Function]}
13671361
placeholder="0"
1368-
selection={
1369-
{
1370-
"end": 0,
1371-
"start": 0,
1372-
}
1373-
}
13741362
showSoftInputOnFocus={false}
13751363
style={
13761364
{

app/components/UI/Bridge/Views/BridgeView/index.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useSelector, useDispatch } from 'react-redux';
33
import ScreenView from '../../../../Base/ScreenView';
44
import Keypad from '../../../../Base/Keypad';
55
import {
6+
MAX_INPUT_LENGTH,
67
TokenInputArea,
78
TokenInputAreaType,
89
} from '../../components/TokenInputArea';
@@ -267,6 +268,9 @@ const BridgeView = () => {
267268
valueAsNumber: number;
268269
pressedKey: string;
269270
}) => {
271+
if (value.length >= MAX_INPUT_LENGTH) {
272+
return;
273+
}
270274
dispatch(setSourceAmount(value || undefined));
271275
};
272276

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import React from 'react';
2+
import { fireEvent } from '@testing-library/react-native';
3+
import { renderScreen } from '../../../../../util/test/renderWithProvider';
4+
import { TokenInputArea, TokenInputAreaType, calculateFontSize, getDisplayAmount } from '.';
5+
import { initialState } from '../../_mocks_/initialState';
6+
7+
const mockOnTokenPress = jest.fn();
8+
const mockOnFocus = jest.fn();
9+
const mockOnBlur = jest.fn();
10+
const mockOnInputPress = jest.fn();
11+
12+
describe('TokenInputArea', () => {
13+
beforeEach(() => {
14+
jest.clearAllMocks();
15+
});
16+
17+
it('renders with initial state', () => {
18+
const { getByTestId } = renderScreen(
19+
() => (
20+
<TokenInputArea
21+
testID="token-input"
22+
tokenType={TokenInputAreaType.Source}
23+
onTokenPress={mockOnTokenPress}
24+
onFocus={mockOnFocus}
25+
onBlur={mockOnBlur}
26+
onInputPress={mockOnInputPress}
27+
/>
28+
),
29+
{
30+
name: 'TokenInputArea',
31+
},
32+
{ state: initialState },
33+
);
34+
35+
expect(getByTestId('token-input-input')).toBeTruthy();
36+
});
37+
38+
it('handles input focus and blur correctly', () => {
39+
const { getByTestId } = renderScreen(
40+
() => (
41+
<TokenInputArea
42+
testID="token-input"
43+
tokenType={TokenInputAreaType.Source}
44+
onFocus={mockOnFocus}
45+
onBlur={mockOnBlur}
46+
/>
47+
),
48+
{
49+
name: 'TokenInputArea',
50+
},
51+
{ state: initialState },
52+
);
53+
54+
const input = getByTestId('token-input-input');
55+
fireEvent(input, 'focus');
56+
expect(mockOnFocus).toHaveBeenCalled();
57+
58+
fireEvent(input, 'blur');
59+
expect(mockOnBlur).toHaveBeenCalled();
60+
});
61+
});
62+
63+
describe('calculateFontSize', () => {
64+
it('returns 40 for lengths up to 10', () => {
65+
expect(calculateFontSize(5)).toBe(40);
66+
expect(calculateFontSize(10)).toBe(40);
67+
});
68+
69+
it('returns 35 for lengths between 11 and 15', () => {
70+
expect(calculateFontSize(11)).toBe(35);
71+
expect(calculateFontSize(15)).toBe(35);
72+
});
73+
74+
it('returns 30 for lengths between 16 and 20', () => {
75+
expect(calculateFontSize(16)).toBe(30);
76+
expect(calculateFontSize(20)).toBe(30);
77+
});
78+
79+
it('returns 25 for lengths between 21 and 25', () => {
80+
expect(calculateFontSize(21)).toBe(25);
81+
expect(calculateFontSize(25)).toBe(25);
82+
});
83+
84+
it('returns 20 for lengths greater than 25', () => {
85+
expect(calculateFontSize(26)).toBe(20);
86+
expect(calculateFontSize(100)).toBe(20);
87+
});
88+
});
89+
90+
describe('getDisplayAmount', () => {
91+
it('returns undefined for undefined input', () => {
92+
expect(getDisplayAmount(undefined)).toBeUndefined();
93+
});
94+
95+
it('returns full amount for source type when under max length', () => {
96+
const amount = '123456789012345678';
97+
expect(getDisplayAmount(amount, TokenInputAreaType.Source)).toBe(amount);
98+
});
99+
100+
it('returns full amount for source type when over max length', () => {
101+
const amount = '1234567890123456789';
102+
expect(getDisplayAmount(amount, TokenInputAreaType.Source)).toBe(amount);
103+
});
104+
105+
it('parses amount for destination type', () => {
106+
const amount = '1234567890123456789.12345';
107+
expect(getDisplayAmount(amount, TokenInputAreaType.Destination)).toBe('1234567890123456789.12345');
108+
});
109+
});

app/components/UI/Bridge/components/TokenInputArea/index.tsx

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,20 @@ import parseAmount from '../../../Ramp/utils/parseAmount';
3838
import useIsInsufficientBalance from '../../hooks/useInsufficientBalance';
3939

4040
const MAX_DECIMALS = 5;
41+
export const MAX_INPUT_LENGTH = 18;
4142

42-
const createStyles = () =>
43+
/**
44+
* Calculates font size based on input length
45+
*/
46+
export const calculateFontSize = (length: number): number => {
47+
if (length <= 10) return 40;
48+
if (length <= 15) return 35;
49+
if (length <= 20) return 30;
50+
if (length <= 25) return 25;
51+
return 20;
52+
};
53+
54+
const createStyles = ({ vars }: { vars: { fontSize: number } }) =>
4355
StyleSheet.create({
4456
content: {
4557
paddingVertical: 16,
@@ -53,21 +65,34 @@ const createStyles = () =>
5365
flex: 1,
5466
},
5567
input: {
56-
fontSize: 40,
5768
borderWidth: 0,
5869
lineHeight: 50,
5970
height: 50,
71+
fontSize: vars.fontSize,
6072
},
6173
});
6274

63-
const formatAddress = (address?: string) =>
64-
address ? `${address.slice(0, 6)}...${address.slice(-4)}` : undefined;
65-
6675
export enum TokenInputAreaType {
6776
Source = 'source',
6877
Destination = 'destination',
6978
}
7079

80+
const formatAddress = (address?: string) =>
81+
address ? `${address.slice(0, 6)}...${address.slice(-4)}` : undefined;
82+
83+
export const getDisplayAmount = (
84+
amount?: string,
85+
tokenType?: TokenInputAreaType,
86+
) => {
87+
if (amount === undefined) return amount;
88+
89+
const displayAmount = tokenType === TokenInputAreaType.Source
90+
? amount
91+
: parseAmount(amount, MAX_DECIMALS);
92+
93+
return displayAmount;
94+
};
95+
7196
export interface TokenInputAreaRef {
7297
blur: () => void;
7398
}
@@ -130,7 +155,6 @@ export const TokenInputArea = forwardRef<
130155
},
131156
}));
132157

133-
const { styles } = useStyles(createStyles, {});
134158
const navigation = useNavigation();
135159

136160
const navigateToDestNetworkSelector = () => {
@@ -181,6 +205,10 @@ export const TokenInputArea = forwardRef<
181205
? formattedBalance
182206
: formattedAddress;
183207

208+
const displayedAmount = getDisplayAmount(amount, tokenType);
209+
const fontSize = calculateFontSize(displayedAmount?.length ?? 0);
210+
const { styles } = useStyles(createStyles, { fontSize });
211+
184212
return (
185213
<Box>
186214
<Box style={styles.content} gap={4}>
@@ -191,7 +219,7 @@ export const TokenInputArea = forwardRef<
191219
) : (
192220
<Input
193221
ref={inputRef}
194-
value={amount ? parseAmount(amount, MAX_DECIMALS) : amount}
222+
value={displayedAmount}
195223
style={styles.input}
196224
isDisabled={false}
197225
isReadonly={tokenType === TokenInputAreaType.Destination}
@@ -209,7 +237,11 @@ export const TokenInputArea = forwardRef<
209237
}}
210238
// Android only issue, for long numbers, the input field will focus on the right hand side
211239
// Force it to focus on the left hand side
212-
selection={{ start: 0, end: 0 }}
240+
selection={
241+
tokenType === TokenInputAreaType.Destination
242+
? { start: 0, end: 0 }
243+
: undefined
244+
}
213245
/>
214246
)}
215247
</Box>

0 commit comments

Comments
 (0)