Skip to content

Commit e38066b

Browse files
committed
Merge branch 'main' into mikesposito/fix/duplicate-accounts
2 parents 5a9b848 + 0989e40 commit e38066b

File tree

62 files changed

+2627
-1427
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+2627
-1427
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
name: "Check PR Max Lines"
2+
3+
on:
4+
pull_request:
5+
types: [opened, reopened, synchronize]
6+
7+
jobs:
8+
check-pr-max-lines:
9+
uses: metamask/github-tools/.github/workflows/pr-line-check.yml@main
10+
with:
11+
max_lines: 1000
12+
ignore_patterns: '(\.lock$)'

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212

1313
### Added
1414

15+
- feat(bridge): improve bridge screen layout and user experience ([#15425](https://github.com/MetaMask/metamask-mobile/pull/15425))
16+
- fix(browser): fix browser PermissionsSummary origin spoofing when browser redirects issue ([#13394](https://github.com/MetaMask/metamask-mobile/pull/13394))
1517
- feat: migrate eth_accounts and permittedChains to CAIP-25 endowment ([#13970](https://github.com/MetaMask/metamask-mobile/pull/13970))
1618
- feat(identity): rebrand "Profile syncing" to "Backup and sync", adding a dedicated settings menu and more ([#15003](https://github.com/MetaMask/metamask-mobile/pull/15003))
1719
- feat(bridge): implement quote expiration handling in Bridge feature ([#14872](https://github.com/MetaMask/metamask-mobile/pull/14872))

app/components/Nav/App/App.tsx

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -129,10 +129,8 @@ import {
129129
import getUIStartupSpan from '../../../core/Performance/UIStartup';
130130
import { selectUserLoggedIn } from '../../../reducers/user/selectors';
131131
import { Confirm } from '../../Views/confirmations/components/confirm';
132-
///: BEGIN:ONLY_INCLUDE_IF(multi-srp)
133132
import ImportNewSecretRecoveryPhrase from '../../Views/ImportNewSecretRecoveryPhrase';
134133
import { SelectSRPBottomSheet } from '../../Views/SelectSRP/SelectSRPBottomSheet';
135-
///: END:ONLY_INCLUDE_IF
136134
import NavigationService from '../../../core/NavigationService';
137135
import ConfirmTurnOnBackupAndSyncModal from '../../UI/Identity/ConfirmTurnOnBackupAndSyncModal/ConfirmTurnOnBackupAndSyncModal';
138136
import AddNewAccount from '../../Views/AddNewAccount';
@@ -305,17 +303,13 @@ const DetectedTokensFlow = () => (
305303
</Stack.Navigator>
306304
);
307305

308-
///: BEGIN:ONLY_INCLUDE_IF(multi-srp)
309306
interface RootModalFlowProps {
310307
route: {
311308
params: Record<string, unknown>;
312309
};
313310
}
314-
///: END:ONLY_INCLUDE_IF(multi-srp)
315311
const RootModalFlow = (
316-
///: BEGIN:ONLY_INCLUDE_IF(multi-srp)
317312
props: RootModalFlowProps,
318-
///: END:ONLY_INCLUDE_IF
319313
) => (
320314
<Stack.Navigator mode={'modal'} screenOptions={clearStackNavigatorOptions}>
321315
<Stack.Screen
@@ -430,19 +424,15 @@ const RootModalFlow = (
430424
component={EnableAutomaticSecurityChecksModal}
431425
/>
432426
{
433-
///: BEGIN:ONLY_INCLUDE_IF(multi-srp)
434427
<Stack.Screen
435428
name={Routes.SHEET.SELECT_SRP}
436429
component={SelectSRPBottomSheet}
437430
/>
438-
///: END:ONLY_INCLUDE_IF
439431
}
440432
<Stack.Screen
441433
name={Routes.MODAL.SRP_REVEAL_QUIZ}
442434
component={SRPQuiz}
443-
///: BEGIN:ONLY_INCLUDE_IF(multi-srp)
444435
initialParams={{ ...props.route.params }}
445-
///: END:ONLY_INCLUDE_IF
446436
/>
447437
<Stack.Screen
448438
name={Routes.SHEET.ACCOUNT_ACTIONS}
@@ -501,7 +491,6 @@ const ImportPrivateKeyView = () => (
501491
</Stack.Navigator>
502492
);
503493

504-
///: BEGIN:ONLY_INCLUDE_IF(multi-srp)
505494
const ImportSRPView = () => (
506495
<Stack.Navigator
507496
screenOptions={{
@@ -514,7 +503,6 @@ const ImportSRPView = () => (
514503
/>
515504
</Stack.Navigator>
516505
);
517-
///: END:ONLY_INCLUDE_IF
518506

519507
const ConnectQRHardwareFlow = () => (
520508
<Stack.Navigator
@@ -641,13 +629,11 @@ const AppFlow = () => {
641629
options={{ animationEnabled: true }}
642630
/>
643631
{
644-
///: BEGIN:ONLY_INCLUDE_IF(multi-srp)
645632
<Stack.Screen
646633
name="ImportSRPView"
647634
component={ImportSRPView}
648635
options={{ animationEnabled: true }}
649636
/>
650-
///: END:ONLY_INCLUDE_IF
651637
}
652638
<Stack.Screen
653639
name="ConnectQRHardwareFlow"

app/components/UI/AssetElement/__snapshots__/index.test.tsx.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
exports[`AssetElement should render correctly 1`] = `
44
<TouchableOpacity
5+
disabled={false}
56
onLongPress={[Function]}
67
onPress={[Function]}
78
style={

app/components/UI/AssetElement/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ interface AssetElementProps {
2727
balance?: string;
2828
secondaryBalance?: string;
2929
privacyMode?: boolean;
30+
disabled?: boolean;
3031
}
3132

3233
const createStyles = (colors: Colors) =>
@@ -67,6 +68,7 @@ const AssetElement: React.FC<AssetElementProps> = ({
6768
onPress,
6869
onLongPress,
6970
privacyMode = false,
71+
disabled = false,
7072
}) => {
7173
const { colors } = useTheme();
7274
const styles = createStyles(colors);
@@ -83,6 +85,7 @@ const AssetElement: React.FC<AssetElementProps> = ({
8385
// when privacyMode is true, we should hide the balance and the fiat
8486
return (
8587
<TouchableOpacity
88+
disabled={disabled}
8689
onPress={handleOnPress}
8790
onLongPress={handleOnLongPress}
8891
style={styles.itemWrapper}

app/components/UI/AssetOverview/Balance/Balance.tsx

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import React, { useCallback } from 'react';
1+
import React, { useCallback, useMemo } from 'react';
22
import { View } from 'react-native';
3-
import { Hex, isCaipChainId } from '@metamask/utils';
3+
import { CaipAssetId, Hex, isCaipChainId } from '@metamask/utils';
44
import { strings } from '../../../../../locales/i18n';
55
import { useStyles } from '../../../../component-library/hooks';
66
import styleSheet from './Balance.styles';
@@ -108,23 +108,32 @@ const Balance = ({ asset, mainBalance, secondaryBalance }: BalanceProps) => {
108108
);
109109
}, [asset, styles.ethLogo]);
110110

111+
const isDisabled = useMemo(
112+
() => asset.isNative || isCaipChainId(asset.chainId as CaipAssetId),
113+
[asset.chainId, asset.isNative],
114+
);
115+
116+
const handlePress = useCallback(
117+
() =>
118+
!asset.isNative &&
119+
navigation.navigate('AssetDetails', {
120+
chainId: asset.chainId,
121+
address: asset.address,
122+
}),
123+
[asset.address, asset.chainId, asset.isNative, navigation],
124+
);
125+
111126
return (
112127
<View style={styles.wrapper}>
113128
<Text variant={TextVariant.HeadingMD} style={styles.title}>
114129
{strings('asset_overview.your_balance')}
115130
</Text>
116131
<AssetElement
132+
disabled={isDisabled}
117133
asset={asset}
118134
balance={mainBalance}
119135
secondaryBalance={secondaryBalance}
120-
onPress={() =>
121-
!asset.isETH &&
122-
!asset.isNative &&
123-
navigation.navigate('AssetDetails', {
124-
chainId: asset.chainId,
125-
address: asset.address,
126-
})
127-
}
136+
onPress={handlePress}
128137
>
129138
<BadgeWrapper
130139
style={styles.badgeWrapper}

app/components/UI/AssetOverview/Balance/__snapshots__/index.test.tsx.snap

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ exports[`Balance should render correctly with main and secondary balance 1`] = `
2626
Your balance
2727
</Text>
2828
<TouchableOpacity
29+
disabled={false}
2930
onLongPress={[Function]}
3031
onPress={[Function]}
3132
style={
@@ -251,6 +252,7 @@ exports[`Balance should render correctly without a secondary balance 1`] = `
251252
Your balance
252253
</Text>
253254
<TouchableOpacity
255+
disabled={false}
254256
onLongPress={[Function]}
255257
onPress={[Function]}
256258
style={

app/components/UI/AssetOverview/__snapshots__/AssetOverview.test.tsx.snap

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -942,6 +942,7 @@ exports[`AssetOverview should render correctly 1`] = `
942942
Your balance
943943
</Text>
944944
<TouchableOpacity
945+
disabled={false}
945946
onLongPress={[Function]}
946947
onPress={[Function]}
947948
style={
@@ -2102,6 +2103,7 @@ exports[`AssetOverview should render native balances even if there are no accoun
21022103
Your balance
21032104
</Text>
21042105
<TouchableOpacity
2106+
disabled={true}
21052107
onLongPress={[Function]}
21062108
onPress={[Function]}
21072109
style={
@@ -3162,6 +3164,7 @@ exports[`AssetOverview should render native balances when non evm network is sel
31623164
Your balance
31633165
</Text>
31643166
<TouchableOpacity
3167+
disabled={true}
31653168
onLongPress={[Function]}
31663169
onPress={[Function]}
31673170
style={

app/components/UI/Bridge/Views/BridgeView/BridgeView.styles.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,27 +59,28 @@ export const createStyles = (params: { theme: Theme }) => {
5959
keypadContainer: {
6060
flex: 1,
6161
justifyContent: 'flex-end',
62-
paddingBottom: 36,
63-
paddingTop: 8,
62+
paddingBottom: 8,
6463
},
6564
keypad: {
6665
paddingHorizontal: 4,
6766
},
6867
destinationAccountSelectorContainer: {
6968
paddingBottom: 12,
7069
},
71-
mainContent: {
72-
flex: 1,
73-
},
7470
dynamicContent: {
7571
flex: 1,
7672
paddingBottom: 12,
7773
justifyContent: 'flex-start',
7874
},
7975
keypadContainerWithDestinationPicker: {
80-
justifyContent: 'center',
81-
paddingTop: 8,
82-
paddingBottom: 12,
76+
justifyContent: 'flex-end',
77+
paddingBottom: 8,
78+
},
79+
scrollView: {
80+
flex: 1,
81+
},
82+
scrollViewContent: {
83+
flexGrow: 1,
8384
},
8485
});
8586
};

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

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,4 +631,100 @@ describe('BridgeView', () => {
631631
expect(toJSON()).toMatchSnapshot();
632632
});
633633
});
634+
635+
describe('Error Banner Visibility', () => {
636+
it('should hide error banner when input is focused', async () => {
637+
// Setup state with error condition
638+
const testState = createBridgeTestState({
639+
bridgeControllerOverrides: {
640+
quotesLoadingStatus: RequestStatus.FETCHED,
641+
quotes: [],
642+
quotesLastFetched: 12,
643+
},
644+
bridgeReducerOverrides: {
645+
sourceAmount: '1.0',
646+
},
647+
});
648+
649+
// Mock quote data to show an error
650+
jest
651+
.mocked(useBridgeQuoteData as unknown as jest.Mock)
652+
.mockImplementation(() => ({
653+
...mockUseBridgeQuoteData,
654+
quoteFetchError: 'Error fetching quote',
655+
isNoQuotesAvailable: true,
656+
isLoading: false,
657+
}));
658+
659+
const { getByTestId, queryByTestId } = renderScreen(
660+
BridgeView,
661+
{
662+
name: Routes.BRIDGE.ROOT,
663+
},
664+
{ state: testState },
665+
);
666+
667+
// Error banner should be visible initially
668+
await waitFor(() => {
669+
expect(queryByTestId('banneralert')).toBeTruthy();
670+
});
671+
672+
// Focus the input
673+
const input = getByTestId('source-token-area-input');
674+
fireEvent(input, 'focus');
675+
676+
// Error banner should be hidden
677+
await waitFor(() => {
678+
expect(queryByTestId('banneralert')).toBeNull();
679+
});
680+
});
681+
682+
it('should focus input and show keypad when error banner is closed', async () => {
683+
// Setup state with error condition
684+
const testState = createBridgeTestState({
685+
bridgeControllerOverrides: {
686+
quotesLoadingStatus: RequestStatus.FETCHED,
687+
quotes: [],
688+
quotesLastFetched: 12,
689+
},
690+
bridgeReducerOverrides: {
691+
sourceAmount: '1.0',
692+
},
693+
});
694+
695+
// Mock quote data to show an error
696+
jest
697+
.mocked(useBridgeQuoteData as unknown as jest.Mock)
698+
.mockImplementation(() => ({
699+
...mockUseBridgeQuoteData,
700+
quoteFetchError: 'Error fetching quote',
701+
isNoQuotesAvailable: true,
702+
isLoading: false,
703+
}));
704+
705+
const { getByTestId, queryByTestId } = renderScreen(
706+
BridgeView,
707+
{
708+
name: Routes.BRIDGE.ROOT,
709+
},
710+
{ state: testState },
711+
);
712+
713+
// Error banner should be visible initially
714+
await waitFor(() => {
715+
expect(queryByTestId('banneralert')).toBeTruthy();
716+
});
717+
718+
// Close the banner by clicking close button
719+
const closeButton = getByTestId('banner-close-button-icon');
720+
fireEvent.press(closeButton);
721+
722+
// Error banner should be hidden and keypad should be visible
723+
await waitFor(() => {
724+
expect(queryByTestId('banneralert')).toBeNull();
725+
// Keypad should be visible - check for the delete button which is part of the keypad
726+
expect(queryByTestId('keypad-delete-button')).toBeTruthy();
727+
});
728+
});
729+
});
634730
});

0 commit comments

Comments
 (0)