[Home refresh] Unified Scan / Receive screen#944
Conversation
Reusable segmented tab control matching the Figma Tabs designs. Supports three sizing modes: hug-content (default, all pills sized to the widest label), full-width, and fixed width. nativewind-styled; hug mode measures pill widths and stays hidden for the single measuring frame to avoid a width jump. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Migrate the two DeveloperBackendConfig usages to <Tabs sizing="fill" /> and remove the now-redundant SegmentedControl component. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Nested git worktrees under .claude/ (created by Claude Code sessions) are full repo copies; Jest's Haste scan of their node_modules/__mocks__ collided with the main project and broke the suite. Exclude .claude/ from module and test path resolution. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…mera Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…m auto analytics Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…er hook Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…per-tab analytics Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…emove AccountQRCodeScreen Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…Stellar logo, tertiary copy button - Center the Scan/Receive tabs on screen (the Tabs hug container's self-start was defeating the parent's items-center; use a row with justify-center instead). - Lift the dev-only WalletConnect manual-input overlay above the bottom safe-area inset instead of pinning it to the screen edge. - Render the Stellar network chip logo as a circle. - Make the Copy wallet address button tertiary. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
iOS Simulator preview build is ready: https://github.com/stellar/freighter-mobile/releases/tag/untagged-f1c51182b8ef2d576dc8 (SDF collaborators only — install instructions in the release description) |
…ide margins - Keep the camera session active while the screen is focused (both tabs) instead of toggling it per-tab, so switching Receive -> Scan no longer blinks from the capture session re-initializing. Scanning is gated to the Scan tab so the still-running camera behind Receive can't trigger a navigation; the camera still pauses on blur (useIsFocused). - Give the dev-only WalletConnect overlay 20px horizontal margins. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… overlay Match the app-wide bottom spacing (insets.bottom + DEFAULT_PADDING, as BaseLayout applies on the Asset Details and Swap screens) for the Receive copy button and the dev WalletConnect overlay, instead of the ad-hoc 16px. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…n title Pin the copy icon to the right of the label and color it with the tertiary button's text color (near-black) so it matches the title instead of using the theme-dependent foreground color. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Helper text uses md (Inter Medium 16, secondary) per Figma Text/MD/500, was sm. - Copy icon size 18px. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Unifies scanning and receiving into one tabbed screen while preserving navigation, analytics, and send-flow scanning.
Changes:
- Adds the Scan/Receive screen with persistent camera and QR display.
- Introduces reusable SDS Tabs and migrates existing controls.
- Updates navigation, translations, analytics, and tests.
Reviewed changes
Copilot reviewed 26 out of 27 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
src/navigators/RootNavigator.tsx |
Registers the unified screen. |
src/navigators/AddFundsNavigator.tsx |
Adds the screen to Add Funds. |
src/i18n/locales/pt/translations.json |
Adds Portuguese strings. |
src/i18n/locales/en/translations.json |
Adds English strings. |
src/hooks/useHomeHeaders.tsx |
Redirects Home entry points. |
src/helpers/navigationOptions.tsx |
Generalizes transition documentation. |
src/config/routes.ts |
Defines unified route parameters. |
src/config/analyticsConfig.ts |
Adds per-tab analytics handling. |
src/components/sds/Tabs/index.tsx |
Implements the Tabs control. |
src/components/sds/SegmentedControl.tsx |
Removes the superseded control. |
src/components/screens/ScanReceiveScreen/ScanTabView.tsx |
Adds the scanner tab body. |
src/components/screens/ScanReceiveScreen/ScanReceiveScreen.tsx |
Implements tab orchestration and animation. |
src/components/screens/ScanReceiveScreen/ReceiveTabView.tsx |
Adds account QR and copy action. |
src/components/screens/ScanReceiveScreen/index.ts |
Exports the unified screen. |
src/components/screens/ChangeNetworkScreen/DeveloperBackendConfig.tsx |
Migrates controls to Tabs. |
src/components/screens/AddFundsScreen/AddFundsScreen.tsx |
Opens the Receive tab. |
src/components/screens/AccountQRCodeScreen/index.ts |
Removes the old export. |
src/components/screens/AccountQRCodeScreen/AccountQRCodeScreen.tsx |
Removes the old QR screen. |
src/components/QRScanner.tsx |
Adds camera activation control. |
jest.setup.js |
Mocks navigation focus. |
jest.config.js |
Excludes nested worktrees. |
__tests__/Tabs.test.tsx |
Tests Tabs behavior. |
__tests__/config/analyticsConfig.test.ts |
Tests analytics exclusions. |
__tests__/components/screens/ScanReceiveScreen/ScanReceiveScreen.test.tsx |
Tests tabs, analytics, and closing. |
__tests__/components/screens/ScanReceiveScreen/ReceiveTabView.test.tsx |
Tests account display and copying. |
__tests__/components/screens/AddFundsScreen/AddFundsScreen.test.tsx |
Updates navigation expectations. |
__tests__/components/screens/AccountQRCodeScreen/AccountQRCodeScreen.test.tsx |
Removes obsolete tests. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…ating, a11y - Mount the scanner lazily on the first Scan visit so opening straight to Receive (My QR code / Add Funds) never triggers a camera permission prompt; keep it mounted afterwards. - Combine useIsFocused with AppState so the camera also pauses when the app is backgrounded, not just on route blur. - Add a real scanEnabled gate to QRScanner that drops detections before the debounce/dedup logic, instead of a no-op onRead that left codes cached and unscannable for up to 10s after returning to Scan. - Expose accessibilityRole="tab" and accessibilityState on Tabs touch targets. - Correct the ScanReceiveScreen camera-lifecycle doc comment. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e, add tests Copilot review: - Hide the inactive Scan/Receive layer from screen readers (accessibilityElements Hidden + importantForAccessibility) so VoiceOver/TalkBack can't reach content behind the visible tab. - Add the missing article: "the Stellar network." (en). Additional review findings: - Delete the orphaned accountQRCodeScreen translation blocks (en + pt). - Add QRScanner tests for the scanEnabled drop-before-debounce gate. - Add accessibilityRole="tablist" to the Tabs container. - Add Tabs tests for hug-mode equalization and fill mode. - Drop the vestigial transition param / withTransitionOverride / useClearTransition Param plumbing on the unified screen (no caller passes transition). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Other than that, everything looks good to me! |
I think we decided to remove the component that allows users to paste the wallet connect URI, but we want to keep support for scanning wallet connect QR codes generally |
@minkyeongshin the WC scan functionality is removed just on the Send flow, the other flows should keep it. The video shows it. We can ignore though the "Paste WalletConnect URI" box at the bottom of the Scan screen since that's only shown on the Dev build, the Prod build won't have it.
The notification component should be discussed and implemented as a separate task (we've raised it last year). The Figma file is using a new notification style which doesn't support "title + message" as it only shows a single "title" string. We use "title + message" in lots of places in the code so this needs some discussion. The Notification component on SDS components also have support for "title + message" as reference. As for the icons, do you see some icon that is not matching Figma designs? Thanks! |
@JakeUrban yeah I also noticed it while testing, this is an existing Prod behavior. I'll adjust it together with the Home refresh 👍 |
Nit: "camera lifecycle" isn't actually covered by the ScanReceiveScreen testsNit / test-coverage gap — not a merge blocker. TL;DR: The headline behavior of this screen — keeping the camera warm underneath the Receive layer and pausing it when the app is backgrounded or the screen loses focus — has no test asserting it. The current tests only check when the scanner is mounted, not that it actually pauses/resumes. If that pause-on-background logic silently regressed, the suite would stay green. Worth a follow-up test; fine to merge as-is. Detailed explanation (for agents)Root cause: Meanwhile the real wiring being claimed as "camera lifecycle" is here: Because the stub ignores its props, nothing asserts that Suggested fixes (in increasing order of depth):
|
piyalbasu
left a comment
There was a problem hiding this comment.
This is looking good to me on Simulator! Confirmed that the Paste Wallectconnect URI is gated behind a dev build. Just left 1 nit test improvement
The existing tests fully mocked ScanTabView to a static stub, so nothing verified the "keep camera warm but paused" contract described in the component doc comment. A regression dropping the `isFocused && isAppActive` guard (camera stays live while backgrounded) or the tab-gated scanning would have kept the suite green. Capture the props handed to ScanTabView and add camera-lifecycle tests: - camera active + scanning on the Scan tab - camera stays warm but scanning is gated off behind the Receive tab - camera pauses when the screen is not focused - camera pauses when the app is backgrounded and resumes on return Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UW7CohZ9tsQjuqpddG5DBd

What this does
Introduces a single Scan / Receive screen that replaces the two separate screens we had for scanning a QR code and showing your account's QR code. A tab control in the header switches between:
Switching tabs does a smooth fade, and the camera stays warm underneath so there's no flicker when you jump back to Scan.
🎨 Figma: Scan / Receive design
Where it opens from
Also included
/sds/Tabsdesign-system component (hug / full-width / fixed-width sizing) used in the header. It replaces the oldSegmentedControl, which is removed.AccountQRCodeScreenis removed (its consumers now use the Receive tab). The Send-flow scanner is untouched — it keeps its own screen.📹 Demo video
One video covering all four scanner flows:
scan-receive-ux.mov
🔧 Technical details (for reviewers / agents)
Structure
New
src/components/screens/ScanReceiveScreen/:ScanReceiveScreen.tsx— orchestrator. HoldsactiveTab(fromroute.params.initialTab, default"scan"). Renders a floating header (X close + centeredTabs) over two absolute-fill layers: the scanner and an opaque Receive layer whose opacity is animated (Animated, ~200ms) to crossfade over the camera.ScanTabView.tsx— headerless scanner body. Reuses the existinguseQRCodeScreenScanner(HOME_SCANNER)hook +QRScanner, so all address / WalletConnect handling and QR analytics are unchanged. Also hosts the dev-only WalletConnect manual-input overlay.ReceiveTabView.tsx— avatar + name + truncated address, QR (react-native-qrcode-svg), "Stellar" chip, helper text, full-width tertiary "Copy wallet address" button.Camera lifecycle (why it doesn't blink)
Toggling
vision-camera'sisActiveoff/on tears down and re-initializes the capture session, which shows a black frame (the blink). Instead:useIsFocused), across both tabs — the opaque Receive layer just fades over the live feed.isScanning) so the still-running camera behind Receive can't trigger a navigation.QRScannergained an optionalisActiveprop (defaulttrue) for this./sds/Tabssizing:"hug"(default, all pills sized to the widest label via anonLayoutmeasure pass, hidden for the single measuring frame to avoid a width jump),"fill", or fixedwidth.SegmentedControl; the twoDeveloperBackendConfigusages migrated to<Tabs sizing="fill" />.alignSelf: flex-start, so a consumer that wants it centered must center it on the main axis (flex-row justify-center), not the cross axis (items-center). The header does this.Analytics (per-tab, preserved)
ROUTES_WITHOUT_ANALYTICSso it does not auto-fire a generic view event.useEffect([activeTab])firesVIEW_SCAN_QR_CODE(new enum,"loaded screen: scan qr code") on Scan andVIEW_ACCOUNT_QR_CODEon Receive — matching the previous per-screen tracking on both initial open and tab switches.QR_SCAN_SUCCESS/QR_SCAN_ERRORare untouched (reused hook +QRScanner).Navigation / cleanup
SCAN_RECEIVE_SCREEN(+ScanReceiveTabtype), registered inRootNavigatorand (mirroring howAccountQRCodeScreenwas) in the Add Funds stack so it opens within that stack.AccountQRCodeScreen+ its test deleted;ACCOUNT_QR_CODE_SCREENroute/params removed.useHomeHeaders) andAddFundsScreennow navigate to the unified screen.Testing / infra
ScanReceiveScreen(initial tab, per-tab analytics, close),ReceiveTabView(renders address, copies public key), plusanalyticsConfigassertions./sds/Tabshas its own unit tests.jest.config.jsnow ignores nested.claude/git worktrees, which were colliding via Haste and breaking the suite.🤖 Generated with Claude Code