Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions app/component-library/hooks/useStyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,31 @@ import { Theme } from '../../util/theme/models';
* Hook that handles both passing style sheet variables into style sheet and memoization.
*
* @param styleSheet Return value of useStyles hook.
* @param vars Variables of styleSheet function.
* @param vars Variables of styleSheet function (optional).
* @returns StyleSheet object.
*/
export const useStyles = <R, V>(
// Overload: when vars is provided
export function useStyles<R, V>(
styleSheet: (params: { theme: Theme; vars: V }) => R,
vars: V,
): { styles: R; theme: Theme } => {
): { styles: R; theme: Theme };

// Overload: when vars is not provided
export function useStyles<R>(styleSheet: (params: { theme: Theme }) => R): {
styles: R;
theme: Theme;
};

export function useStyles<R, V>(
styleSheet:
| ((params: { theme: Theme; vars: V }) => R)
| ((params: { theme: Theme }) => R),
vars?: V,
): { styles: R; theme: Theme } {
const theme = useAppThemeFromContext();
const styles = useMemo(
() => styleSheet({ theme, vars }),
() => styleSheet({ theme, vars: vars as V }),
[styleSheet, theme, vars],
);
return { styles, theme };
};
}
40 changes: 39 additions & 1 deletion app/components/Nav/App/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,14 @@ import * as NavigationNative from '@react-navigation/native';
import configureMockStore from 'redux-mock-store';
import { Provider } from 'react-redux';
import { mockTheme, ThemeContext } from '../../../util/theme';
import { Linking } from 'react-native';
import { Linking, View as MockView } from 'react-native';
import StorageWrapper from '../../../store/storage-wrapper';
import { Authentication } from '../../../core';
import { internalAccount1 as mockAccount } from '../../../util/test/accountsControllerTestUtils';
import { KeyringTypes } from '@metamask/keyring-controller';
import { AccountDetailsIds } from '../../../../e2e/selectors/MultichainAccounts/AccountDetails.selectors';
import { AvatarAccountType } from '../../../component-library/components/Avatars/Avatar';
import { useOTAUpdates } from '../../hooks/useOTAUpdates';

