From 16478489ad3a73e39957df8094911e8d54907892 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Mon, 19 Oct 2026 20:21:35 +0200 Subject: [PATCH 1/4] fix: wrap receive tab swipes around --- .../wallets/receive/ReceiveQrScreen.kt | 6 ++ .../wallets/usdt/UsdtDepositReceiveScreen.kt | 2 +- .../modifiers/SwipeToChangeTabModifier.kt | 65 +++++++++++++++++++ 3 files changed, 72 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveQrScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveQrScreen.kt index 366bc70746..0cd6670942 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveQrScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveQrScreen.kt @@ -88,6 +88,7 @@ import to.bitkit.ui.screens.wallets.activity.components.CustomTabRowWithSpacing import to.bitkit.ui.screens.wallets.usdt.UsdtDepositReceiveScreen import to.bitkit.ui.shared.effects.SetMaxBrightness import to.bitkit.ui.shared.modifiers.sheetHeight +import to.bitkit.ui.shared.modifiers.swipePastEdges import to.bitkit.ui.shared.util.gradientBackground import to.bitkit.ui.shared.util.shareQrCode import to.bitkit.ui.shared.util.shareText @@ -387,6 +388,11 @@ fun ReceiveQrScreen( modifier = Modifier .weight(1f) .fillMaxWidth() + .swipePastEdges( + state = lazyListState, + onSwipePastStart = { selectReceiveTab(visibleTabs.last()) }, + onSwipePastEnd = { selectReceiveTab(visibleTabs.first()) }, + ) ) { itemsIndexed( items = visibleTabs, diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/usdt/UsdtDepositReceiveScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/usdt/UsdtDepositReceiveScreen.kt index bff9afae02..f1f8cbec3f 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/usdt/UsdtDepositReceiveScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/usdt/UsdtDepositReceiveScreen.kt @@ -142,7 +142,7 @@ internal fun ColumnScope.UsdtDepositReceiveScreen( .fillMaxWidth() .then( if (page == ReceivePage.ADDRESS) { - Modifier.swipeToChangeTab(tabIndex, tabs.size, { onSelectTab(tabs[it]) }) + Modifier.swipeToChangeTab(tabIndex, tabs.size, { onSelectTab(tabs[it]) }, wrapAround = true) } else { Modifier } diff --git a/app/src/main/java/to/bitkit/ui/shared/modifiers/SwipeToChangeTabModifier.kt b/app/src/main/java/to/bitkit/ui/shared/modifiers/SwipeToChangeTabModifier.kt index 82c6bd1c82..ebd8cbb403 100644 --- a/app/src/main/java/to/bitkit/ui/shared/modifiers/SwipeToChangeTabModifier.kt +++ b/app/src/main/java/to/bitkit/ui/shared/modifiers/SwipeToChangeTabModifier.kt @@ -1,12 +1,17 @@ package to.bitkit.ui.shared.modifiers +import androidx.compose.foundation.gestures.ScrollableState +import androidx.compose.foundation.gestures.awaitEachGesture +import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.gestures.detectHorizontalDragGestures import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.composed +import androidx.compose.ui.input.pointer.PointerEventPass import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.util.VelocityTracker import androidx.compose.ui.platform.LocalDensity @@ -24,6 +29,7 @@ import kotlin.math.abs * @param currentTabIndex The currently selected tab index (0-based) * @param tabCount Total number of tabs available for navigation * @param onTabChange Callback invoked when user swipes to change tabs, receives the new tab index + * @param wrapAround Whether a swipe past the first or last tab selects the tab at the other end * @param velocityThreshold Velocity threshold in px/s (default: 600f) * @param distanceThreshold Distance threshold in dp (default: 50.dp) */ @@ -31,6 +37,7 @@ fun Modifier.swipeToChangeTab( currentTabIndex: Int, tabCount: Int, onTabChange: (Int) -> Unit, + wrapAround: Boolean = false, velocityThreshold: Float = DEFAULT_VELOCITY_THRESHOLD, distanceThreshold: Float = DEFAULT_DISTANCE_THRESHOLD_DP, ): Modifier = composed { @@ -58,9 +65,13 @@ fun Modifier.swipeToChangeTab( (velocity > 0 || dragDistance > 0) && currentTabIndex > 0 -> onTabChange(currentTabIndex - 1) + (velocity > 0 || dragDistance > 0) && wrapAround -> onTabChange(tabCount - 1) + // Swipe left (next tab) - negative velocity/drag (velocity < 0 || dragDistance < 0) && currentTabIndex < tabCount - 1 -> onTabChange(currentTabIndex + 1) + + (velocity < 0 || dragDistance < 0) && wrapAround -> onTabChange(0) } } @@ -75,6 +86,60 @@ fun Modifier.swipeToChangeTab( } } +/** + * Reports a horizontal swipe that pushes past the first or last page of a scrollable pager, so the pager can wrap + * around. It only observes the pointer: the pager keeps handling and consuming its own drag. + * + * @param state The pager's scroll state, read when the swipe starts + * @param onSwipePastStart Callback invoked for a swipe to the right on the first page + * @param onSwipePastEnd Callback invoked for a swipe to the left on the last page + * @param velocityThreshold Velocity threshold in px/s (default: 600f) + * @param distanceThreshold Distance threshold in dp (default: 50.dp) + */ +fun Modifier.swipePastEdges( + state: ScrollableState, + onSwipePastStart: () -> Unit, + onSwipePastEnd: () -> Unit, + velocityThreshold: Float = DEFAULT_VELOCITY_THRESHOLD, + distanceThreshold: Float = DEFAULT_DISTANCE_THRESHOLD_DP, +): Modifier = composed { + val currentOnSwipePastStart by rememberUpdatedState(onSwipePastStart) + val currentOnSwipePastEnd by rememberUpdatedState(onSwipePastEnd) + val distanceThresholdPx = with(LocalDensity.current) { distanceThreshold.dp.toPx() } + + pointerInput(state) { + awaitEachGesture { + val down = awaitFirstDown(requireUnconsumed = false, pass = PointerEventPass.Initial) + val isAtStart = !state.canScrollBackward + val isAtEnd = !state.canScrollForward + if (!isAtStart && !isAtEnd) return@awaitEachGesture + + val velocityTracker = VelocityTracker() + velocityTracker.addPosition(down.uptimeMillis, down.position) + var position = down.position + while (true) { + val change = awaitPointerEvent(PointerEventPass.Final).changes + .firstOrNull { it.id == down.id } ?: return@awaitEachGesture + velocityTracker.addPosition(change.uptimeMillis, change.position) + position = change.position + if (!change.pressed) break + } + + val dragX = position.x - down.position.x + val dragY = position.y - down.position.y + val velocity = velocityTracker.calculateVelocity().x + val isSwipe = abs(dragX) > abs(dragY) && + (abs(velocity) >= velocityThreshold || abs(dragX) >= distanceThresholdPx) + if (!isSwipe) return@awaitEachGesture + + when { + dragX > 0 && isAtStart -> currentOnSwipePastStart() + dragX < 0 && isAtEnd -> currentOnSwipePastEnd() + } + } + } +} + // Reduced from 1500 to 600 for better iOS-like sensitivity private const val DEFAULT_VELOCITY_THRESHOLD = 600f From 9f96dd40144bd31c407c34d564e2eb09abccea42 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Mon, 19 Oct 2026 21:01:01 +0200 Subject: [PATCH 2/4] test: add receive tabs wraparound journey --- changelog.d/next/1590.fixed.md | 1 + journeys/README.md | 1 + journeys/receive/receive-tabs-wraparound.xml | 27 ++++++++++++++++++++ journeys/receive/usdt-receive-swipe-tabs.xml | 7 ++--- 4 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 changelog.d/next/1590.fixed.md create mode 100644 journeys/receive/receive-tabs-wraparound.xml diff --git a/changelog.d/next/1590.fixed.md b/changelog.d/next/1590.fixed.md new file mode 100644 index 0000000000..1523bc815e --- /dev/null +++ b/changelog.d/next/1590.fixed.md @@ -0,0 +1 @@ +Swiping past the first or last Receive tab now wraps around to the tab at the other end. diff --git a/journeys/README.md b/journeys/README.md index 79e69fd2aa..d27283ed22 100644 --- a/journeys/README.md +++ b/journeys/README.md @@ -185,6 +185,7 @@ Known differences in the corpus, as of the iOS port (synonymdev/bitkit-ios#691): | `amount-limits/transfer-spending-preset-delete.xml` | not ported yet — the same fix shipped in synonymdev/bitkit-ios#289, so this one should port | | `app-update/critical-update-onboarding.xml` | not ported yet — iOS already blocks at the top level in `AppScene`, so the journey applies there once written | | `home/pull-to-refresh-rates.xml` | not ported — iOS does not refresh exchange rates on pull to refresh | +| `receive/receive-tabs-wraparound.xml` | same file and steps — on Android a swipe that starts at the screen edge is the system back gesture, which closes the sheet on every tab; start the swipes away from the edge | | `receive/receive-auto-tab-selection.xml` | not ported — the Auto tab override fix is Android-only so far; iOS parity not checked | | `security/pin-lock-on-resume.xml` | not ported yet — iOS already clears the PIN verification on entering the background, so the journey applies; it lands with the iOS side of synonymdev/bitkit-android#1298 | | `security/pin-result-long-label.xml` | not ported — the toggle exists on the iOS security success screen, but the overlap check is a follow-up | diff --git a/journeys/receive/receive-tabs-wraparound.xml b/journeys/receive/receive-tabs-wraparound.xml new file mode 100644 index 0000000000..c9abe13338 --- /dev/null +++ b/journeys/receive/receive-tabs-wraparound.xml @@ -0,0 +1,27 @@ + + + On the Receive sheet, tab swipes wrap around: a swipe to the right on the first tab selects the + last tab, and a swipe to the left on the last tab selects the first. A sideways swipe never closes + the Receive sheet, also when it is released above the sheet, and a tap on a QR still copies it. + + Precondition: onboarded dev wallet with USDT enabled (introductory sheet or Settings, General, + Payments), no CJIT invoice pending and no hardware wallet paired, so the tabs are Savings, + Spending and USDT. Start on the wallet home screen. The USDT address loads without funding; wait + for the QR before swiping. + + Swipe with `adb shell input swipe` across the middle of the QR, about 60% of its width, in + 300 ms (`adb shell input swipe 220 1176 860 1176 300` to the right on a 1080 px wide screen). A + swipe that starts at the screen edge is the system back gesture, which closes the sheet on every + tab; start the swipes at least 100 px from the edge. + + + Tap Receive (testTag "Receive") and verify the Savings tab (testTag "Tab-savings") is selected + Swipe right across the Savings QR; verify the USDT tab (testTag "Tab-usdt") is selected and wait until the USDT QR is visible + Swipe left across the USDT QR; verify the Savings tab is selected + Swipe left across the Savings QR in 600 ms (`adb shell input swipe 860 1176 220 1176 600`); verify the Spending tab (testTag "Tab-spending") is selected + Swipe left across the Spending content in 600 ms; verify the USDT tab is selected and wait until its QR is visible + Swipe left from the top right of the USDT QR to above the sheet (`adb shell input swipe 950 720 100 200 300`); verify the Receive sheet is still open and the Savings tab is selected + Swipe right from the top left of the Savings QR to above the sheet (`adb shell input swipe 100 720 950 200 300`); verify the Receive sheet is still open and the USDT tab is selected + Swipe left across the USDT QR, then tap the Savings QR once; verify the "copied" tooltip appears + + diff --git a/journeys/receive/usdt-receive-swipe-tabs.xml b/journeys/receive/usdt-receive-swipe-tabs.xml index 95683d4fc5..fea00c104a 100644 --- a/journeys/receive/usdt-receive-swipe-tabs.xml +++ b/journeys/receive/usdt-receive-swipe-tabs.xml @@ -2,8 +2,9 @@ On the Receive sheet, swiping sideways over the QR changes tabs on every tab, USDT included. With USDT selected, a swipe to the right over its QR selects the tab before it (Spending) and - does not copy the address; a swipe to the left does nothing, since USDT is the last tab. A single - tap on the USDT QR still copies the address and shows the "copied" tooltip. + does not copy the address; a swipe to the left wraps around to the first tab (Savings), since + USDT is the last tab. A single tap on the USDT QR still copies the address and shows the "copied" + tooltip. Precondition: onboarded dev wallet with USDT enabled (introductory sheet or Settings, General, Payments), no CJIT invoice pending, start on the wallet home screen. The USDT address loads @@ -21,7 +22,7 @@ Swipe right across the QR and take a screenshot right away Verify the Spending tab (testTag "Tab-spending") is selected, the Lightning receive content or the Spending setup is shown, and the screenshot shows no "copied" tooltip Swipe left across the middle of the Spending content, whether it shows a Lightning QR or the Spending setup, in 600 ms (`adb shell input swipe 900 1200 150 1200 600`); verify the USDT tab is selected and wait until its QR is visible - Swipe left across the USDT QR and take a screenshot right away; verify the USDT tab stays selected and the screenshot shows no "copied" tooltip Tap the USDT QR once and verify the "copied" tooltip appears + Wait until the tooltip is gone, swipe left across the USDT QR and take a screenshot right away; verify the Savings tab (testTag "Tab-savings") is selected and the screenshot shows no "copied" tooltip From 9f94e09f3ce71616d4f219048231ad920b468586 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Mon, 19 Oct 2026 21:05:24 +0200 Subject: [PATCH 3/4] chore: rename changelog fragment --- changelog.d/next/{1590.fixed.md => 1593.fixed.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/next/{1590.fixed.md => 1593.fixed.md} (100%) diff --git a/changelog.d/next/1590.fixed.md b/changelog.d/next/1593.fixed.md similarity index 100% rename from changelog.d/next/1590.fixed.md rename to changelog.d/next/1593.fixed.md From a64980a00e8edf16345441f8ba539de341c825fc Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Mon, 19 Oct 2026 21:22:05 +0200 Subject: [PATCH 4/4] fix: honor rtl and touch slop in receive edge swipes --- .../modifiers/SwipeToChangeTabModifier.kt | 81 ++++++++++--------- journeys/receive/receive-tabs-wraparound.xml | 12 ++- 2 files changed, 54 insertions(+), 39 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/shared/modifiers/SwipeToChangeTabModifier.kt b/app/src/main/java/to/bitkit/ui/shared/modifiers/SwipeToChangeTabModifier.kt index ebd8cbb403..1ff4571e87 100644 --- a/app/src/main/java/to/bitkit/ui/shared/modifiers/SwipeToChangeTabModifier.kt +++ b/app/src/main/java/to/bitkit/ui/shared/modifiers/SwipeToChangeTabModifier.kt @@ -11,10 +11,14 @@ import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.composed +import androidx.compose.ui.input.pointer.AwaitPointerEventScope import androidx.compose.ui.input.pointer.PointerEventPass +import androidx.compose.ui.input.pointer.PointerInputChange import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.util.VelocityTracker import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import kotlin.math.abs @@ -30,20 +34,16 @@ import kotlin.math.abs * @param tabCount Total number of tabs available for navigation * @param onTabChange Callback invoked when user swipes to change tabs, receives the new tab index * @param wrapAround Whether a swipe past the first or last tab selects the tab at the other end - * @param velocityThreshold Velocity threshold in px/s (default: 600f) - * @param distanceThreshold Distance threshold in dp (default: 50.dp) */ fun Modifier.swipeToChangeTab( currentTabIndex: Int, tabCount: Int, onTabChange: (Int) -> Unit, wrapAround: Boolean = false, - velocityThreshold: Float = DEFAULT_VELOCITY_THRESHOLD, - distanceThreshold: Float = DEFAULT_DISTANCE_THRESHOLD_DP, ): Modifier = composed { val velocityTracker = remember { VelocityTracker() } var totalDragDistance by remember { mutableFloatStateOf(0f) } - val distanceThresholdPx = with(LocalDensity.current) { distanceThreshold.dp.toPx() } + val distanceThresholdPx = with(LocalDensity.current) { DEFAULT_DISTANCE_THRESHOLD_DP.dp.toPx() } pointerInput(currentTabIndex) { detectHorizontalDragGestures( @@ -56,23 +56,17 @@ fun Modifier.swipeToChangeTab( val dragDistance = totalDragDistance // Check if either velocity OR distance threshold is met - val shouldNavigate = abs(velocity) >= velocityThreshold || + val shouldNavigate = abs(velocity) >= DEFAULT_VELOCITY_THRESHOLD || abs(dragDistance) >= distanceThresholdPx if (shouldNavigate) { - when { + swipeTargetIndex( // Swipe right (previous tab) - positive velocity/drag - (velocity > 0 || dragDistance > 0) && currentTabIndex > 0 -> - onTabChange(currentTabIndex - 1) - - (velocity > 0 || dragDistance > 0) && wrapAround -> onTabChange(tabCount - 1) - - // Swipe left (next tab) - negative velocity/drag - (velocity < 0 || dragDistance < 0) && currentTabIndex < tabCount - 1 -> - onTabChange(currentTabIndex + 1) - - (velocity < 0 || dragDistance < 0) && wrapAround -> onTabChange(0) - } + isPrevious = velocity > 0 || dragDistance > 0, + currentTabIndex = currentTabIndex, + tabCount = tabCount, + wrapAround = wrapAround, + )?.let(onTabChange) } velocityTracker.resetTracking() @@ -86,13 +80,22 @@ fun Modifier.swipeToChangeTab( } } +private fun swipeTargetIndex(isPrevious: Boolean, currentTabIndex: Int, tabCount: Int, wrapAround: Boolean): Int? = + when { + isPrevious && currentTabIndex > 0 -> currentTabIndex - 1 + isPrevious && wrapAround -> tabCount - 1 + !isPrevious && currentTabIndex < tabCount - 1 -> currentTabIndex + 1 + !isPrevious && wrapAround -> 0 + else -> null + } + /** * Reports a horizontal swipe that pushes past the first or last page of a scrollable pager, so the pager can wrap * around. It only observes the pointer: the pager keeps handling and consuming its own drag. * * @param state The pager's scroll state, read when the swipe starts - * @param onSwipePastStart Callback invoked for a swipe to the right on the first page - * @param onSwipePastEnd Callback invoked for a swipe to the left on the last page + * @param onSwipePastStart Callback invoked for a swipe toward the start on the first page (to the right in LTR) + * @param onSwipePastEnd Callback invoked for a swipe toward the end on the last page (to the left in LTR) * @param velocityThreshold Velocity threshold in px/s (default: 600f) * @param distanceThreshold Distance threshold in dp (default: 50.dp) */ @@ -106,8 +109,9 @@ fun Modifier.swipePastEdges( val currentOnSwipePastStart by rememberUpdatedState(onSwipePastStart) val currentOnSwipePastEnd by rememberUpdatedState(onSwipePastEnd) val distanceThresholdPx = with(LocalDensity.current) { distanceThreshold.dp.toPx() } + val layoutDirection = LocalLayoutDirection.current - pointerInput(state) { + pointerInput(state, layoutDirection) { awaitEachGesture { val down = awaitFirstDown(requireUnconsumed = false, pass = PointerEventPass.Initial) val isAtStart = !state.canScrollBackward @@ -116,30 +120,35 @@ fun Modifier.swipePastEdges( val velocityTracker = VelocityTracker() velocityTracker.addPosition(down.uptimeMillis, down.position) - var position = down.position - while (true) { - val change = awaitPointerEvent(PointerEventPass.Final).changes - .firstOrNull { it.id == down.id } ?: return@awaitEachGesture - velocityTracker.addPosition(change.uptimeMillis, change.position) - position = change.position - if (!change.pressed) break - } + val up = awaitRelease(down, velocityTracker) ?: return@awaitEachGesture - val dragX = position.x - down.position.x - val dragY = position.y - down.position.y - val velocity = velocityTracker.calculateVelocity().x - val isSwipe = abs(dragX) > abs(dragY) && - (abs(velocity) >= velocityThreshold || abs(dragX) >= distanceThresholdPx) + val drag = up.position - down.position + val isSwipe = abs(drag.x) > viewConfiguration.touchSlop && + abs(drag.x) > abs(drag.y) && + (abs(velocityTracker.calculateVelocity().x) >= velocityThreshold || abs(drag.x) >= distanceThresholdPx) if (!isSwipe) return@awaitEachGesture + // LazyRow scrolls the other way in RTL, so a swipe toward its start moves the finger left + val towardStart = if (layoutDirection == LayoutDirection.Rtl) drag.x < 0 else drag.x > 0 when { - dragX > 0 && isAtStart -> currentOnSwipePastStart() - dragX < 0 && isAtEnd -> currentOnSwipePastEnd() + towardStart && isAtStart -> currentOnSwipePastStart() + !towardStart && isAtEnd -> currentOnSwipePastEnd() } } } } +private suspend fun AwaitPointerEventScope.awaitRelease( + down: PointerInputChange, + velocityTracker: VelocityTracker, +): PointerInputChange? { + while (true) { + val change = awaitPointerEvent(PointerEventPass.Final).changes.firstOrNull { it.id == down.id } ?: return null + velocityTracker.addPosition(change.uptimeMillis, change.position) + if (!change.pressed) return change + } +} + // Reduced from 1500 to 600 for better iOS-like sensitivity private const val DEFAULT_VELOCITY_THRESHOLD = 600f diff --git a/journeys/receive/receive-tabs-wraparound.xml b/journeys/receive/receive-tabs-wraparound.xml index c9abe13338..1fabf103d6 100644 --- a/journeys/receive/receive-tabs-wraparound.xml +++ b/journeys/receive/receive-tabs-wraparound.xml @@ -5,9 +5,9 @@ the Receive sheet, also when it is released above the sheet, and a tap on a QR still copies it. Precondition: onboarded dev wallet with USDT enabled (introductory sheet or Settings, General, - Payments), no CJIT invoice pending and no hardware wallet paired, so the tabs are Savings, - Spending and USDT. Start on the wallet home screen. The USDT address loads without funding; wait - for the QR before swiping. + Payments), no open Spending channel, no CJIT invoice pending and no hardware wallet paired, so the + tabs are Savings, Spending and USDT; a usable channel adds the Auto tab. Start on the wallet home + screen. The USDT address loads without funding; wait for the QR before swiping. Swipe with `adb shell input swipe` across the middle of the QR, about 60% of its width, in 300 ms (`adb shell input swipe 220 1176 860 1176 300` to the right on a 1080 px wide screen). A @@ -23,5 +23,11 @@ Swipe left from the top right of the USDT QR to above the sheet (`adb shell input swipe 950 720 100 200 300`); verify the Receive sheet is still open and the Savings tab is selected Swipe right from the top left of the Savings QR to above the sheet (`adb shell input swipe 100 720 950 200 300`); verify the Receive sheet is still open and the USDT tab is selected Swipe left across the USDT QR, then tap the Savings QR once; verify the "copied" tooltip appears + Close Receive, turn USDT off (Settings, General, Payments), return to the wallet home screen and tap Receive; verify the Savings tab is selected and only Savings and Spending show + Swipe right across the Savings QR; verify the Spending tab is selected + Swipe right across the Spending content; verify the Savings tab is selected + Swipe left across the Savings QR; verify the Spending tab is selected + Swipe left across the Spending content; verify the Savings tab is selected + Swipe left across the Savings QR; verify the Spending tab is selected