Skip to content

Commit

Permalink
chore: Convert token sort ActionSheet to BottomSheet (#11853)
Browse files Browse the repository at this point in the history
## **Description**

Converts the `ActionSheet` that controls [token sorting in this
PR](#11618) to a
`BottomSheet` per design spec.

Design spec:
https://www.figma.com/design/aMYisczaJyEsYl1TYdcPUL/Portfolio-View?node-id=5008-57060&node-type=frame&t=DkOQgh3ZMZfzy6C4-0

## **Related issues**

Fixes: https://consensyssoftware.atlassian.net/browse/MMASSETS-432

## **Manual testing steps**

Functionality should remain the same as
#11618 however rather
than `ActionSheet` controls, we should have `BottomSheet` controls

## **Screenshots/Recordings**

### **Before**


https://github.com/user-attachments/assets/00b1b471-996b-4a12-acfd-3f7c486bdbc8


https://github.com/user-attachments/assets/6160e434-dbbd-4b73-9edf-a971eea14448

### **After**

[<!-- [screenshots/recordings]
-->](https://github.com/user-attachments/assets/752941e1-1ba6-4e23-ad26-ded482fb6c12)


https://github.com/user-attachments/assets/bff36815-9ae4-4c02-a6a0-2e427365cddb

## **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.

---------

Co-authored-by: salimtb <salim.toubal@outlook.com>
  • Loading branch information
gambinish and salimtb authored Oct 28, 2024
1 parent d1a58ab commit 0aae9b4
Show file tree
Hide file tree
Showing 11 changed files with 346 additions and 38 deletions.
5 changes: 5 additions & 0 deletions app/components/Nav/App/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import Toast, {
ToastContext,
} from '../../../component-library/components/Toast';
import AccountSelector from '../../../components/Views/AccountSelector';
import TokenSortBottomSheet from '../../../components/UI/Tokens/TokensBottomSheet/TokenSortBottomSheet.tsx';
import AccountConnect from '../../../components/Views/AccountConnect';
import AccountPermissions from '../../../components/Views/AccountPermissions';
import { AccountPermissionsScreens } from '../../../components/Views/AccountPermissions/AccountPermissions.types';
Expand Down Expand Up @@ -426,6 +427,10 @@ const RootModalFlow = () => (
name={Routes.SHEET.NETWORK_SELECTOR}
component={NetworkSelector}
/>
<Stack.Screen
name={Routes.SHEET.TOKEN_SORT}
component={TokenSortBottomSheet}
/>
<Stack.Screen
name={Routes.SHEET.BASIC_FUNCTIONALITY}
component={BasicFunctionalityModal}
Expand Down
2 changes: 2 additions & 0 deletions app/components/UI/Tokens/TokenList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { TokenI } from '../types';
import { strings } from '../../../../../locales/i18n';
import { TokenListFooter } from './TokenListFooter';
import { TokenListItem } from './TokenListItem';
import { WalletViewSelectorsIDs } from '../../../../../e2e/selectors/wallet/WalletView.selectors';

interface TokenListProps {
tokens: TokenI[];
Expand Down Expand Up @@ -71,6 +72,7 @@ export const TokenList = ({

return tokens?.length ? (
<FlatList
testID={WalletViewSelectorsIDs.TOKENS_CONTAINER_LIST}
data={tokens}
renderItem={({ item }) => (
<TokenListItem
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react-native';
import TokenSortBottomSheet from './TokenSortBottomSheet';
import { useSelector } from 'react-redux';
import Engine from '../../../../core/Engine';
import { selectTokenSortConfig } from '../../../../selectors/preferencesController';
import { selectCurrentCurrency } from '../../../../selectors/currencyRateController';
import { WalletViewSelectorsIDs } from '../../../../../e2e/selectors/wallet/WalletView.selectors';

jest.mock('react-redux', () => ({
useSelector: jest.fn(),
}));

jest.mock('../../../../util/theme', () => ({
useTheme: jest.fn(),
}));

jest.mock('../../../../core/Engine', () => ({
context: {
PreferencesController: {
setTokenSortConfig: jest.fn(),
},
},
}));

jest.mock('@react-navigation/native', () => {
const reactNavigationModule = jest.requireActual('@react-navigation/native');
return {
...reactNavigationModule,
useNavigation: () => ({
navigate: jest.fn(),
goBack: jest.fn(),
}),
};
});

jest.mock('react-native-safe-area-context', () => {
// copied from BottomSheetDialog.test.tsx
const inset = { top: 1, right: 2, bottom: 3, left: 4 };
const frame = { width: 5, height: 6, x: 7, y: 8 };
return {
SafeAreaProvider: jest.fn().mockImplementation(({ children }) => children),
SafeAreaConsumer: jest
.fn()
.mockImplementation(({ children }) => children(inset)),
useSafeAreaInsets: jest.fn().mockImplementation(() => inset),
useSafeAreaFrame: jest.fn().mockImplementation(() => frame),
};
});

describe('TokenSortBottomSheet', () => {
beforeEach(() => {
(useSelector as jest.Mock).mockImplementation((selector) => {
if (selector === selectTokenSortConfig) {
return {
key: 'tokenFiatAmount',
order: 'dsc',
sortCallback: 'stringNumeric',
}; // Default token sort config
} else if (selector === selectCurrentCurrency) {
return 'USD';
}
return null;
});
});

afterEach(() => {
jest.clearAllMocks();
});

it('renders correctly with the default sort option selected', () => {
const { queryByTestId } = render(<TokenSortBottomSheet />);

expect(queryByTestId(WalletViewSelectorsIDs.SORT_BY)).toBeTruthy();
expect(
queryByTestId(WalletViewSelectorsIDs.SORT_DECLINING_BALANCE),
).toBeTruthy();
expect(
queryByTestId(WalletViewSelectorsIDs.SORT_ALPHABETICAL),
).toBeTruthy();
});

it('triggers PreferencesController to sort by token fiat amount when first cell is pressed', async () => {
const { queryByTestId } = render(<TokenSortBottomSheet />);

fireEvent.press(
queryByTestId(WalletViewSelectorsIDs.SORT_DECLINING_BALANCE),
);

await waitFor(() => {
expect(
Engine.context.PreferencesController.setTokenSortConfig,
).toHaveBeenCalledWith({
key: 'tokenFiatAmount',
order: 'dsc',
sortCallback: 'stringNumeric',
});
});
});

it('triggers PreferencesController to sort alphabetically when the second cell is pressed', async () => {
const { queryByTestId } = render(<TokenSortBottomSheet />);

fireEvent.press(queryByTestId(WalletViewSelectorsIDs.SORT_ALPHABETICAL));

await waitFor(() => {
expect(
Engine.context.PreferencesController.setTokenSortConfig,
).toHaveBeenCalledWith({
key: 'symbol',
sortCallback: 'alphaNumeric',
order: 'asc',
});
});
});

it('displays the correct selection based on tokenSortConfig', () => {
(useSelector as jest.Mock).mockImplementation((selector) => {
if (selector === selectTokenSortConfig) {
return { key: 'symbol', order: 'dsc', sortCallback: 'stringNumeric' };
}
return null;
});

const { queryByTestId } = render(<TokenSortBottomSheet />);

expect(
queryByTestId(WalletViewSelectorsIDs.SORT_ALPHABETICAL),
).toBeTruthy();
});
});
104 changes: 104 additions & 0 deletions app/components/UI/Tokens/TokensBottomSheet/TokenSortBottomSheet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import React, { useRef } from 'react';
import { View } from 'react-native';
import { useSelector } from 'react-redux';
import { useTheme } from '../../../../util/theme';
import Engine from '../../../../core/Engine';
import createStyles from '../styles';
import { strings } from '../../../../../locales/i18n';
import { selectTokenSortConfig } from '../../../../selectors/preferencesController';
import { selectCurrentCurrency } from '../../../../selectors/currencyRateController';
import BottomSheet, {
BottomSheetRef,
} from '../../../../component-library/components/BottomSheets/BottomSheet';
import Text, {
TextVariant,
} from '../../../../component-library/components/Texts/Text';
import currencySymbols from '../../../../util/currency-symbols.json';
import { WalletViewSelectorsIDs } from '../../../../../e2e/selectors/wallet/WalletView.selectors';
import ListItemSelect from '../../../../component-library/components/List/ListItemSelect';
import { VerticalAlignment } from '../../../../component-library/components/List/ListItem';

enum SortOption {
FiatAmount = 0,
Alphabetical = 1,
}

const TokenSortBottomSheet = () => {
const sheetRef = useRef<BottomSheetRef>(null);
const { colors } = useTheme();
const styles = createStyles(colors);

const tokenSortConfig = useSelector(selectTokenSortConfig);
const currentCurrency = useSelector(selectCurrentCurrency);

const onSortControlsBottomSheetPress = (option: SortOption) => {
const { PreferencesController } = Engine.context;
switch (option) {
case SortOption.FiatAmount:
PreferencesController.setTokenSortConfig({
key: 'tokenFiatAmount',
order: 'dsc',
sortCallback: 'stringNumeric',
});
sheetRef.current?.onCloseBottomSheet();
break;
case SortOption.Alphabetical:
PreferencesController.setTokenSortConfig({
key: 'symbol',
sortCallback: 'alphaNumeric',
order: 'asc',
});
sheetRef.current?.onCloseBottomSheet();
break;
default:
break;
}
};

return (
<BottomSheet shouldNavigateBack ref={sheetRef}>
<View style={styles.bottomSheetWrapper}>
<Text
testID={WalletViewSelectorsIDs.SORT_BY}
variant={TextVariant.HeadingMD}
style={styles.bottomSheetTitle}
>
{strings('wallet.sort_by')}
</Text>
<ListItemSelect
testID={WalletViewSelectorsIDs.SORT_DECLINING_BALANCE}
onPress={() => onSortControlsBottomSheetPress(SortOption.FiatAmount)}
isSelected={tokenSortConfig.key === 'tokenFiatAmount'}
isDisabled={false}
gap={8}
verticalAlignment={VerticalAlignment.Center}
>
<Text style={styles.bottomSheetText}>
{strings('wallet.declining_balance', {
currency:
currencySymbols[
currentCurrency as keyof typeof currencySymbols
] ?? currentCurrency,
})}
</Text>
</ListItemSelect>
<ListItemSelect
testID={WalletViewSelectorsIDs.SORT_ALPHABETICAL}
onPress={() =>
onSortControlsBottomSheetPress(SortOption.Alphabetical)
}
isSelected={tokenSortConfig.key !== 'tokenFiatAmount'}
isDisabled={false}
gap={8}
verticalAlignment={VerticalAlignment.Center}
>
<Text style={styles.bottomSheetText}>
{strings('wallet.alphabetically')}
</Text>
</ListItemSelect>
</View>
</BottomSheet>
);
};

export default TokenSortBottomSheet;
8 changes: 8 additions & 0 deletions app/components/UI/Tokens/TokensBottomSheet/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Routes from '../../../../constants/navigation/Routes';
import { createNavigationDetails } from '../../../../util/navigation/navUtils';

export const createTokensBottomSheetNavDetails = createNavigationDetails(
Routes.MODAL.ROOT_MODAL_FLOW,
Routes.SHEET.TOKEN_SORT,
);
export { default } from './TokenSortBottomSheet';
3 changes: 3 additions & 0 deletions app/components/UI/Tokens/__snapshots__/index.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,7 @@ exports[`Tokens should hide zero balance tokens when setting is on 1`] = `
renderItem={[Function]}
scrollEventThrottle={50}
stickyHeaderIndices={[]}
testID="token-list"
viewabilityConfigCallbackPairs={[]}
>
<RCTRefreshControl />
Expand Down Expand Up @@ -1746,6 +1747,7 @@ exports[`Tokens should render correctly 1`] = `
renderItem={[Function]}
scrollEventThrottle={50}
stickyHeaderIndices={[]}
testID="token-list"
viewabilityConfigCallbackPairs={[]}
>
<RCTRefreshControl />
Expand Down Expand Up @@ -3005,6 +3007,7 @@ exports[`Tokens should show all balance tokens when hideZeroBalanceTokens settin
renderItem={[Function]}
scrollEventThrottle={50}
stickyHeaderIndices={[]}
testID="token-list"
viewabilityConfigCallbackPairs={[]}
>
<RCTRefreshControl />
Expand Down
Loading

0 comments on commit 0aae9b4

Please sign in to comment.