-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Token Network Filter UI [Mobile] (#11808)
## **Description** This PR introduces token network filter UI component. It lives behind a feature flag `PORTFOLIO_VIEW` in order to allow this to get merged while backend changes are in flight. Integration with the multichain asset list will occur in a separate PR. We'll likely want to add additional e2e tests when that happens. ### Running this branch `yarn && yarn setup` `PORTFOLIO_VIEW=1 yarn watch` `yarn start:ios` or `i` from the watch process Please ensure that this PR looks okay both with and without the feature flag running. I have introduced a horizontal scroll view because there simply was not enough screen real estate to make the network filter look okay with truncated text. ## **Related issues** Fixes: ## **Manual testing steps** 1. Run app with feature flag 2. Go to main Portfolio view 3. Pressing the network filter should trigger the bottom sheet, allowing the user to select different filter options. ## **Screenshots/Recordings** https://github.com/user-attachments/assets/636d1627-5da3-4826-83a1-924ff4f28603 ## **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. --------- Signed-off-by: Kai Huang <kai.huang@consensys.net> Co-authored-by: salimtb <salim.toubal@outlook.com> Co-authored-by: Vince Howard <vincenguyenhoward@gmail.com> Co-authored-by: sethkfman <10342624+sethkfman@users.noreply.github.com> Co-authored-by: runway-github[bot] <73448015+runway-github[bot]@users.noreply.github.com> Co-authored-by: MetaMask Bot <37885440+metamaskbot@users.noreply.github.com> Co-authored-by: metamaskbot <metamaskbot@users.noreply.github.com> Co-authored-by: tommasini <46944231+tommasini@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Xiaoming Wang <7315988+dawnseeker8@users.noreply.github.com> Co-authored-by: Nico MASSART <NicolasMassart@users.noreply.github.com> Co-authored-by: Xiaoming Wang <dawnseeker8@gmail.com> Co-authored-by: Nicholas Ellul <nicholas.ellul1@gmail.com> Co-authored-by: legobt <6wbvkn0j@anonaddy.me> Co-authored-by: OGPoyraz <omergoktugpoyraz@gmail.com> Co-authored-by: Pedro Pablo Aste Kompen <wachunei@gmail.com> Co-authored-by: legobeat <109787230+legobeat@users.noreply.github.com> Co-authored-by: tommasini <tommasini15@gmail.com> Co-authored-by: Bryan Fullam <bryanfullam@gmail.com> Co-authored-by: Mpendulo Ndlovu <mpendulo@elefantel.com> Co-authored-by: Kaihuang72490 <147628638+Kaihuang72490@users.noreply.github.com> Co-authored-by: Cal-L <cleun007@gmail.com> Co-authored-by: Cal Leung <cal.leung@consensys.net> Co-authored-by: sahar-fehri <sahar.fehri@consensys.net> Co-authored-by: Matthew Walsh <matthew.walsh@consensys.net>
- Loading branch information
1 parent
7a49a4c
commit de05544
Showing
17 changed files
with
390 additions
and
43 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
113 changes: 113 additions & 0 deletions
113
app/components/UI/Tokens/TokensBottomSheet/TokenFilterBottomSheet.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import React from 'react'; | ||
import { render, fireEvent, waitFor } from '@testing-library/react-native'; | ||
import { TokenFilterBottomSheet } from './TokenFilterBottomSheet'; | ||
import { useSelector } from 'react-redux'; | ||
import Engine from '../../../../core/Engine'; | ||
import { selectChainId } from '../../../../selectors/networkController'; | ||
import { selectTokenNetworkFilter } from '../../../../selectors/preferencesController'; | ||
|
||
jest.mock('react-redux', () => ({ | ||
useSelector: jest.fn(), | ||
})); | ||
|
||
jest.mock('../../../../util/theme', () => ({ | ||
useTheme: jest.fn(() => ({ colors: {} })), | ||
})); | ||
|
||
jest.mock('../../../../core/Engine', () => ({ | ||
context: { | ||
PreferencesController: { | ||
setTokenNetworkFilter: 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('TokenFilterBottomSheet', () => { | ||
beforeEach(() => { | ||
(useSelector as jest.Mock).mockImplementation((selector) => { | ||
if (selector === selectChainId) { | ||
return '0x1'; // default chain ID | ||
} else if (selector === selectTokenNetworkFilter) { | ||
return {}; // default to show all networks | ||
} | ||
return null; | ||
}); | ||
}); | ||
|
||
afterEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
it('renders correctly with the default option (All Networks) selected', () => { | ||
const { queryByText } = render(<TokenFilterBottomSheet />); | ||
|
||
expect(queryByText('All Networks')).toBeTruthy(); | ||
expect(queryByText('Current Network')).toBeTruthy(); | ||
}); | ||
|
||
it('sets filter to All Networks and closes bottom sheet when first option is pressed', async () => { | ||
const { queryByText } = render(<TokenFilterBottomSheet />); | ||
|
||
fireEvent.press(queryByText('All Networks')); | ||
|
||
await waitFor(() => { | ||
expect( | ||
Engine.context.PreferencesController.setTokenNetworkFilter, | ||
).toHaveBeenCalledWith({}); | ||
}); | ||
}); | ||
|
||
it('sets filter to Current Network and closes bottom sheet when second option is pressed', async () => { | ||
const { queryByText } = render(<TokenFilterBottomSheet />); | ||
|
||
fireEvent.press(queryByText('Current Network')); | ||
|
||
await waitFor(() => { | ||
expect( | ||
Engine.context.PreferencesController.setTokenNetworkFilter, | ||
).toHaveBeenCalledWith({ | ||
'0x1': true, | ||
}); | ||
}); | ||
}); | ||
|
||
it('displays the correct selection based on tokenNetworkFilter', () => { | ||
(useSelector as jest.Mock).mockImplementation((selector) => { | ||
if (selector === selectChainId) { | ||
return '0x1'; | ||
} else if (selector === selectTokenNetworkFilter) { | ||
return { '0x1': true }; // filter by current network | ||
} | ||
return null; | ||
}); | ||
|
||
const { queryByText } = render(<TokenFilterBottomSheet />); | ||
|
||
expect(queryByText('Current Network')).toBeTruthy(); | ||
}); | ||
}); |
87 changes: 87 additions & 0 deletions
87
app/components/UI/Tokens/TokensBottomSheet/TokenFilterBottomSheet.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import React, { useRef } from 'react'; | ||
import { useSelector } from 'react-redux'; | ||
import { selectChainId } from '../../../../selectors/networkController'; | ||
import { selectTokenNetworkFilter } from '../../../../selectors/preferencesController'; | ||
import BottomSheet, { | ||
BottomSheetRef, | ||
} from '../../../../component-library/components/BottomSheets/BottomSheet'; | ||
import { useTheme } from '../../../../util/theme'; | ||
import createStyles from '../styles'; | ||
import Engine from '../../../../core/Engine'; | ||
import { View } from 'react-native'; | ||
import Text, { | ||
TextVariant, | ||
} from '../../../../component-library/components/Texts/Text'; | ||
import ListItemSelect from '../../../../component-library/components/List/ListItemSelect'; | ||
import { VerticalAlignment } from '../../../../component-library/components/List/ListItem'; | ||
import { strings } from '../../../../../locales/i18n'; | ||
|
||
enum FilterOption { | ||
AllNetworks = 0, | ||
CurrentNetwork = 1, | ||
} | ||
|
||
const TokenFilterBottomSheet = () => { | ||
const sheetRef = useRef<BottomSheetRef>(null); | ||
const { colors } = useTheme(); | ||
const styles = createStyles(colors); | ||
|
||
const chainId = useSelector(selectChainId); | ||
const tokenNetworkFilter = useSelector(selectTokenNetworkFilter); | ||
|
||
const onFilterControlsBottomSheetPress = (option: FilterOption) => { | ||
const { PreferencesController } = Engine.context; | ||
switch (option) { | ||
case FilterOption.AllNetworks: | ||
PreferencesController.setTokenNetworkFilter({}); | ||
sheetRef.current?.onCloseBottomSheet(); | ||
break; | ||
case FilterOption.CurrentNetwork: | ||
PreferencesController.setTokenNetworkFilter({ | ||
[chainId]: true, | ||
}); | ||
sheetRef.current?.onCloseBottomSheet(); | ||
break; | ||
default: | ||
break; | ||
} | ||
}; | ||
|
||
const isSelectedNetwork = Boolean(tokenNetworkFilter?.[chainId]); | ||
|
||
return ( | ||
<BottomSheet shouldNavigateBack ref={sheetRef}> | ||
<View style={styles.bottomSheetWrapper}> | ||
<Text variant={TextVariant.HeadingMD} style={styles.bottomSheetTitle}> | ||
{strings('wallet.filter_by')} | ||
</Text> | ||
<ListItemSelect | ||
onPress={() => | ||
onFilterControlsBottomSheetPress(FilterOption.AllNetworks) | ||
} | ||
isSelected={!isSelectedNetwork} | ||
gap={8} | ||
verticalAlignment={VerticalAlignment.Center} | ||
> | ||
<Text style={styles.bottomSheetText}> | ||
{strings('wallet.all_networks')} | ||
</Text> | ||
</ListItemSelect> | ||
<ListItemSelect | ||
onPress={() => | ||
onFilterControlsBottomSheetPress(FilterOption.CurrentNetwork) | ||
} | ||
isSelected={isSelectedNetwork} | ||
gap={8} | ||
verticalAlignment={VerticalAlignment.Center} | ||
> | ||
<Text style={styles.bottomSheetText}> | ||
{strings('wallet.current_network')} | ||
</Text> | ||
</ListItemSelect> | ||
</View> | ||
</BottomSheet> | ||
); | ||
}; | ||
|
||
export { TokenFilterBottomSheet }; |
2 changes: 1 addition & 1 deletion
2
app/components/UI/Tokens/TokensBottomSheet/TokenSortBottomSheet.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.