Skip to content

fix: wrap receive tab swipes around - #1027

Merged
ovitrif merged 12 commits into
masterfrom
fix/receive-tabs-wraparound
Oct 21, 2026
Merged

ovitrif merged 12 commits into
masterfrom
fix/receive-tabs-wraparound

Conversation

@ovitrif

@ovitrif ovitrif commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #1024
Twin: synonymdev/bitkit-android#1593
Stacked on #1020

This PR makes the Receive tabs wrap around: a swipe right on the first tab selects the last one, and a swipe left on the last tab selects the first one.

Expected behaviour, as specified: "we can swipe fw to next, bw to prev, and if on last + fw it 'rotates' back, if on first + bw it rotates vice-versa".

Description

  • ReceiveQr: the paged TabView holds an extra page before the first tab and after the last one, each showing the tab at the other end. Paging onto one selects that tab, so the swipe follows the finger as it does between the other tabs, and a swipe down still closes the sheet. With USDT off, the pager returns to the real page after a wrap, so the next swipe moves to the adjacent tab. The wrap pages are hidden from accessibility, so UI tests and VoiceOver see each tab's buttons once.

  • UsdtReceiveView: the USDT address page passes wrapAround to swipeSegmentedTabs, so a swipe left on USDT selects the first tab instead of doing nothing.

  • swipeSegmentedTabs gains the wrapAround option, off by default, so Settings, Shop and All Activity keep their current swipes.

  • Sheet dismissal: a sideways swipe released above the sheet does not close Receive on iOS, with the swipes in the Preview.

  • Leaving USDT, to Spending or by the wrap to Savings, crossfades rather than slides, because USDT replaces the pager with its own view (as since fix: swipe between receive tabs from the usdt qr #1020). The Android twin slides there.

Out of Scope

Design

N/A — no UI changes.

Preview

iPhone 17 Pro simulator (iOS 26.5), dev wallet with USDT enabled, tabs Savings, Spending and USDT, swipes with AXe; recordings played at 2x.

A tap on the Savings QR still copies.

A swipe right on Savings follows the finger onto the wrap page and selects USDT

ios-a-savings-right2x.mp4

A swipe left on USDT selects Savings

ios-b-usdt-left2x.mp4

A swipe left on Savings selects Spending

ios-c-savings-left2x.mp4

A swipe right on Spending selects Savings

ios-d-spending-right2x.mp4

A swipe left on Spending selects USDT

ios-e-spending-left2x.mp4

A swipe right on USDT selects Spending

ios-f-usdt-right2x.mp4

A swipe left on USDT released above the sheet selects Savings; Receive stays open

ios-h-usdt-left-above2x.mp4

A swipe right on Savings released above the sheet selects USDT; Receive stays open

ios-i-savings-right-above2x.mp4

A swipe down on Savings still closes the sheet

ios-l-savings-swipe-down2x.mp4

A swipe down on USDT still closes the sheet

ios-m-usdt-swipe-down2x.mp4

QA Notes

Journeys

  • new receive-tabs-wraparound.xml — swipes wrap from Savings to USDT and back, a swipe released above the sheet keeps Receive open, a tap on the QR still copies, and with USDT off Savings and Spending wrap both ways
  • updated usdt-receive-swipe-tabs.xml — a swipe left on USDT selects Savings instead of staying on USDT

Manual Tests

N/A

Automated Checks

N/A

@ovitrif ovitrif self-assigned this Oct 19, 2026
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-19T19:07:30.192231Z ab6d110 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@ovitrif
ovitrif requested review from a team, coreyphillips and pwltr and removed request for a team October 19, 2026 19:07
@greptile-apps

greptile-apps Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 4/5

[Low impact] The change is limited to navigation within the Receive workflow.

The PR appears safe to merge, with non-blocking improvements to the USDT wrap preview and journey coverage.

Findings

  1. P2 USDT preview is blank ▶
  2. P2 Wrap reset lacks coverage ▶

Summary

Adds wraparound swipes to Receive.

  • Receive tabs now loop when you swipe past either end.

Two non-blocking improvements remain: show the USDT content during the wrap drag, and cover repeated wrapping with USDT disabled. No runtime UI checks were run; this Linux environment cannot launch the iOS app.

ovitrif explicitly identified downward swipes not dismissing Receive on USDT as an existing issue from PR #1020 and excluded it from this PR.

Reviews (1) · Last reviewed commit: "fix: wrap receive tab swipes around" · Reviewed by Greptile

Comment thread Bitkit/Views/Wallets/Receive/ReceiveQr.swift
Comment thread journeys/receive/receive-tabs-wraparound.xml Outdated

@talosmachina talosmachina left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No findings. Adds wrap pages before the first and after the last tab of the Receive TabView, and wrapAround for the USDT page swipe. Reviewed 6fbf185, full tier, reasoned from the code and CI (iOS does not build on this side).

What I checked, and 4 candidates I ruled out

Read in full: ReceiveQr.swift, View+SwipeGestures.swift, the UsdtReceiveView.swift change, both journeys
Call sites traced: swipeSegmentedTabs (Settings, Shop, All Activity keep the default wrapAround: false), selectedTabBinding

Ruled out

  • Duplicate tags: the wrap pages are tagged .wrapToLast/.wrapToFirst, distinct from every .tab(_), so the TabView never sees two pages with the same tag.
  • Selection stuck on a wrap page: the getter always returns .tab(selectedTab), so after the setter maps a wrap page to the end tab the pager snaps to the real page.
  • Wrap page duplicates side effects: tabContent side effects (hardware load, detail reset) hang off onChange(of: selectedTab) in the parent, not off the page, so the extra page does not trigger them twice.
  • USDT as the wrap target: wrapPage renders Color.clear for USDT, matching the existing USDT placeholder page.

Merge confidence: 4/5, no findings, but the swipe behaviour has no automated test and Run Tests was still running when this was written.

@ovitrif

ovitrif commented Oct 19, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed f9085b4, answering the review threads:

  • receive-tabs-wraparound.xml now turns USDT off and wraps Savings and Spending both ways, each wrap followed by a swipe to the adjacent tab. On an iPhone 17 simulator (iOS 27), two rounds of that sequence each landed on the expected tab.
  • The journey's precondition now requires no open Spending channel, since a usable channel adds the Auto tab.

The empty USDT page while swiping predates this PR and is tracked in #1029.

@jvsena42 jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code scan: no findings.

Checked and clean:

  • No hidden tabs. The pager setter maps .wrapToLast and .wrapToFirst through availableTabItems, and the getter always returns .tab(selectedTab).
  • Content follows the tab. The wrap pages render tabContent(for:) of the tab at the other end, so the QR, copy and edit values match the tab that gets selected.
  • USDT page. It wraps only on .address.

Worth checking on the e2e run once this targets master, not a finding: when USDT is off and the last tab has a QR, the wrap page puts a second SpecifyInvoiceButton and ReceiveCopyQR ahead of the real first page in the view order. Several e2e specs tap('SpecifyInvoiceButton'), so if XCUITest takes the first match it could edit the off-screen tab's invoice. All iOS e2e jobs were skipped on this stacked PR.

Device gate (partial): not run yet. Pending: receive/receive-tabs-wraparound.xml and usdt-receive-swipe-tabs.xml, plus Receive open time with USDT off, since the two wrap pages render full QR content. The device result will follow.

@ovitrif

ovitrif commented Oct 19, 2026

Copy link
Copy Markdown
Collaborator Author

when USDT is off and the last tab has a QR, the wrap page puts a second SpecifyInvoiceButton and ReceiveCopyQR ahead of the real first page in the view order. Several e2e specs tap('SpecifyInvoiceButton'), so if XCUITest takes the first match it could edit the off-screen tab's invoice.

@jvsena42 Pushed 4934e28: both wrap pages are now accessibilityHidden, so XCUITest and VoiceOver see only the real tab pages, and a lookup for SpecifyInvoiceButton or ReceiveCopyQR matches the visible tab alone. The wrap pages still page and select as before.

@ovitrif

ovitrif commented Oct 19, 2026

Copy link
Copy Markdown
Collaborator Author

@jvsena42 thanks. The duplicate SpecifyInvoiceButton and ReceiveCopyQR are covered by 4934e28: the wrap pages are now hidden from accessibility, so an identifier lookup only matches the real tab. The e2e run will confirm it once this targets master.

…o fix/receive-tabs-wraparound

# Conflicts:
#	Bitkit/Extensions/View+SwipeGestures.swift
#	Bitkit/Views/Wallets/UsdtReceiveView.swift
@ovitrif

ovitrif commented Oct 19, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed 02fad0c, merging the base branch's 047370c (swipe down from the USDT QR). The conflict was in swipeSegmentedTabs, which now takes both wrapAround and simultaneous; the USDT page passes both, so it wraps and still lets the sheet close on a swipe down. The Out of Scope bullet about swipe down is gone, since the base now fixes it.

The Run Tests failure on 4934e28 was testPendingWalletSwitchResumesAfterTags in the send flow, which this PR does not touch; this push reruns it.

@jvsena42 jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delta since f9085b4 (4934e28 plus the merge of 047370c): no findings. My e2e note is addressed: non-USDT wrap pages are .accessibilityHidden(true) (ReceiveQr.swift:379), so XCUITest sees only the real tab's SpecifyInvoiceButton and ReceiveCopyQR. pageSelection, wrapPage and tabContent are untouched, so a wrap still lands on the selected tab's content and skips hidden tabs. The merge keeps both wrap branches in swipeSegmentedTabs, and USDT passes wrapAround: true, simultaneous: true, so swipe-down still closes the sheet.

Device gate (partial): not run yet. Pending: receive/receive-tabs-wraparound.xml and receive/usdt-receive-swipe-tabs.xml on the simulator. The Android twin synonymdev/bitkit-android#1593 passed both.

@ovitrif

ovitrif commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed 364756f: the wraparound journey now says the wrap slides on to the other end like a swipe between neighbouring tabs, matching the Android twin. No code change.

I also re-recorded the Preview on 02fad0c with every frame kept: both wraps follow the finger, a swipe released above the sheet keeps Receive open, and a swipe down closes the sheet from Savings and from USDT.

@ovitrif

ovitrif commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed 3fec826: the wraparound journey's two pager steps now swipe in 1200 ms, kept identical to the Android twin (synonymdev/bitkit-android#1608). Journey only, no code change.

…o fix/receive-tabs-wraparound

# Conflicts:
#	Bitkit/Views/Wallets/Receive/ReceiveQr.swift
@ovitrif

ovitrif commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed ae4f35f: merged the updated base (b0ca762, which brings in master) and resolved the conflict in ReceiveQr, keeping the wrap pages in the wrapped if style SwiftFormat 0.64.0 wants. Simulator build with warnings-as-errors passes with no Swift warnings.

@jvsena42 jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code scan at ae4f35f: no issues found. 364756f and 3fec826 are journey text only. The ae4f35f merge re-wraps the USDT if in ReceiveQr and keeps .tag(ReceivePage.tab(.usdt)), both wrap pages, pageSelection, wrapPage and tabContent unchanged.

Notes:

  • The red SwiftFormat check is on ActivityRow.swift:104-105, which this PR doesn't touch. It comes from master and is fixed by #1035.
  • This PR is based on #1020 and has to merge after it.
  • The Android twin synonymdev/bitkit-android#1608 has a tab-then-swipe regression that iOS doesn't have: the TabView selection jumps straight to the page.

Device gate (partial): ae4f35f — not run yet. Pending:

  • receive/receive-tabs-wraparound.xml and receive/usdt-receive-swipe-tabs.xml on the simulator;
  • Receive open time with USDT off, since the two wrap pages render full tab content.

Device results will follow in a separate review.

@ovitrif

ovitrif commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator Author

@jvsena42 thanks, the Android tab-then-swipe issue you noted is fixed in synonymdev/bitkit-android@8488077 on synonymdev/bitkit-android#1608: a tap on the USDT tab now lands the pager on the USDT page, so the next swipe slides one page.

@ovitrif

ovitrif commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed cf51fb2: the wraparound journey gains the two USDT-tab-then-leave steps, kept identical to the Android twin. Journey only.

@jvsena42

Copy link
Copy Markdown
Member

Device gate (partial): ae4f35f on iPhone 17 (simulator), iOS 26.5, regtest wallet with USDT on and tabs Savings, Spending, USDT.

  • receive/receive-tabs-wraparound.xml: 14/14 passed (09:39-09:42 UTC).
    • Every wrap step holds in both directions with USDT on and with USDT off.
    • The diagonal swipes keep the sheet open.
    • QR tap copies bitcoin:bcrt1q6qcd….
  • receive/usdt-receive-swipe-tabs.xml: 7/7 passed (09:43 UTC).
    • The 600 ms Spending → USDT swipe moves.
    • Tap-to-copy on the USDT QR copies the 0x address.
    • Swipes out of USDT neither show the tooltip nor change the pasteboard.

Transitions out of USDT, both to Spending and the wrap to Savings, are crossfades rather than slides because USDT is a separate view. That matches the journey wording.

Still running: Receive open time with USDT off, PR vs master.

@jvsena42

Copy link
Copy Markdown
Member

Device gate (partial 2): ae4f35f vs master cf2b97e on iPhone 17 (simulator), USDT off. Here the two wrap pages render full Spending and Savings content.

Receive open time, tap → Savings QR settled, from simctl recordings:

build runs median
PR 11 606 ms (533-628)
master 6 627 ms (589-871)

The sheet starts moving after a median 490 ms on the PR and 534 ms on master. I measured no regression. The two master outliers (848 and 871 ms) coincide with load from the parallel emulator.

Still running: the two actions cf51fb2 adds to receive-tabs-wraparound.xml (tap USDT, then leave by swipe or tap), on the simulator.

jvsena42
jvsena42 previously approved these changes Oct 20, 2026

@jvsena42 jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving. One LOW on the new journey wording is inline; it can be fixed here or in a follow-up, your choice. Please reply on the thread with which.

Device gate: cf51fb2 — journeys: 23 passed, 0 failed (receive/receive-tabs-wraparound.xml 16/16 including the two cf51fb2 actions, receive/usdt-receive-swipe-tabs.xml 7/7); perf: Receive open, tap → settled, median 606 ms (n=11) vs master 627 ms (n=6), no regression; Figma: n/a (no new UI); l10n: no UI text changed; Android: same journey file on synonymdev/bitkit-android#1608.

The app code is the same as at ae4f35f, which ran the first 21 actions. cf51fb2 only adds the two actions, and I drove those on the same build. Ran on the iPhone 17 simulator with the tabs Savings, Spending and USDT, with USDT on and off.

The red SwiftFormat check is on ActivityRow.swift, which this PR doesn't touch; it's fixed by #1035. Run Tests is still pending. This PR must merge after #1020.

Comment thread journeys/receive/receive-tabs-wraparound.xml Outdated
@ovitrif

ovitrif commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed 912ac74: the two USDT-exit journey checks now read "goes straight from the USDT QR to the Savings QR" and "the same direct change", which hold on both platforms; the journey notes the iOS crossfade. Journey and description only.

@pwltr

pwltr commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

Manual testing confirms that selection wraps, but the transition at the ends is abrupt or unanimated. The device QA above also notes that leaving USDT crossfades rather than slides. Changing the journey wording does not address that interaction.

I recommend using one persistent circular pager for the Receive tabs, with real USDT content in the pager, instead of duplicate end pages plus selection redirection and a separate USDT view replacing the pager.

A feasible native approach is a SwiftUI wrapper around UIPageViewController using its scrolling transition style:

  • Supply the previous/next visible tab through the public data-source methods, wrapping the indices at either end.
  • Update the selected tab after a completed swipe; leave it unchanged if the gesture is cancelled.
  • Handle segmented-control taps through the same pager, with an explicit animation direction.
  • Preserve USDT state and its network/amount/fee navigation, and keep vertical sheet dismissal working.

This uses the pager's supported neighbour-navigation API rather than private APIs or duplicate-page reset timing. Apple documents the data-source mechanism here: https://developer.apple.com/library/archive/documentation/WindowsViews/Conceptual/ViewControllerCatalog/Chapters/PageViewControllers.html

It would also address the architectural cause of the blank USDT preview tracked in #1029. This is a recommendation, not a claim that a replacement has been implemented or device-verified. Verification should cover repeated wraps in both directions, cancelled/slow swipes, tab-tap followed by swipe, USDT on/off, Auto/Hardware configurations, QR copy/edit, accessibility, and swipe-down dismissal.

@ovitrif

ovitrif commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator Author

Manual testing confirms that selection wraps, but the transition at the ends is abrupt or unanimated.

@pwltr thanks. Out of scope: tracked in #1038 (3.2.0), because a circular UIPageViewController pager with USDT inside it replaces how ReceiveQr swaps in UsdtReceiveView, which is larger than this PR's wraparound; the issue carries your approach and test list, and it also covers the blank USDT preview in #1029.

On the ends: my simulator recordings in the Preview show the wrap page following the finger and sliding on, with the swap to the real page after it settles. Which steps or device showed the abrupt change? If it is the end tabs with USDT off on a device, I'd like to look at that here.

@ovitrif

ovitrif commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator Author

Retargeted to master before #1020 merges, so deleting its branch does not close this PR. Until #1020 lands, the diff also shows its commits.

@ovitrif
ovitrif changed the base branch from fix/usdt-receive-swipe-tabs to master October 20, 2026 11:28
@ovitrif
ovitrif dismissed jvsena42’s stale review October 20, 2026 11:28

The base branch was changed.

@ovitrif

ovitrif commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator Author

Retargeted to master ahead of #1020 merging, so deleting its branch does not close this PR. Until #1020 lands, the diff also shows its commits. The base change cleared the approval; I will ask for a fresh one once #1020 is in.

jvsena42
jvsena42 previously approved these changes Oct 20, 2026

@jvsena42 jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving again. 912ac74 changes only journey text, and my LOW is fixed.

Device gate: 912ac74 (app code ae4f35f) — journeys: 23 passed, 0 failed (receive/receive-tabs-wraparound.xml 16/16, receive/usdt-receive-swipe-tabs.xml 7/7); perf: Receive open, tap → settled, median 606 ms vs master 627 ms; Figma: n/a; l10n: no UI text changed; Android: same journey on synonymdev/bitkit-android#1608.

The red SwiftFormat check is on ActivityRow.swift:104, which is the same on master (#1035 closed without merging) and isn't touched here. It needs a fix on master before merge.

@ovitrif
ovitrif dismissed jvsena42’s stale review October 20, 2026 13:29

The merge-base changed after approval.

@ovitrif
ovitrif requested a review from jvsena42 October 20, 2026 13:33
pwltr
pwltr previously approved these changes Oct 20, 2026
@ovitrif
ovitrif dismissed pwltr’s stale review October 20, 2026 15:09

The merge-base changed after approval.

@ovitrif

ovitrif commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator Author

@jvsena42 I merged master in at 0e39441 so this PR runs the now-required SwiftFormat check against master with #1040 in. This PR's own files are unchanged since your approval on 912ac74. GitHub dismissed that approval when #1020 merged and changed the merge base, so this needs a fresh approval.

@jvsena42 jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving again. The new head 12f6356 is a master merge (#923's CI move) with no conflict resolution, so my earlier review and device gate stand: 912ac74 (app code ae4f35f), 23/23 journey actions passed, Receive open median 606 ms against master 627 ms. CI is re-running.

@ovitrif
ovitrif enabled auto-merge October 20, 2026 21:52
@ovitrif
ovitrif merged commit 1b6250e into master Oct 21, 2026
21 of 23 checks passed
@ovitrif
ovitrif deleted the fix/receive-tabs-wraparound branch October 21, 2026 00:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: receive tabs don't wrap on swipe, and swiping past usdt dismisses receive

4 participants