diff --git a/.cursor/rules/pr-creation-guidelines.mdc b/.cursor/rules/pr-creation-guidelines.mdc index 4398b24702b..3f3bee63469 100644 --- a/.cursor/rules/pr-creation-guidelines.mdc +++ b/.cursor/rules/pr-creation-guidelines.mdc @@ -1,3 +1,8 @@ +--- +description: Pull Request (PR) Creation Guidelines. Use these rules when creating pull requests +alwaysApply: false +--- + # MetaMask Mobile Pull Request (PR) Creation Guidelines These rules apply whenever creating PRs in the MetaMask Mobile repository or suggesting PR creation steps to others. Follow them to ensure consistent quality and smooth reviews. @@ -85,11 +90,44 @@ CHANGELOG entry: null - **Always assign the PR to yourself (the author)** immediately after creation. - This ensures proper ownership tracking and notifications. +### Automatic Team Label Detection + +When creating PRs, automatically detect and apply the correct team label based on: + +1. **Author's GitHub team membership** - Check which MetaMask teams the author belongs to +2. **Available team labels in the repository** - Match against existing `team-*` labels +3. **Context awareness** - Consider files changed when author belongs to multiple teams + +#### Generic Team Label Detection Process + +```bash +# Step 1: Get author's MetaMask teams +USER_TEAMS=$(gh api user/teams --paginate | jq -r '.[] | select(.organization.login == "MetaMask") | .slug') + +# Step 2: Get available team labels in the repository +REPO_TEAM_LABELS=$(gh label list --search "team-" --limit 100 | cut -f1) + +# Step 3: Try to find a matching team label +# Common mappings to check: +# - Exact match: team-{github-team} (e.g., mobile-platform → team-mobile-platform) +# - Without suffix: design-system-engineers → team-design-system +# - With wallet prefix: wallet-ux → team-wallet-ux + +# If no clear match is found, DO NOT add a team label +# It's better to have no team label than an incorrect one +``` + ### Required Labels Apply labels to enable automation and proper routing. Some labels can block merging. -- **Team labels** (for internal devs): `team-mobile-ux`, `team-mobile-platform`, etc. +- **Team labels** (OPTIONAL - only add if there's a clear match): + - Auto-detect based on author's GitHub team membership when possible + - Follow the pattern `team-*` + - Only add if you can confidently match the GitHub team to a label + - If unsure, leave it out - no team label is better than a wrong one + - Check for deprecated labels in the label description + - To see all available team labels: `gh label list --search "team-" --limit 100` - **QA labels**: - `needs-qa` (blocks merging until QA passes) - `No QA Needed` @@ -102,10 +140,15 @@ Apply labels to enable automation and proper routing. Some labels can block merg ### Label and Assignment DO / DON'T - ✅ DO: Assign the PR to yourself as the author +- ✅ DO: Try to auto-detect team label based on GitHub team membership +- ✅ DO: Leave team label empty if no clear match exists +- ✅ DO: Verify team labels aren't deprecated (check label descriptions) - ✅ DO: Add a QA label for every PR -- ✅ DO: Add the correct team label for routing - ✅ DO: Remove blocking labels when conditions are resolved - ❌ DON'T: Leave PR unassigned +- ❌ DON'T: Force a team label if there's no clear match - it's optional +- ❌ DON'T: Use deprecated team labels (check label descriptions for deprecation notices) +- ❌ DON'T: Guess team labels - only use ones you're confident about - ❌ DON'T: Merge while `needs-qa` or `DO-NOT-MERGE` is present ## 4. Branch Naming Conventions @@ -153,13 +196,35 @@ Use descriptive branch names prefixed by the commit type. ## Examples for MetaMask Mobile Context +### Example with Auto-Detected Team Label + - Title: `feat: add NFT gallery to collectibles tab` - Branch: `feat/add-nft-gallery` - Assignee: The PR author (yourself) -- Labels: `team-mobile-ux`, `needs-qa`, `Run E2E Smoke` +- Team label: `team-design-system` (IF clear match found) or none (if no match) +- Required Labels: `needs-qa` or `No QA Needed`, plus E2E label - Changelog: `Added NFT gallery to Collectibles tab.` - Manual testing (Gherkin): see example above +### GitHub CLI Command Example + +```bash +# Create PR with auto team detection +gh pr create \ + --title "feat: add NFT gallery to collectibles tab" \ + --assignee @me \ + --draft + +# Try to detect team label (optional) +USER_TEAMS=$(gh api user/teams --paginate | jq -r '.[] | select(.organization.login == "MetaMask") | .slug') + +# Only add team label if there's a confident match +# Examples of clear matches: +# - mobile-platform → team-mobile-platform ✓ +# - wallet-ux → team-wallet-ux ✓ +# If no clear match, skip the team label +``` + ## Enforcement - PRs must use `.github/pull-request-template.md` with all sections completed. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cd714dc822b..bf6dcff02b8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -124,6 +124,12 @@ jobs: else echo "No changes detected" fi + e2e-tests: + permissions: + contents: read + id-token: write + uses: ./.github/workflows/run-e2e.yml + secrets: inherit merge-unit-tests: runs-on: ubuntu-latest needs: unit-tests diff --git a/.github/workflows/run-e2e-workflow.yml b/.github/workflows/run-e2e-workflow.yml index aa300b8672d..556a67a6a97 100644 --- a/.github/workflows/run-e2e-workflow.yml +++ b/.github/workflows/run-e2e-workflow.yml @@ -77,6 +77,7 @@ jobs: setup-simulator: ${{ inputs.platform == 'ios' }} android-avd-name: emulator configure-keystores: false + sd-card-size: 8192 - name: Build Detox framework cache (iOS) if: ${{ inputs.platform == 'ios' }} @@ -181,7 +182,10 @@ jobs: if: failure() run: | echo "Removing APKs from artifacts..." - find artifacts/ -name "*app-*" -delete 2>/dev/null || true + # Remove APKs, but ignore coverage folders and ios-bundle + find artifacts/ \( -type d \( -name "coverage-*" -o -name "ios-bundle" \) -prune \) -o -type f -name "*app-*" -delete 2>/dev/null || true + # Also remove artifact directories that contain APK/AAB files (download-artifact creates directories) + find artifacts/ \( -type d \( -name "coverage-*" -o -name "ios-bundle" \) -prune \) -o -type d -name "*app-*" -exec rm -rf {} + 2>/dev/null || true continue-on-error: true - name: Upload screenshots diff --git a/.github/workflows/run-e2e.yml b/.github/workflows/run-e2e.yml index b30a3840fa1..0e760e8fbdd 100644 --- a/.github/workflows/run-e2e.yml +++ b/.github/workflows/run-e2e.yml @@ -6,11 +6,9 @@ name: Mobile E2E Smoke Tests on: workflow_call: workflow_dispatch: - pull_request: - types: [opened, synchronize] permissions: - contents: write + contents: read id-token: write jobs: @@ -198,6 +196,27 @@ jobs: test_suite_tag: ".*Performance.*" secrets: inherit + smoke-card-ios: + name: "Card Smoke (iOS)" + if: false + uses: ./.github/workflows/run-e2e-workflow.yml + with: + test-suite-name: smoke-card-ios + platform: ios + test_suite_tag: ".*SmokeCard.*" + secrets: inherit + + smoke-card-android: + name: "Card Smoke (Android)" + needs: build-android-apks + if: always() && needs.build-android-apks.result != 'failure' + uses: ./.github/workflows/run-e2e-workflow.yml + with: + test-suite-name: smoke-card-android + platform: android + test_suite_tag: ".*SmokeCard.*" + secrets: inherit + smoke-api-specs: name: "API Specs E2E" if: false @@ -207,7 +226,7 @@ jobs: report-e2e-smoke-tests: name: Report E2E Smoke Tests runs-on: ubuntu-latest - if: false + if: always() needs: - smoke-confirmations-ios - smoke-confirmations-android @@ -226,6 +245,8 @@ jobs: - smoke-network-expansion-android - smoke-performance-ios - smoke-performance-android + - smoke-card-ios + - smoke-card-android - smoke-api-specs steps: diff --git a/README.md b/README.md index 53de2524787..6f6b5788e51 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ To learn how to contribute to the MetaMask codebase, visit our [Contributor Docs - [Testing](./docs/readme/testing.md) - [Debugging](./docs/readme/debugging.md) - [Performance](./docs/readme/performance.md) +- [Release Build Profiling](./docs/readme/release-build-profiler.md) - [API Call Logging for Debugging](./docs/readme/api-logging.md) - [Storybook](./docs/readme/storybook.md) - [Miscellaneous](./docs/readme/miscellaneous.md) diff --git a/app/actions/wizard/index.js b/app/actions/wizard/index.js deleted file mode 100644 index 3cc5acb70e3..00000000000 --- a/app/actions/wizard/index.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Sets onboarding wizard step - */ -export default function setOnboardingWizardStep(step) { - return { - type: 'SET_ONBOARDING_WIZARD_STEP', - step, - }; -} diff --git a/app/component-library/components-temp/MultichainAccounts/AccountCell/AccountCell.test.tsx b/app/component-library/components-temp/MultichainAccounts/AccountCell/AccountCell.test.tsx index 95d90ea3818..1442c995289 100644 --- a/app/component-library/components-temp/MultichainAccounts/AccountCell/AccountCell.test.tsx +++ b/app/component-library/components-temp/MultichainAccounts/AccountCell/AccountCell.test.tsx @@ -1,9 +1,18 @@ import React from 'react'; import { AccountGroupObject } from '@metamask/account-tree-controller'; -import { AccountCell } from './AccountCell'; +import AccountCell from './AccountCell'; import renderWithProvider from '../../../../util/test/renderWithProvider'; +import { fireEvent } from '@testing-library/react-native'; import { createMockAccountGroup } from '../test-utils'; +// Mock navigation +const mockNavigate = jest.fn(); + +jest.mock('@react-navigation/native', () => ({ + ...jest.requireActual('@react-navigation/native'), + useNavigation: () => ({ navigate: mockNavigate }), +})); + const mockAccountGroup = createMockAccountGroup( 'keyring:test-group/ethereum', 'Test Account Group', @@ -56,4 +65,13 @@ describe('AccountCell', () => { expect(getByText('Test Account Group')).toBeTruthy(); expect(getByText('$1234567890.00')).toBeTruthy(); }); + + it('navigates to account actions when menu button is pressed', () => { + const { getByTestId } = renderAccountCell(); + const menuButton = getByTestId('multichain-account-cell-menu'); + fireEvent.press(menuButton); + expect(mockNavigate).toHaveBeenCalledWith('MultichainAccountActions', { + accountGroup: mockAccountGroup, + }); + }); }); diff --git a/app/component-library/components-temp/MultichainAccounts/AccountCell/AccountCell.tsx b/app/component-library/components-temp/MultichainAccounts/AccountCell/AccountCell.tsx index d6b9a0c06d4..bf2c24d5560 100644 --- a/app/component-library/components-temp/MultichainAccounts/AccountCell/AccountCell.tsx +++ b/app/component-library/components-temp/MultichainAccounts/AccountCell/AccountCell.tsx @@ -1,6 +1,7 @@ import { AccountGroupObject } from '@metamask/account-tree-controller'; -import React from 'react'; +import React, { useCallback } from 'react'; import { TouchableOpacity, View } from 'react-native'; +import { useNavigation } from '@react-navigation/native'; import { useStyles } from '../../../hooks'; import styleSheet from './AccountCell.styles'; import Text, { TextColor, TextVariant } from '../../../components/Texts/Text'; @@ -12,14 +13,22 @@ import { } from '../../../../components/UI/Box/box.types'; import Icon, { IconName, IconSize } from '../../../components/Icons/Icon'; import { AccountCellIds } from '../../../../../e2e/selectors/MultichainAccounts/AccountCell.selectors'; +import Routes from '../../../../constants/navigation/Routes'; interface AccountCellProps { accountGroup: AccountGroupObject; isSelected: boolean; } -export const AccountCell = ({ accountGroup, isSelected }: AccountCellProps) => { +const AccountCell = ({ accountGroup, isSelected }: AccountCellProps) => { const { styles } = useStyles(styleSheet, { isSelected }); + const { navigate } = useNavigation(); + + const handleMenuPress = useCallback(() => { + navigate(Routes.MULTICHAIN_ACCOUNTS.ACCOUNT_CELL_ACTIONS, { + accountGroup, + }); + }, [navigate, accountGroup]); return ( { { ); }; + +export default React.memo(AccountCell); diff --git a/app/component-library/components-temp/MultichainAccounts/AccountCell/index.ts b/app/component-library/components-temp/MultichainAccounts/AccountCell/index.ts index 97146ae05a7..7cfd34a2193 100644 --- a/app/component-library/components-temp/MultichainAccounts/AccountCell/index.ts +++ b/app/component-library/components-temp/MultichainAccounts/AccountCell/index.ts @@ -1 +1 @@ -export { AccountCell as default } from './AccountCell'; +export { default } from './AccountCell'; diff --git a/app/component-library/components-temp/MultichainAccounts/MultichainAccountSelectorList/AccountListCell/AccountListCell.test.tsx b/app/component-library/components-temp/MultichainAccounts/MultichainAccountSelectorList/AccountListCell/AccountListCell.test.tsx index 20c9b038586..759caf7927f 100644 --- a/app/component-library/components-temp/MultichainAccounts/MultichainAccountSelectorList/AccountListCell/AccountListCell.test.tsx +++ b/app/component-library/components-temp/MultichainAccounts/MultichainAccountSelectorList/AccountListCell/AccountListCell.test.tsx @@ -3,6 +3,13 @@ import { render, fireEvent } from '@testing-library/react-native'; import AccountListCell from './AccountListCell'; import { createMockAccountGroup } from '../../test-utils'; +const mockNavigate = jest.fn(); + +jest.mock('@react-navigation/native', () => ({ + ...jest.requireActual('@react-navigation/native'), + useNavigation: () => ({ navigate: mockNavigate }), +})); + const mockAccountGroup = createMockAccountGroup( 'keyring:test-group/ethereum', 'Test Account', diff --git a/app/component-library/components-temp/MultichainAccounts/MultichainAccountSelectorList/MultichainAccountSelectorList.test.tsx b/app/component-library/components-temp/MultichainAccounts/MultichainAccountSelectorList/MultichainAccountSelectorList.test.tsx index 7fe910435d0..21f81d1a373 100644 --- a/app/component-library/components-temp/MultichainAccounts/MultichainAccountSelectorList/MultichainAccountSelectorList.test.tsx +++ b/app/component-library/components-temp/MultichainAccounts/MultichainAccountSelectorList/MultichainAccountSelectorList.test.tsx @@ -19,6 +19,13 @@ import { createMockInternalAccountsWithAddresses, } from '../test-utils'; +const mockNavigate = jest.fn(); + +jest.mock('@react-navigation/native', () => ({ + ...jest.requireActual('@react-navigation/native'), + useNavigation: () => ({ navigate: mockNavigate }), +})); + describe('MultichainAccountSelectorList', () => { const mockOnSelectAccount = jest.fn(); diff --git a/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/MultichainAddressRow.constants.ts b/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/MultichainAddressRow.constants.ts index 40922b15afc..56c56c19e76 100644 --- a/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/MultichainAddressRow.constants.ts +++ b/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/MultichainAddressRow.constants.ts @@ -1,10 +1,5 @@ import { MultichainAddressRowProps } from './MultichainAddressRow.types'; - -export const SAMPLE_MULTICHAIN_ADDRESS_ROW_PROPS: MultichainAddressRowProps = { - chainId: 'eip155:1', - networkName: 'Ethereum Mainnet', - address: '0x1234567890123456789012345678901234567890', -}; +import { IconName } from '../../../components/Icons/Icon'; export const MULTICHAIN_ADDRESS_ROW_TEST_ID = 'multichain-address-row'; export const MULTICHAIN_ADDRESS_ROW_NETWORK_ICON_TEST_ID = @@ -17,3 +12,27 @@ export const MULTICHAIN_ADDRESS_ROW_COPY_BUTTON_TEST_ID = 'multichain-address-row-copy-button'; export const MULTICHAIN_ADDRESS_ROW_QR_BUTTON_TEST_ID = 'multichain-address-row-qr-button'; + +export const SAMPLE_ICONS = [ + { + name: IconName.Copy, + callback: () => { + // Do nothing + }, + testId: MULTICHAIN_ADDRESS_ROW_COPY_BUTTON_TEST_ID, + }, + { + name: IconName.QrCode, + callback: () => { + // Do nothing + }, + testId: MULTICHAIN_ADDRESS_ROW_QR_BUTTON_TEST_ID, + }, +]; + +export const SAMPLE_MULTICHAIN_ADDRESS_ROW_PROPS: MultichainAddressRowProps = { + chainId: 'eip155:1', + networkName: 'Ethereum Mainnet', + address: '0x1234567890123456789012345678901234567890', + icons: SAMPLE_ICONS, +}; diff --git a/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/MultichainAddressRow.stories.tsx b/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/MultichainAddressRow.stories.tsx index 998c175bc59..12e8b1a6af5 100644 --- a/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/MultichainAddressRow.stories.tsx +++ b/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/MultichainAddressRow.stories.tsx @@ -4,7 +4,11 @@ import { View } from 'react-native'; import { CaipChainId } from '@metamask/utils'; import { mockTheme } from '../../../../util/theme'; import { default as MultichainAddressRowComponent } from './MultichainAddressRow'; -import { SAMPLE_MULTICHAIN_ADDRESS_ROW_PROPS } from './MultichainAddressRow.constants'; +import { + SAMPLE_ICONS, + SAMPLE_MULTICHAIN_ADDRESS_ROW_PROPS, +} from './MultichainAddressRow.constants'; +import { Icon } from './MultichainAddressRow.types'; const MultichainAddressRowMeta = { title: 'Component Library / MultichainAccounts', @@ -24,6 +28,7 @@ const MultichainAddressRowMeta = { }, }, }; + export default MultichainAddressRowMeta; export const MultichainAddressRow = { @@ -31,6 +36,7 @@ export const MultichainAddressRow = { chainId: CaipChainId; networkName: string; address: string; + icons: Icon[]; }) => ( ), @@ -63,6 +70,7 @@ export const WithLongNetworkName = { chainId={args.chainId || '0x1'} networkName="Very Long Network Name That Might Wrap" address={args.address} + icons={SAMPLE_ICONS} /> ), @@ -82,6 +90,7 @@ export const WithCustomNetwork = { chainId="eip155:137" networkName="Polygon Mainnet" address="0xabcdefabcdefabcdefabcdefabcdefabcdefabcd" + icons={SAMPLE_ICONS} /> ), diff --git a/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/MultichainAddressRow.tsx b/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/MultichainAddressRow.tsx index 7f98362b98f..edafdc8069c 100644 --- a/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/MultichainAddressRow.tsx +++ b/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/MultichainAddressRow.tsx @@ -1,4 +1,4 @@ -import React, { useCallback } from 'react'; +import React from 'react'; import { View } from 'react-native'; import Avatar, { @@ -9,7 +9,7 @@ import ButtonIcon, { ButtonIconSizes, } from '../../../components/Buttons/ButtonIcon'; import Text, { TextVariant, TextColor } from '../../../components/Texts/Text'; -import { IconName, IconColor } from '../../../components/Icons/Icon'; +import { IconColor } from '../../../components/Icons/Icon'; import { useStyles } from '../../../hooks'; import { formatAddress } from '../../../../util/address'; import { getNetworkImageSource } from '../../../../util/networks'; @@ -21,34 +21,22 @@ import { MULTICHAIN_ADDRESS_ROW_NETWORK_ICON_TEST_ID, MULTICHAIN_ADDRESS_ROW_NETWORK_NAME_TEST_ID, MULTICHAIN_ADDRESS_ROW_ADDRESS_TEST_ID, - MULTICHAIN_ADDRESS_ROW_COPY_BUTTON_TEST_ID, - MULTICHAIN_ADDRESS_ROW_QR_BUTTON_TEST_ID, } from './MultichainAddressRow.constants'; -import useCopyClipboard from '../../../../components/Views/Notifications/Details/hooks/useCopyClipboard'; const MultichainAddressRow = ({ chainId, networkName, address, + icons, style, testID = MULTICHAIN_ADDRESS_ROW_TEST_ID, ...props }: MultichainAddressRowProps) => { const { styles } = useStyles(styleSheet, { style }); - const copyToClipboard = useCopyClipboard(); const networkImageSource = getNetworkImageSource({ chainId }); const truncatedAddress = formatAddress(address, 'short'); - const handleCopyClick = useCallback(() => { - copyToClipboard(address); - }, [copyToClipboard, address]); - - const handleQrClick = useCallback(() => { - // TODO: Implement QR code functionality - // QR code clicked for address: address - }, []); - return ( - - - + {icons.map((icon, index) => ( + + ))} ); diff --git a/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/MultichainAddressRow.types.ts b/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/MultichainAddressRow.types.ts index e19ae421a37..7c55be07dfa 100644 --- a/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/MultichainAddressRow.types.ts +++ b/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/MultichainAddressRow.types.ts @@ -1,6 +1,25 @@ import { StyleProp, ViewStyle } from 'react-native'; import { CaipChainId } from '@metamask/utils'; +import { IconName } from '../../../components/Icons/Icon'; + +export interface Icon { + /** + * Icon name to display + */ + name: IconName; + /** + * Callback function to execute when the icon is pressed + * This can be used for actions like copying the address or navigating to a different screen + */ + callback: () => void; + /** + * Test ID for the icon, useful for testing purposes + * Should be unique to each icon in the row + */ + testId: string; +} + export interface MultichainAddressRowProps { /** * Chain ID to identify the network @@ -14,6 +33,13 @@ export interface MultichainAddressRowProps { * Address string to display (will be truncated) */ address: string; + /** + * Object containing icons to display in the row. + * Each icon should have a name, a callback function, and test ID. + * The callback will be executed when the icon is pressed. + * Icons are displayed in the order they are provided. + */ + icons: Icon[]; /** * Optional style object for the container */ diff --git a/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/index.ts b/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/index.ts index 29ab96ce96a..cdfb3bc054a 100644 --- a/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/index.ts +++ b/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/index.ts @@ -11,4 +11,5 @@ export { MULTICHAIN_ADDRESS_ROW_COPY_BUTTON_TEST_ID, MULTICHAIN_ADDRESS_ROW_QR_BUTTON_TEST_ID, SAMPLE_MULTICHAIN_ADDRESS_ROW_PROPS, + SAMPLE_ICONS, } from './MultichainAddressRow.constants'; diff --git a/app/component-library/components-temp/MultichainAccounts/MultichainAddressRowsList/MultichainAddressRowsList.tsx b/app/component-library/components-temp/MultichainAccounts/MultichainAddressRowsList/MultichainAddressRowsList.tsx index 5168a9af976..bc5acab86dd 100644 --- a/app/component-library/components-temp/MultichainAccounts/MultichainAddressRowsList/MultichainAddressRowsList.tsx +++ b/app/component-library/components-temp/MultichainAccounts/MultichainAddressRowsList/MultichainAddressRowsList.tsx @@ -10,7 +10,7 @@ import Text, { TextVariant, TextColor } from '../../../components/Texts/Text'; import TextFieldSearch from '../../../components/Form/TextFieldSearch'; import { TextFieldSize } from '../../../components/Form/TextField/TextField.types'; import { strings } from '../../../../../locales/i18n'; -import MultichainAddressRow from '../MultichainAddressRow'; +import MultichainAddressRow, { SAMPLE_ICONS } from '../MultichainAddressRow'; import { selectEvmNetworkConfigurationsByChainId } from '../../../../selectors/networkController'; import { selectNonEvmNetworkConfigurationsByChainId } from '../../../../selectors/multichainNetworkController'; import { @@ -109,6 +109,7 @@ const MultichainAddressRowsList: React.FC = ({ chainId={item.chainId} networkName={item.networkName} address={item.address} + icons={SAMPLE_ICONS} /> ), [], diff --git a/app/component-library/components-temp/Price/AggregatedPercentage/AggregatedPercentage.stories.tsx b/app/component-library/components-temp/Price/AggregatedPercentage/AggregatedPercentage.stories.tsx index 4e8a816b8b9..666439a3648 100644 --- a/app/component-library/components-temp/Price/AggregatedPercentage/AggregatedPercentage.stories.tsx +++ b/app/component-library/components-temp/Price/AggregatedPercentage/AggregatedPercentage.stories.tsx @@ -5,9 +5,6 @@ import { createStore } from 'redux'; import initialBackgroundState from '../../../../util/test/initial-background-state.json'; import { AggregatedPercentageProps } from './AggregatedPercentage.types'; const mockInitialState = { - wizard: { - step: 1, - }, engine: { backgroundState: initialBackgroundState, }, diff --git a/app/component-library/components-temp/Price/PercentageChange/PercentageChange.stories.tsx b/app/component-library/components-temp/Price/PercentageChange/PercentageChange.stories.tsx index 9a802d49789..d25c7093201 100644 --- a/app/component-library/components-temp/Price/PercentageChange/PercentageChange.stories.tsx +++ b/app/component-library/components-temp/Price/PercentageChange/PercentageChange.stories.tsx @@ -5,9 +5,6 @@ import { createStore } from 'redux'; import initialBackgroundState from '../../../../util/test/initial-background-state.json'; const mockInitialState = { - wizard: { - step: 1, - }, engine: { backgroundState: initialBackgroundState, }, diff --git a/app/component-library/components/Navigation/TabBar/TabBar.stories.tsx b/app/component-library/components/Navigation/TabBar/TabBar.stories.tsx index 55874e85b0a..5a4cf0c2da4 100644 --- a/app/component-library/components/Navigation/TabBar/TabBar.stories.tsx +++ b/app/component-library/components/Navigation/TabBar/TabBar.stories.tsx @@ -14,9 +14,6 @@ import { default as TabBarComponent } from './TabBar'; import { TabBarIconKey } from './TabBar.types'; const mockInitialState = { - wizard: { - step: 1, - }, engine: { backgroundState, }, diff --git a/app/component-library/components/Navigation/TabBar/TabBar.test.tsx b/app/component-library/components/Navigation/TabBar/TabBar.test.tsx index 99df17d1191..5e7bb4e04ff 100644 --- a/app/component-library/components/Navigation/TabBar/TabBar.test.tsx +++ b/app/component-library/components/Navigation/TabBar/TabBar.test.tsx @@ -18,9 +18,6 @@ const navigation = { }; const mockInitialState = { - wizard: { - step: 1, - }, engine: { backgroundState, }, diff --git a/app/component-library/components/Navigation/TabBar/TabBar.tsx b/app/component-library/components/Navigation/TabBar/TabBar.tsx index b852e349c3b..11123e04f70 100644 --- a/app/component-library/components/Navigation/TabBar/TabBar.tsx +++ b/app/component-library/components/Navigation/TabBar/TabBar.tsx @@ -26,7 +26,6 @@ import { ICON_BY_TAB_BAR_ICON_KEY, LABEL_BY_TAB_BAR_ICON_KEY, } from './TabBar.constants'; -import OnboardingWizard from '../../../../components/UI/OnboardingWizard'; import { selectChainId } from '../../../../selectors/networkController'; const TabBar = ({ state, descriptors, navigation }: TabBarProps) => { @@ -35,22 +34,6 @@ const TabBar = ({ state, descriptors, navigation }: TabBarProps) => { const chainId = useSelector(selectChainId); const tabBarRef = useRef(null); const tw = useTailwind(); - /** - * Current onboarding wizard step - */ - // TODO: Replace "any" with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const wizardStep = useSelector((reduxState: any) => reduxState.wizard.step); - /** - * Return current step of onboarding wizard if not step 5 nor 0 - */ - const renderOnboardingWizard = useCallback( - () => - [4, 5, 6].includes(wizardStep) && ( - - ), - [navigation, wizardStep], - ); const renderTabBarItem = useCallback( (route: { name: string; key: string }, index: number) => { @@ -144,7 +127,6 @@ const TabBar = ({ state, descriptors, navigation }: TabBarProps) => { > {renderTabBarItems()} - {renderOnboardingWizard()} ); }; diff --git a/app/components/Nav/App/App.test.tsx b/app/components/Nav/App/App.test.tsx index d01115d893c..441f7e58a02 100644 --- a/app/components/Nav/App/App.test.tsx +++ b/app/components/Nav/App/App.test.tsx @@ -12,7 +12,6 @@ import Routes from '../../../constants/navigation/Routes'; import { OPTIN_META_METRICS_UI_SEEN, EXISTING_USER, - ONBOARDING_WIZARD, } from '../../../constants/storage'; import { strings } from '../../../../locales/i18n'; import { @@ -50,6 +49,14 @@ jest.mock('react-native/Libraries/Linking/Linking', () => ({ removeEventListener: jest.fn(), })); +jest.mock('expo-sensors', () => ({ + Accelerometer: { + setUpdateInterval: jest.fn(), + addListener: jest.fn(), + removeAllListeners: jest.fn(), + }, +})); + jest.mock('../../../core/DeeplinkManager/SharedDeeplinkManager', () => ({ init: jest.fn(), parse: jest.fn(), @@ -210,6 +217,7 @@ jest.mock('react-native-branch', () => ({ jest.mock('react-native-device-info', () => ({ getVersion: jest.fn().mockReturnValue('1.0.0'), + getBundleId: jest.fn().mockReturnValue('io.metamask'), })); jest.mock('../../../selectors/accountsController', () => ({ @@ -661,9 +669,7 @@ describe('App', () => { if (key === OPTIN_META_METRICS_UI_SEEN) { return true; // OptinMetrics UI has been seen } - if (key === ONBOARDING_WIZARD) { - return true; - } + return null; // Default for other keys }); diff --git a/app/components/Nav/App/App.tsx b/app/components/Nav/App/App.tsx index 7cf7b1f7e3a..d18dc48d7f9 100644 --- a/app/components/Nav/App/App.tsx +++ b/app/components/Nav/App/App.tsx @@ -48,6 +48,7 @@ import Toast, { } from '../../../component-library/components/Toast'; import AccountSelector from '../../../components/Views/AccountSelector'; import { TokenSortBottomSheet } from '../../../components/UI/Tokens/TokensBottomSheet/TokenSortBottomSheet'; +import ProfilerManager from '../../../components/UI/ProfilerManager'; import { TokenFilterBottomSheet } from '../../../components/UI/Tokens/TokensBottomSheet/TokenFilterBottomSheet'; import NetworkManager from '../../../components/UI/NetworkManager'; import AccountConnect from '../../../components/Views/AccountConnect'; @@ -156,6 +157,7 @@ import SolanaNewFeatureContent from '../../UI/SolanaNewFeatureContent'; import { DeepLinkModal } from '../../UI/DeepLinkModal'; import { checkForDeeplink } from '../../../actions/user'; import { WalletDetails } from '../../Views/MultichainAccounts/WalletDetails/WalletDetails'; +import MultichainAccountActions from '../../Views/MultichainAccounts/sheets/MultichainAccountActions/MultichainAccountActions'; import useInterval from '../../hooks/useInterval'; import { Duration } from '@metamask/utils'; import { selectSeedlessOnboardingLoginFlow } from '../../../selectors/seedlessOnboardingController'; @@ -835,6 +837,10 @@ const AppFlow = () => { name={Routes.MULTICHAIN_ACCOUNTS.ACCOUNT_DETAILS} component={MultichainAccountDetails} /> + { + ); }; diff --git a/app/components/UI/AccountOverview/index.js b/app/components/UI/AccountOverview/index.js index c64c25d0ba3..91bc8793d59 100644 --- a/app/components/UI/AccountOverview/index.js +++ b/app/components/UI/AccountOverview/index.js @@ -164,10 +164,6 @@ class AccountOverview extends PureComponent { /* Triggers global alert */ showAlert: PropTypes.func, - /** - * whether component is being rendered from onboarding wizard - */ - onboardingWizard: PropTypes.bool, /** * Used to get child ref */ @@ -207,9 +203,8 @@ class AccountOverview extends PureComponent { mainView = React.createRef(); openAccountSelector = () => { - const { onboardingWizard, navigation } = this.props; - !onboardingWizard && - navigation.navigate(...createAccountSelectorNavDetails({})); + const { navigation } = this.props; + navigation.navigate(...createAccountSelectorNavDetails({})); }; isAccountLabelDefined = (accountLabel) => @@ -337,7 +332,6 @@ class AccountOverview extends PureComponent { render() { const { account: { address, name }, - onboardingWizard, } = this.props; const colors = this.context.colors || mockTheme.colors; const themeAppearance = this.context.themeAppearance || 'light'; @@ -359,15 +353,10 @@ class AccountOverview extends PureComponent { - + - - - - - - - - - - title - - - - - - content - - - - 1 - /6 - - - - - Got it - - - - - -`; diff --git a/app/components/UI/OnboardingWizard/Coachmark/index.js b/app/components/UI/OnboardingWizard/Coachmark/index.js deleted file mode 100644 index 16fb6dad3ce..00000000000 --- a/app/components/UI/OnboardingWizard/Coachmark/index.js +++ /dev/null @@ -1,439 +0,0 @@ -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; -import { Animated, StyleSheet, Text, View } from 'react-native'; -import { - colors as importedColors, - fontStyles, -} from '../../../../styles/common'; -import StyledButton from '../../StyledButton'; -import { strings } from '../../../../../locales/i18n'; -import { mockTheme, ThemeContext } from '../../../../util/theme'; -import ButtonIcon, { - ButtonIconSizes, -} from '../../../../component-library/components/Buttons/ButtonIcon'; -import { - IconName, - IconColor, -} from '../../../../component-library/components/Icons/Icon'; -import { typography } from '@metamask/design-tokens'; -import { - ButtonSize, - ButtonVariants, - ButtonWidthTypes, -} from '../../../../component-library/components/Buttons/Button'; -import Button from '../../../../component-library/components/Buttons/Button/Button'; -import { OnboardingWizardModalSelectorsIDs } from '../../../../../e2e/selectors/Onboarding/OnboardingWizardModal.selectors'; -import { - getFontFamily, - TextVariant, -} from '../../../../component-library/components/Texts/Text'; - -const createStyles = (colors) => - StyleSheet.create({ - coachmark: { - backgroundColor: colors.primary.default, - borderRadius: 8, - padding: 20, - }, - progress: { - flexDirection: 'row', - justifyContent: 'space-between', - }, - actions: { - flexDirection: 'row', - }, - actionButtonPrimary: { - flex: 0.5, - borderWidth: 1, - borderColor: colors.primary.inverse, - marginRight: 4, - }, - actionButtonSecondary: { - flex: 0.5, - backgroundColor: colors.primary.inverse, - marginLeft: 4, - }, - title: { - ...fontStyles.bold, - color: colors.primary.inverse, - fontSize: 18, - alignSelf: 'center', - }, - triangle: { - width: 0, - height: 0, - backgroundColor: importedColors.transparent, - borderStyle: 'solid', - borderLeftWidth: 15, - borderRightWidth: 15, - borderBottomWidth: 12, - borderLeftColor: importedColors.transparent, - borderRightColor: importedColors.transparent, - borderBottomColor: colors.primary.default, - position: 'absolute', - }, - triangleDown: { - width: 0, - height: 0, - backgroundColor: importedColors.transparent, - borderStyle: 'solid', - borderLeftWidth: 15, - borderRightWidth: 15, - borderTopWidth: 12, - borderLeftColor: importedColors.transparent, - borderRightColor: importedColors.transparent, - borderTopColor: colors.primary.default, - position: 'absolute', - }, - progressButton: { - width: 75, - height: 45, - padding: 5, - }, - leftProgessButton: { - left: 0, - }, - rightProgessButton: { - right: 0, - }, - topCenter: { - marginBottom: 10, - bottom: -2, - alignItems: 'center', - }, - topLeft: { - marginBottom: 10, - bottom: -2, - alignItems: 'flex-start', - marginLeft: 30, - }, - topRight: { - marginBottom: 10, - bottom: -2, - alignItems: 'flex-end', - marginRight: 38, - }, - topLeftCorner: { - marginBottom: 10, - bottom: -2, - alignItems: 'flex-start', - marginLeft: 12, - }, - topRightCorner: { - marginBottom: 10, - bottom: -2, - alignItems: 'flex-end', - marginRight: 12, - }, - bottomCenter: { - marginBottom: 10, - top: -2, - alignItems: 'center', - }, - bottomLeft: { - marginBottom: 10, - top: -2, - alignItems: 'flex-start', - marginLeft: 60, - }, - bottomLeftCorner: { - marginBottom: 10, - top: -2, - alignItems: 'flex-start', - marginLeft: 30, - }, - bottomRight: { - marginBottom: 10, - top: -2, - alignItems: 'flex-end', - marginRight: 90, - }, - circle: { - width: 6, - height: 6, - borderRadius: 6 / 2, - backgroundColor: colors.primary.inverse, - opacity: 0.4, - margin: 3, - }, - solidCircle: { - opacity: 1, - }, - progessContainer: { - flexDirection: 'row', - alignSelf: 'center', - }, - stepCounter: { - ...typography.BodyMD, - fontFamily: getFontFamily(TextVariant.BodyMD), - color: colors.info.inverse, - }, - titleContainer: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - }, - }); - -export default class Coachmark extends PureComponent { - static propTypes = { - /** - * Custom coachmark style to apply - */ - coachmarkStyle: PropTypes.object, - /** - * Custom animated view style to apply - */ - style: PropTypes.object, - /** - * Content object - */ - content: PropTypes.object, - /** - * Title text - */ - title: PropTypes.string, - /** - * Current onboarding wizard step - */ - currentStep: PropTypes.number, - /** - * Callback to be called when next is pressed - */ - onNext: PropTypes.func, - /** - * Callback to be called when back is pressed - */ - onBack: PropTypes.func, - /** - * Whether action buttons have to be rendered - */ - action: PropTypes.bool, - /** - * Top indicator position - */ - topIndicatorPosition: PropTypes.oneOf([ - false, - 'topCenter', - 'topLeft', - 'topLeftCorner', - 'topRight', - 'topRightCorner', - ]), - /** - * Bottom indicator position - */ - bottomIndicatorPosition: PropTypes.oneOf([ - false, - 'bottomCenter', - 'bottomLeft', - 'bottomLeftCorner', - 'bottomRight', - ]), - /** - * Callback called when closing on boarding wizard - */ - onClose: PropTypes.func, - }; - - state = { - ready: false, - }; - - opacity = new Animated.Value(0); - - componentDidMount = () => { - Animated.timing(this.opacity, { - toValue: 1, - duration: 500, - useNativeDriver: true, - isInteraction: false, - }).start(); - }; - - componentWillUnmount = () => { - Animated.timing(this.opacity, { - toValue: 0, - duration: 500, - useNativeDriver: true, - isInteraction: false, - }).start(); - }; - - /** - * Calls props onNext - */ - onNext = () => { - const { onNext } = this.props; - onNext && onNext(); - }; - - /** - * Calls props onBack - */ - onBack = () => { - const { onBack } = this.props; - onBack && onBack(); - }; - - getStyles = () => { - const colors = this.context.colors || mockTheme.colors; - return createStyles(colors); - }; - - /** - * Gets top indicator style according to 'topIndicatorPosition' - * - * @param {string} topIndicatorPosition - Indicator position - * @returns {Object} - Corresponding style object - */ - getIndicatorStyle = (topIndicatorPosition) => { - const styles = this.getStyles(); - - const positions = { - topCenter: styles.topCenter, - topLeft: styles.topLeft, - topRight: styles.topRight, - topLeftCorner: styles.topLeftCorner, - topRightCorner: styles.topRightCorner, - [undefined]: styles.topCenter, - }; - return positions[topIndicatorPosition]; - }; - - /** - * Gets top indicator style according to 'bottomIndicatorPosition' - * - * @param {string} bottomIndicatorPosition - Indicator position - * @returns {Object} - Corresponding style object - */ - getBotttomIndicatorStyle = (bottomIndicatorPosition) => { - const styles = this.getStyles(); - - const positions = { - bottomCenter: styles.bottomCenter, - bottomLeft: styles.bottomLeft, - bottomLeftCorner: styles.bottomLeftCorner, - bottomRight: styles.bottomRight, - [undefined]: styles.bottomCenter, - }; - return positions[bottomIndicatorPosition]; - }; - - /** - * Returns progress bar, back and next buttons. According to currentStep - * - * @returns {Object} - Corresponding view object - */ - renderProgressButtons = () => { - const { currentStep } = this.props; - const styles = this.getStyles(); - return ( - - - {currentStep !== 0 && ( - {currentStep}/6 - )} - - - - {strings('onboarding_wizard_new.coachmark.progress_next')} - - - ); - }; - - /** - * Returns horizontal action buttons - * - * @returns {Object} - Corresponding view object - */ - renderActionButtons = () => { - const styles = this.getStyles(); - - return ( - -