const initialState: DeepPartial<RootState> = {
user: {
Expand All @@ -40,6 +41,8 @@ const initialState: DeepPartial<RootState> = {
},
};

const MOCK_FOX_LOADER_ID = 'FOX_LOADER_ID';

jest.mock('react-native/Libraries/Linking/Linking', () => ({
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
Expand Down Expand Up @@ -74,6 +77,24 @@ jest.mock('../../hooks/useMetrics/useMetrics', () => ({
}),
}));

jest.mock('../../hooks/useOTAUpdates', () => ({
useOTAUpdates: jest.fn().mockReturnValue({
isCheckingUpdates: false,
}),
}));

const mockUseOTAUpdates = useOTAUpdates as jest.MockedFunction<
typeof useOTAUpdates
>;

jest.mock(
'../../UI/FoxLoader',
() =>
function MockFoxLoader() {
return <MockView testID={MOCK_FOX_LOADER_ID} />;
},
);

jest.mock('react-native-branch', () => ({
subscribe: jest.fn(),
getLatestReferringParams: jest.fn(),
Expand Down Expand Up @@ -238,6 +259,9 @@ describe('App', () => {

beforeEach(() => {
jest.clearAllMocks();
mockUseOTAUpdates.mockReturnValue({
isCheckingUpdates: false,
});
mockNavigate.mockClear();
});

Expand All @@ -250,6 +274,20 @@ describe('App', () => {
jest.useRealTimers();
});

it('renders FoxLoader when OTA update check runs', () => {
mockUseOTAUpdates.mockReturnValue({
isCheckingUpdates: true,
});

const { getByTestId } = renderScreen(
App,
{ name: 'App' },
{ state: initialState },
);

expect(getByTestId(MOCK_FOX_LOADER_ID)).toBeTruthy();
});

it('configures MetaMetrics instance and identifies user on startup', async () => {
renderScreen(App, { name: 'App' }, { state: initialState });
await waitFor(() => {
Expand Down
13 changes: 12 additions & 1 deletion app/components/Nav/App/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ import MultichainAccountActions from '../../Views/MultichainAccounts/sheets/Mult
import useInterval from '../../hooks/useInterval';
import { Duration } from '@metamask/utils';
import { selectSeedlessOnboardingLoginFlow } from '../../../selectors/seedlessOnboardingController';
import { useOTAUpdates } from '../../hooks/useOTAUpdates';
import { SmartAccountUpdateModal } from '../../Views/confirmations/components/smart-account-update-modal';
import { PayWithModal } from '../../Views/confirmations/components/modals/pay-with-modal/pay-with-modal';
import { useMetrics } from '../../hooks/useMetrics';
Expand Down Expand Up @@ -1072,7 +1073,7 @@ const AppFlow = () => {
);
};

const App: React.FC = () => {
const AppContent: React.FC = () => {
const navigation = useNavigation();
const routes = useNavigationState((state) => state.routes);
const { toastRef } = useContext(ToastContext);
Expand Down Expand Up @@ -1257,4 +1258,14 @@ const App: React.FC = () => {
);
};

const App: React.FC = () => {
const { isCheckingUpdates } = useOTAUpdates();

if (isCheckingUpdates) {
return <FoxLoader />;
}

return <AppContent />;
};

export default App;
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,7 @@ exports[`BridgeDestTokenSelector renders with initial state and displays tokens
"paddingHorizontal": 4,
}
}
handlerTag={1}
handlerTag={-1}
handlerType="NativeViewGestureHandler"
horizontal={true}
onGestureHandlerEvent={[Function]}
Expand Down Expand Up @@ -886,7 +886,13 @@ exports[`BridgeDestTokenSelector renders with initial state and displays tokens
</View>
<View>
<RCTScrollView
ListEmptyComponent={[Function]}
ListEmptyComponent={
{
"$$typeof": Symbol(react.memo),
"compare": null,
"type": [Function],
}
}
bounces={true}
collapsable={false}
data={
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,21 +49,31 @@ const createStyles = () =>
marginRight: 12,
},
});
export const BridgeDestTokenSelector: React.FC = () => {
export const BridgeDestTokenSelector: React.FC = React.memo(() => {
const dispatch = useDispatch();
const { styles } = useStyles(createStyles, {});
const { styles } = useStyles(createStyles);
const navigation = useNavigation();
const bridgeViewMode = useSelector(selectBridgeViewMode);

const networkConfigurations = useSelector(selectNetworkConfigurations);
const selectedDestToken = useSelector(selectDestToken);
const selectedDestChainId = useSelector(selectSelectedDestChainId);
const selectedSourceToken = useSelector(selectSourceToken);

const balanceChainIds = useMemo(
() => (selectedDestChainId ? [selectedDestChainId] : []),
[selectedDestChainId],
);
const tokensToExclude = useMemo(
() => (selectedSourceToken ? [selectedSourceToken] : []),
[selectedSourceToken],
);
const { allTokens, tokensToRender, pending } = useTokens({
topTokensChainId: selectedDestChainId,
balanceChainIds: selectedDestChainId ? [selectedDestChainId] : [],
tokensToExclude: selectedSourceToken ? [selectedSourceToken] : [],
balanceChainIds,
tokensToExclude,
});

const handleTokenPress = useCallback(
(token: BridgeToken) => {
dispatch(setDestToken(token));
Expand Down Expand Up @@ -155,14 +165,18 @@ export const BridgeDestTokenSelector: React.FC = () => {
],
);

const networksBar = useMemo(
() =>
bridgeViewMode === BridgeViewMode.Bridge ||
bridgeViewMode === BridgeViewMode.Unified ? (
<BridgeDestNetworksBar />
) : undefined,
[bridgeViewMode],
);

return (
<BridgeTokenSelectorBase
networksBar={
bridgeViewMode === BridgeViewMode.Bridge ||
bridgeViewMode === BridgeViewMode.Unified ? (
<BridgeDestNetworksBar />
) : undefined
}
networksBar={networksBar}
renderTokenItem={renderToken}
allTokens={allTokens}
tokensToRender={tokensToRender}
Expand All @@ -171,4 +185,6 @@ export const BridgeDestTokenSelector: React.FC = () => {
scrollResetKey={selectedDestChainId}
/>
);
};
});

BridgeDestTokenSelector.displayName = 'BridgeDestTokenSelector';
Original file line number Diff line number Diff line change
Expand Up @@ -891,7 +891,13 @@ exports[`BridgeSourceTokenSelector renders with initial state and displays token
</View>
<View>
<RCTScrollView
ListEmptyComponent={[Function]}
ListEmptyComponent={
{
"$$typeof": Symbol(react.memo),
"compare": null,
"type": [Function],
}
}
bounces={true}
collapsable={false}
data={
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import { BridgeToken, BridgeViewMode } from '../../types';
import { useSwitchNetworks } from '../../../../Views/NetworkSelector/useSwitchNetworks';
import { useNetworkInfo } from '../../../../../selectors/selectedNetworkController';

export const BridgeSourceTokenSelector: React.FC = () => {
export const BridgeSourceTokenSelector: React.FC = React.memo(() => {
const dispatch = useDispatch();
const navigation = useNavigation();
const bridgeViewMode = useSelector(selectBridgeViewMode);
Expand Down Expand Up @@ -79,10 +79,14 @@ export const BridgeSourceTokenSelector: React.FC = () => {
: undefined;
}

const tokenToExclude = useMemo(
() => (selectedDestToken ? [selectedDestToken] : []),
[selectedDestToken],
);
const { allTokens, tokensToRender, pending } = useTokens({
topTokensChainId: selectedSourceToken?.chainId,
balanceChainIds,
tokensToExclude: selectedDestToken ? [selectedDestToken] : [],
tokensToExclude: tokenToExclude,
});

const handleTokenPress = useCallback(
Expand Down Expand Up @@ -167,23 +171,35 @@ export const BridgeSourceTokenSelector: React.FC = () => {
[selectedSourceChainIds, sortedSourceNetworks],
);

const networksBar = useMemo(
() =>
isBridgeOrUnified ? (
<BridgeSourceNetworksBar
networksToShow={networksToShow}
networkConfigurations={allNetworkConfigurations}
selectedSourceChainIds={selectedSourceChainIds as Hex[]}
enabledSourceChains={enabledSourceChains}
/>
) : undefined,
[
isBridgeOrUnified,
networksToShow,
allNetworkConfigurations,
selectedSourceChainIds,
enabledSourceChains,
],
);

return (
<BridgeTokenSelectorBase
networksBar={
isBridgeOrUnified ? (
<BridgeSourceNetworksBar
networksToShow={networksToShow}
networkConfigurations={allNetworkConfigurations}
selectedSourceChainIds={selectedSourceChainIds as Hex[]}
enabledSourceChains={enabledSourceChains}
/>
) : undefined
}
networksBar={networksBar}
renderTokenItem={renderItem}
allTokens={allTokens}
tokensToRender={tokensToRender}
pending={pending}
chainIdToFetchMetadata={selectedChainId}
/>
);
};
});

BridgeSourceTokenSelector.displayName = 'BridgeSourceTokenSelector';
Loading
Loading