From d867b4d7f67ef807041b1dc699ef3319c0f2ba4d Mon Sep 17 00:00:00 2001 From: George Weiler Date: Thu, 19 Feb 2026 16:41:09 -0700 Subject: [PATCH 1/6] fix(ramps): fix Android navigation showing raw screen names (#26254) ## **Description** Fixes an Android navigation bug in the Ramps Deposit flow where users see raw screen names ("DepositRoot", "RampTokenSelection") as header titles and a 5-second loading spinner after selecting a token. Three root causes were identified and fixed: 1. **"DepositRoot" header title**: The `Root` screen in the Deposit navigator had `headerMode="screen"` but never set `headerShown: false`. Since Root is a transient routing screen that never calls `navigation.setOptions()`, React Navigation fell back to displaying the raw route name. 2. **"RampTokenSelection" header flash**: The `TokenSelection` component set its header options in `useEffect`, which runs after the first paint. Switching to `useLayoutEffect` eliminates the single-frame flash of the raw route name. 3. **5-second loading delay**: `checkExistingToken()` hangs on Android, and the timeout was set to 5000ms. The token check result is only relevant when a `CREATED` order exists. Restructured the flow to check for created orders first (synchronous, from Redux) and skip the token check entirely for the common case. Reduced the timeout from 5s to 2s for the rare created-order path. ## **Changelog** CHANGELOG entry: null ## **Related issues** Follow up from this PR https://github.com/MetaMask/metamask-mobile/pull/26140 Fixes: ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** ### **Before** ### **After** ## **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. --- > [!NOTE] > **Medium Risk** > Touches Deposit flow routing and auth-token gating logic; mistakes could misroute users between `BuildQuote`, `EnterEmail`, and `BankDetails`, though changes are scoped and covered by updated tests. > > **Overview** > Fixes Deposit/Ramps navigation UI issues by hiding the header on the transient `DepositRoot` screen and setting token-selection header options in `useLayoutEffect` to prevent a first-frame flash of raw route names. > > Restructures the Deposit `Root` initialization to *skip `checkExistingToken()` unless a `CREATED` order exists*, and reduces the token-check timeout from `5000ms` to `2000ms`; updates tests to cover the new routing behavior (default route when no created order, `EnterEmail` fallback on token-check failure, and bank-details routing when authenticated). > > Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 9073c855493f93ed13526670c2faed76763fc708. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot). --- .../UI/Ramp/Deposit/Views/Root/Root.test.tsx | 52 ++++++++++++----- .../UI/Ramp/Deposit/Views/Root/Root.tsx | 56 +++++++++---------- .../UI/Ramp/Deposit/routes/index.tsx | 2 +- .../Views/TokenSelection/TokenSelection.tsx | 4 +- 4 files changed, 70 insertions(+), 44 deletions(-) diff --git a/app/components/UI/Ramp/Deposit/Views/Root/Root.test.tsx b/app/components/UI/Ramp/Deposit/Views/Root/Root.test.tsx index c3602761d54..91a6d937b81 100644 --- a/app/components/UI/Ramp/Deposit/Views/Root/Root.test.tsx +++ b/app/components/UI/Ramp/Deposit/Views/Root/Root.test.tsx @@ -87,16 +87,7 @@ describe('Root Component', () => { expect(screen.toJSON()).toMatchSnapshot(); }); - it('calls checkExistingToken on load', async () => { - mockCheckExistingToken.mockResolvedValue(false); - render(Root); - await waitFor(() => { - expect(mockCheckExistingToken).toHaveBeenCalled(); - }); - }); - - it('redirects to BUILD_QUOTE when existing token has been checked', async () => { - mockCheckExistingToken.mockResolvedValue(true); + it('redirects to BUILD_QUOTE immediately when no created orders exist', async () => { render(Root); await waitFor(() => { expect(mockReset).toHaveBeenCalledWith({ @@ -109,6 +100,27 @@ describe('Root Component', () => { ], }); }); + expect(mockCheckExistingToken).not.toHaveBeenCalled(); + }); + + it('calls checkExistingToken when a created order exists', async () => { + const mockOrders = [ + { + id: 'test-order-id', + provider: FIAT_ORDER_PROVIDERS.DEPOSIT, + state: FIAT_ORDER_STATES.CREATED, + }, + ] as FiatOrder[]; + + ( + getAllDepositOrders as jest.MockedFunction + ).mockReturnValue(mockOrders); + mockCheckExistingToken.mockResolvedValue(false); + render(Root); + + await waitFor(() => { + expect(mockCheckExistingToken).toHaveBeenCalled(); + }); }); it('redirects to bank details when there is a created order and user is authenticated', async () => { @@ -173,7 +185,18 @@ describe('Root Component', () => { }); }); - it('falls back to BUILD_QUOTE when checkExistingToken rejects', async () => { + it('redirects to EnterEmail when checkExistingToken rejects and there is a created order', async () => { + const mockOrders = [ + { + id: 'test-order-reject', + provider: FIAT_ORDER_PROVIDERS.DEPOSIT, + state: FIAT_ORDER_STATES.CREATED, + }, + ] as FiatOrder[]; + + ( + getAllDepositOrders as jest.MockedFunction + ).mockReturnValue(mockOrders); mockCheckExistingToken.mockRejectedValue( new Error('SecureKeychain unavailable'), ); @@ -184,8 +207,11 @@ describe('Root Component', () => { index: 0, routes: [ { - name: Routes.DEPOSIT.BUILD_QUOTE, - params: { animationEnabled: false }, + name: 'EnterEmail', + params: { + redirectToRootAfterAuth: true, + animationEnabled: false, + }, }, ], }); diff --git a/app/components/UI/Ramp/Deposit/Views/Root/Root.tsx b/app/components/UI/Ramp/Deposit/Views/Root/Root.tsx index 43fd556a6a3..cb6d43cbcc0 100644 --- a/app/components/UI/Ramp/Deposit/Views/Root/Root.tsx +++ b/app/components/UI/Ramp/Deposit/Views/Root/Root.tsx @@ -14,7 +14,7 @@ import { useParams } from '../../../../../../util/navigation/navUtils'; import { useTheme } from '../../../../../../util/theme'; import Logger from '../../../../../../util/Logger'; -export const TOKEN_CHECK_TIMEOUT_MS = 5000; +export const TOKEN_CHECK_TIMEOUT_MS = 2000; function withTimeout(promise: Promise, ms: number): Promise { let timeoutId: ReturnType; @@ -65,6 +65,16 @@ const Root = () => { const initializeFlow = async () => { if (hasCheckedToken.current) return; + hasCheckedToken.current = true; + + const createdOrder = orders.find( + (order) => order.state === FIAT_ORDER_STATES.CREATED, + ); + + if (!createdOrder) { + navigateToDefaultRoute(); + return; + } let isAuthenticatedFromToken = false; try { @@ -79,31 +89,9 @@ const Root = () => { ); } - hasCheckedToken.current = true; - - const createdOrder = orders.find( - (order) => order.state === FIAT_ORDER_STATES.CREATED, - ); - - if (createdOrder) { - if (!isAuthenticatedFromToken) { - const [routeName, navParams] = createEnterEmailNavDetails({ - redirectToRootAfterAuth: true, - }); - navigation.reset({ - index: 0, - routes: [ - { - name: routeName, - params: { ...navParams, animationEnabled: false }, - }, - ], - }); - return; - } - - const [routeName, navParams] = createBankDetailsNavDetails({ - orderId: createdOrder.id, + if (!isAuthenticatedFromToken) { + const [routeName, navParams] = createEnterEmailNavDetails({ + redirectToRootAfterAuth: true, }); navigation.reset({ index: 0, @@ -114,9 +102,21 @@ const Root = () => { }, ], }); - } else { - navigateToDefaultRoute(); + return; } + + const [routeName, navParams] = createBankDetailsNavDetails({ + orderId: createdOrder.id, + }); + navigation.reset({ + index: 0, + routes: [ + { + name: routeName, + params: { ...navParams, animationEnabled: false }, + }, + ], + }); }; initializeFlow().catch((error) => { diff --git a/app/components/UI/Ramp/Deposit/routes/index.tsx b/app/components/UI/Ramp/Deposit/routes/index.tsx index 6488babd999..67924b58f87 100644 --- a/app/components/UI/Ramp/Deposit/routes/index.tsx +++ b/app/components/UI/Ramp/Deposit/routes/index.tsx @@ -82,7 +82,7 @@ const MainRoutes = ({ route }: MainRoutesProps) => { name={Routes.DEPOSIT.ROOT} component={Root} initialParams={parentParams} - options={{ animationEnabled: false }} + options={{ animationEnabled: false, headerShown: false }} /> { + useLayoutEffect(() => { navigation.setOptions( getDepositNavbarOptions( navigation, From 8f351a460b0ef61e7745a924f8bddd8c81d35605 Mon Sep 17 00:00:00 2001 From: Brian August Nguyen Date: Thu, 19 Feb 2026 15:53:06 -0800 Subject: [PATCH 2/6] refactor: Updated header for Settings - Contacts page (#26243) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR aligns the **Contacts** screen with the inline header pattern by replacing the stack navbar with an inline `HeaderCompactStandard` component. **Reason for change:** The Contacts screen used `getNavigationOptionsTitle` and `navigation.setOptions()` in `componentDidMount` / `componentDidUpdate` to configure the stack header. Migrating to an inline header matches the app-wide header approach (see `docs/header-alignment-plans.md`) and keeps the Contacts Settings subpage consistent with other screens that use `HeaderCompactStandard` (e.g. General Settings, main Settings view). **What changed:** 1. **MainNavigator** – The `ContactsSettings` screen now uses `options={{ headerShown: false }}` so the stack header is hidden and the header is fully inline. 2. **Contacts** – Renders inline `HeaderCompactStandard` at the top with title `strings('app_settings.contacts_title')`, back button calling `navigation.goBack()`, and `includesTopInset`. Removed the `getNavigationOptionsTitle` import and all `updateNavBar` logic (including from `componentDidMount` and `componentDidUpdate`). Removed `marginTop: 16` from the wrapper style so the header sits at the top. The page remains wrapped in `SafeAreaView` with `edges={{ bottom: 'additive' }}`. 3. **Tests** – Switched from Enzyme shallow render to `renderScreen` for full rendering with navigation. Added tests for: inline header presence (via `ContactsViewSelectorIDs.HEADER`), "Contacts" title, and back button press. Added `HEADER` to `ContactsView.testIds.ts` and passed `testID` to `HeaderCompactStandard`. Updated snapshot. User-visible behavior (back button, title, safe area) is unchanged; the header is now rendered inline. ## **Changelog** This PR is not end-user-facing (internal header implementation; same UI/UX, header is now inline). CHANGELOG entry: null ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/jira/software/c/projects/DSYS/boards/1888?selectedIssue=DSYS-355 ## **Manual testing steps** ```gherkin Feature: Contacts inline header Scenario: user opens Contacts from Settings Given the app is open and the user is on the main Settings screen When the user taps "Contacts" Then the Contacts screen is shown with a header that displays "Contacts" and a back arrow on the left When the user taps the back arrow Then the app navigates back to the main Settings screen Scenario: safe area and content layout Given the user is on the Contacts screen When the screen is displayed Then the header sits below the status bar (safe area respected) And the contact list and "Add contact" button are visible and not covered by the header ``` ## **Screenshots/Recordings** ### **Before** ### **After** https://github.com/user-attachments/assets/aef19ecb-499c-4d89-902a-62c44295c900 ## **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. --- > [!NOTE] > **Low Risk** > UI/navigation presentation refactor limited to the Contacts settings screen plus test updates; no new data handling or auth logic, with low regression risk mainly around header/back behavior and spacing. > > **Overview** > Updates the Settings `Contacts` screen to use an **inline** `HeaderCompactStandard` instead of configuring the stack header via `navigation.setOptions`, and hides the stack header for `ContactsSettings` (`headerShown: false`). This also adjusts layout spacing (removes the wrapper top margin) and adds stable test IDs for the new header/back button. > > Reworks the Contacts tests from Enzyme shallow rendering to React Navigation + Testing Library (`renderScreen`/`renderWithProvider`), adding coverage for header title rendering and back navigation, and updates the snapshot accordingly. > > Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit eb56dd2f6f96372c3741cc7816889241eb7ebd09. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot). --- app/components/Nav/Main/MainNavigator.js | 2 +- .../Settings/Contacts/ContactsView.testIds.ts | 2 + .../__snapshots__/index.test.tsx.snap | 520 +++++++++++++++++- .../Views/Settings/Contacts/index.js | 30 +- .../Views/Settings/Contacts/index.test.tsx | 85 ++- 5 files changed, 582 insertions(+), 57 deletions(-) diff --git a/app/components/Nav/Main/MainNavigator.js b/app/components/Nav/Main/MainNavigator.js index dcb9c527749..f38190a6785 100644 --- a/app/components/Nav/Main/MainNavigator.js +++ b/app/components/Nav/Main/MainNavigator.js @@ -424,7 +424,7 @@ const SettingsFlow = () => { - - + + + + + + + + + + + + + + + + + + + + + + + + + + Contacts + + + + + + + + + + + + + + + Add contact + + + + + + + + + + + + + + `; diff --git a/app/components/Views/Settings/Contacts/index.js b/app/components/Views/Settings/Contacts/index.js index 37ef2116914..ace2ae05af2 100644 --- a/app/components/Views/Settings/Contacts/index.js +++ b/app/components/Views/Settings/Contacts/index.js @@ -3,8 +3,8 @@ import { StyleSheet } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import PropTypes from 'prop-types'; import { strings } from '../../../../../locales/i18n'; -import { getNavigationOptionsTitle } from '../../../UI/Navbar'; import { connect } from 'react-redux'; +import HeaderCompactStandard from '../../../../component-library/components-temp/HeaderCompactStandard'; import AddressList from '../../confirmations/legacy/components/AddressList'; import StyledButton from '../../../UI/StyledButton'; import Engine from '../../../../core/Engine'; @@ -21,7 +21,6 @@ const createStyles = (colors) => wrapper: { backgroundColor: colors.background.default, flex: 1, - marginTop: 16, }, addContact: { marginHorizontal: 24, @@ -58,25 +57,7 @@ class Contacts extends PureComponent { actionSheet; contactAddressToRemove; - updateNavBar = () => { - const { navigation } = this.props; - const colors = this.context.colors || mockTheme.colors; - navigation.setOptions( - getNavigationOptionsTitle( - strings('app_settings.contacts_title'), - navigation, - false, - colors, - ), - ); - }; - - componentDidMount = () => { - this.updateNavBar(); - }; - componentDidUpdate = (prevProps) => { - this.updateNavBar(); const { chainId } = this.props; if ( prevProps.addressBook && @@ -143,6 +124,15 @@ class Contacts extends PureComponent { testID={ContactsViewSelectorIDs.CONTAINER} edges={{ bottom: 'additive' }} > + this.props.navigation.goBack()} + includesTopInset + testID={ContactsViewSelectorIDs.HEADER} + backButtonProps={{ + testID: ContactsViewSelectorIDs.HEADER_BACK_BUTTON, + }} + /> void }; +}) { + return ( + + Placeholder + navigation.navigate('ContactsSettings')} + > + Go to Contacts + + + ); +} describe('Contacts', () => { - it('should render correctly', () => { - const wrapper = shallow( - - - , + it('renders correctly', () => { + const { toJSON } = renderScreen( + Contacts, + { name: 'ContactsSettings', options: { headerShown: false } }, + { state: initialState }, + ); + expect(toJSON()).toMatchSnapshot(); + }); + + it('renders inline header with Contacts title', () => { + const { getByTestId, getByText } = renderScreen( + Contacts, + { name: 'ContactsSettings', options: { headerShown: false } }, + { state: initialState }, ); - expect(wrapper).toMatchSnapshot(); + expect(getByTestId(ContactsViewSelectorIDs.HEADER)).toBeOnTheScreen(); + expect(getByText(strings('app_settings.contacts_title'))).toBeOnTheScreen(); + }); + + it('navigates back when header back button is pressed', () => { + const { getByTestId } = renderWithProvider( + + + + , + { state: initialState }, + ); + + expect(getByTestId(PLACEHOLDER_SCREEN_TEST_ID)).toBeOnTheScreen(); + fireEvent.press(getByTestId(GO_TO_CONTACTS_TEST_ID)); + + const backButton = getByTestId(ContactsViewSelectorIDs.HEADER_BACK_BUTTON); + expect(backButton).toBeOnTheScreen(); + fireEvent.press(backButton); + + expect(getByTestId(PLACEHOLDER_SCREEN_TEST_ID)).toBeOnTheScreen(); }); }); From 480800150f558a59314b6b603040e094720bc046 Mon Sep 17 00:00:00 2001 From: Cal Leung Date: Thu, 19 Feb 2026 16:05:21 -0800 Subject: [PATCH 3/6] refactor: Replace device security toggle in settings + deprecate remember me + introduce auth capabilities functionality (#25994) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This change replaces the legacy authentication toggles in settings (biometrics, passcode, and remember me) with a consolidated toggle: Device Authentication. With the new implementation, we support three authentication tiers: Remember Me (Legacy), Biometrics, and Passcode/Pin/Pattern (Consolidated into Device Authentication). Since Remember Me is deprecated, users using the feature will initially see a Remember Me toggle (to preserve backwards compatibility). However, once turned off, Remember Me will never be shown again. New users will never see the Remember Me option. We are also supporting backwards compatibility with respect to the other two authentication preferences: biometrics and passcode. The app will continue to respect the option that the user has chosen as long as the auth preference is toggled on. The toggles and authentication system is also designed to support the transition into a more seamless authentication system: Biometrics first authentication with device passcode fallback. In other words, users will be able to use any biometrics or device passcode to access their wallet. For example, if a user disables biometrics in the OS settings while auth preferences is enabled in the app, the app will automatically fallback to use device passcode as opposed to password. Password is used as a fallback whenever authentication preferences is disabled by a user in the app. This is why the toggle is labeled as Device Authentication, because the device handles which auth tier to use. ## **Changelog** CHANGELOG entry: ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/MCWP-305 ## **Manual testing steps** Remember Me (new users won't see this option anymore) - With Remember Me previously toggled on - User should see Remember Me toggle in Settings - Turn off Remember Me - With biometrics enabled in OS settings, device toggle should now show biometrics option (or device authentication for Android) - With biometrics disabled in OS settings, device toggle should now show passcode option (or device authentication for Android) Legacy biometrics on iOS (force use biometrics) - Turn on biometrics - App should prompt biometrics when locked - Turn off biometrics in OS - App should fallback to use password - User should see CTA in settings that links into OS settings to re-enable device authentication - Turn on biometrics in OS settings - App should now prompt biometrics - Turn off biometrics toggle in settings - App should now fallback to password Legacy Passcode on iOS (force use passcode) - Turn on passcode - App should prompt passcode when locked - Turn off passcode in OS - App should fallback to use password - User should see CTA in settings that links into OS settings to re-enable device authentication - Turn on passcode in OS settings - App should now prompt passcode - Turn off passcode toggle in settings - App should now fallback to password Device Authentication (Consolidated behavior) - If biometrics is enabled in OS - Turn on device authentication toggle - App should prompt biometrics when locked - Turn off biometrics in OS - App should fallback to use passcode - Turn off passcode in OS - User should see CTA in settings that links into OS settings to re-enable device authentication - Turn on biometrics in OS - App should prompt biometrics when locked - Turn off device authentication in settings - App should now fallback to password ## **Screenshots/Recordings** ### **Before** ### **After** Turning off Remember Me https://github.com/user-attachments/assets/606951fa-be6b-4e2f-8915-7eaa72f32a90 Legacy biometrics https://github.com/user-attachments/assets/7c5fcbae-3e24-4af1-9971-827cda947319 Legacy passcode https://github.com/user-attachments/assets/4bd20a71-314b-4544-a412-3091e05de0d3 Device authentication on iOS https://github.com/user-attachments/assets/56db365b-efc7-4d51-866a-208da2e03f9b Device authentication on Android https://github.com/user-attachments/assets/a4d5ffbe-0f90-49cd-9ccd-a7f9bcb524e4 Device authentication on Login screen https://github.com/user-attachments/assets/1190cfad-b015-4697-a8ef-176045bc6543 ## **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** - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] 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. --- > [!NOTE] > **Medium Risk** > Touches login and security-settings authentication flows (capability detection, unlock entry points, and preference updates), so regressions could block sign-in or mis-handle auth state despite solid test updates. > > **Overview** > **Consolidates authentication UX around a single “Device Authentication” capability model.** The login screen replaces the platform-specific `BiometryButton` (and its many icon variants) with a new `DeviceAuthenticationButton` that renders a single `SecurityKey` icon and shows/hides based on `useAuthCapabilities` and lock state. > > **Refactors Security Settings and Remember Me deprecation flow.** The legacy settings sections for biometrics/passcode and the Remember Me toggle are removed and replaced with a new `DeviceSecurityToggle` that derives the target auth type via `getAuthCapabilities`, supports a settings CTA when OS auth must be enabled, handles password-required errors via `EnterPasswordSimple` callbacks, and uses optimistic UI state. Disabling Remember Me via `TurnOffRememberMeModal` now always restores `PASSWORD` auth and clears `PREVIOUS_AUTH_TYPE_BEFORE_REMEMBER_ME`, with modal button styling made configurable via `cancelButtonMode`. > > Tests and snapshots are updated accordingly (new toggle/button tests, updated selectors, and improved async/trace handling in login tests). > > Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 9a0dae8ba8ea11cb4868dcc7e2fe58a797318359. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot). --- .../UI/BiometryButton/BiometryButton.tsx | 139 --- .../UI/BiometryButton/index.test.tsx | 152 --- app/components/UI/BiometryButton/index.ts | 1 - app/components/UI/BiometryButton/styles.ts | 22 - .../DeviceAuthenticationButton.tsx | 38 + .../DeviceAuthenticationButton/index.test.tsx | 40 + .../UI/DeviceAuthenticationButton/index.ts | 1 + .../UI/DeviceAuthenticationButton/styles.ts | 14 + .../TurnOffRememberMeModal.test.tsx | 33 +- .../TurnOffRememberMeModal.tsx | 24 +- .../UI/WarningExistingUserModal/index.js | 7 +- .../Views/EnterPasswordSimple/index.js | 4 +- .../Views/EnterPasswordSimple/index.test.tsx | 5 - .../Views/Login/LoginView.testIds.ts | 9 +- .../Login/__snapshots__/index.test.tsx.snap | 64 +- app/components/Views/Login/index.test.tsx | 179 ++- app/components/Views/Login/index.tsx | 61 +- app/components/Views/Login/index2.test.tsx | 96 +- .../Sections/DeviceSecurityToggle.test.tsx | 481 ++++++++ .../Sections/DeviceSecurityToggle.tsx | 187 +++ .../Sections/LoginOptionsSettings.test.tsx | 1017 ----------------- .../Sections/LoginOptionsSettings.tsx | 320 ------ .../MetaMetricsAndDataCollectionSection.tsx | 5 +- .../Sections/RememberMeOptionSection.test.tsx | 832 -------------- .../Sections/RememberMeOptionSection.tsx | 183 --- .../SecuritySettings/Sections/index.ts | 6 +- .../SecurityPrivacyView.testIds.ts | 3 +- .../SecuritySettings.constants.ts | 2 - .../SecuritySettings.styles.ts | 4 +- .../SecuritySettings.test.tsx | 29 +- .../SecuritySettings/SecuritySettings.tsx | 25 +- .../SecuritySettings.test.tsx.snap | 34 +- app/constants/userProperties.ts | 14 +- .../Authentication/Authentication.test.ts | 361 +++--- app/core/Authentication/Authentication.ts | 212 ++-- .../hooks/useAuthCapabilities.test.ts | 115 +- .../hooks/useAuthCapabilities.ts | 32 +- .../hooks/useAuthCapabilities.types.ts | 4 - .../hooks/useAuthentication.test.ts | 171 +-- .../Authentication/hooks/useAuthentication.ts | 3 + app/core/Authentication/types.ts | 16 +- app/core/Authentication/utils.test.ts | 265 +++-- app/core/Authentication/utils.ts | 119 +- app/core/SecureKeychain.test.ts | 53 +- app/core/SecureKeychain.ts | 81 +- app/store/migrations/120.test.ts | 134 +++ app/store/migrations/120.ts | 70 ++ app/store/migrations/index.ts | 2 + locales/languages/en.json | 16 +- 49 files changed, 1938 insertions(+), 3747 deletions(-) delete mode 100644 app/components/UI/BiometryButton/BiometryButton.tsx delete mode 100644 app/components/UI/BiometryButton/index.test.tsx delete mode 100644 app/components/UI/BiometryButton/index.ts delete mode 100644 app/components/UI/BiometryButton/styles.ts create mode 100644 app/components/UI/DeviceAuthenticationButton/DeviceAuthenticationButton.tsx create mode 100644 app/components/UI/DeviceAuthenticationButton/index.test.tsx create mode 100644 app/components/UI/DeviceAuthenticationButton/index.ts create mode 100644 app/components/UI/DeviceAuthenticationButton/styles.ts create mode 100644 app/components/Views/Settings/SecuritySettings/Sections/DeviceSecurityToggle.test.tsx create mode 100644 app/components/Views/Settings/SecuritySettings/Sections/DeviceSecurityToggle.tsx delete mode 100644 app/components/Views/Settings/SecuritySettings/Sections/LoginOptionsSettings.test.tsx delete mode 100644 app/components/Views/Settings/SecuritySettings/Sections/LoginOptionsSettings.tsx delete mode 100644 app/components/Views/Settings/SecuritySettings/Sections/RememberMeOptionSection.test.tsx delete mode 100644 app/components/Views/Settings/SecuritySettings/Sections/RememberMeOptionSection.tsx create mode 100644 app/store/migrations/120.test.ts create mode 100644 app/store/migrations/120.ts diff --git a/app/components/UI/BiometryButton/BiometryButton.tsx b/app/components/UI/BiometryButton/BiometryButton.tsx deleted file mode 100644 index 9d67da2e4f1..00000000000 --- a/app/components/UI/BiometryButton/BiometryButton.tsx +++ /dev/null @@ -1,139 +0,0 @@ -import React from 'react'; -import { - TouchableOpacity, - Image as ImageRN, - Platform, - TouchableOpacityProps, -} from 'react-native'; -import { useTheme } from '../../../util/theme'; -import { BIOMETRY_TYPE } from 'react-native-keychain'; -import AUTHENTICATION_TYPE from '../../../constants/userProperties'; -import { createStyles } from './styles'; -import Icon, { - IconName, - IconSize, - IconColor, -} from '../../../component-library/components/Icons/Icon'; -import { LoginViewSelectors } from '../../Views/Login/LoginView.testIds'; - -/* eslint-disable @typescript-eslint/no-require-imports */ -/* eslint-disable @typescript-eslint/no-var-requires */ -/* eslint-disable import/no-commonjs */ -const androidIris = require('../../../images/android-iris.png'); - -type BiometryType = BIOMETRY_TYPE | AUTHENTICATION_TYPE | string | null; - -type BiometryButtonProps = { - hidden: boolean; - biometryType: BiometryType | null; -} & TouchableOpacityProps; - -const BiometryButton = ({ - hidden, - biometryType, - ...props -}: BiometryButtonProps) => { - const { colors } = useTheme(); - const styles = createStyles(colors); - - const renderIcon = (type: BiometryType) => { - if (Platform.OS === 'ios') { - if (type === BIOMETRY_TYPE.TOUCH_ID) { - return ( - - ); - } else if (type?.includes(AUTHENTICATION_TYPE.PASSCODE)) { - return ( - - ); - } - return ( - - ); - } - - if (Platform.OS === 'android') { - if (type === BIOMETRY_TYPE.FINGERPRINT) { - return ( - - ); - } else if (type === BIOMETRY_TYPE.FACE) { - return ( - - ); - } else if (type === BIOMETRY_TYPE.IRIS) { - return ( - - ); - } else if (type?.includes(AUTHENTICATION_TYPE.PASSCODE)) { - return ( - - ); - } - } - - return ( - - ); - }; - - if (hidden) return null; - - return ( - - {biometryType ? renderIcon(biometryType) : null} - - ); -}; - -export default BiometryButton; diff --git a/app/components/UI/BiometryButton/index.test.tsx b/app/components/UI/BiometryButton/index.test.tsx deleted file mode 100644 index e4dcc731659..00000000000 --- a/app/components/UI/BiometryButton/index.test.tsx +++ /dev/null @@ -1,152 +0,0 @@ -import React from 'react'; -import { render } from '@testing-library/react-native'; -import { Platform } from 'react-native'; -import { BIOMETRY_TYPE } from 'react-native-keychain'; -import BiometryButton from './BiometryButton'; -import AUTHENTICATION_TYPE from '../../../constants/userProperties'; -import { LoginViewSelectors } from '../../Views/Login/LoginView.testIds'; - -jest.mock('react-native', () => ({ - ...jest.requireActual('react-native'), - Platform: { OS: 'ios' }, -})); - -const mockOnPress = jest.fn(); - -describe('BiometryButton', () => { - it('should hide when hidden is true', () => { - const { toJSON } = render( -