From ae4ed5f280328cf22ddfcc9c208a9d3eb616dcd8 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Wed, 16 Sep 2026 15:39:12 -0300 Subject: [PATCH 1/7] fix: return home after transfer to savings Co-Authored-By: Claude Opus 5 (1M context) --- app/src/main/java/to/bitkit/ui/ContentView.kt | 4 +-- .../test/java/to/bitkit/ui/ContentViewTest.kt | 32 +++++++++++++++++++ changelog.d/next/809.fixed.md | 1 + 3 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 changelog.d/next/809.fixed.md diff --git a/app/src/main/java/to/bitkit/ui/ContentView.kt b/app/src/main/java/to/bitkit/ui/ContentView.kt index b2f5710c4f..7a90666bc9 100644 --- a/app/src/main/java/to/bitkit/ui/ContentView.kt +++ b/app/src/main/java/to/bitkit/ui/ContentView.kt @@ -942,8 +942,8 @@ private fun RootNavHost( app = appViewModel, wallet = walletViewModel, transfer = transferViewModel, - onContinueClick = { navController.popBackStack(inclusive = true) }, - onTransferUnavailable = { navController.popBackStack(inclusive = true) }, + onContinueClick = { navController.navigateToHome() }, + onTransferUnavailable = { navController.navigateToHome() }, ) } deepLinkableComposable { diff --git a/app/src/test/java/to/bitkit/ui/ContentViewTest.kt b/app/src/test/java/to/bitkit/ui/ContentViewTest.kt index 367b06b885..8c18189a3b 100644 --- a/app/src/test/java/to/bitkit/ui/ContentViewTest.kt +++ b/app/src/test/java/to/bitkit/ui/ContentViewTest.kt @@ -1,5 +1,13 @@ package to.bitkit.ui +import android.content.Context +import androidx.navigation.NavDestination.Companion.hasRoute +import androidx.navigation.NavHostController +import androidx.navigation.compose.ComposeNavigator +import androidx.navigation.compose.composable +import androidx.navigation.createGraph +import androidx.navigation.navigation +import androidx.test.core.app.ApplicationProvider import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner @@ -88,4 +96,28 @@ class ContentViewTest { assertEquals(receiveSheetPresentationKey(sheet), receiveSheetPresentationKey(samePresentation)) assertFalse(receiveSheetPresentationKey(sheet) == receiveSheetPresentationKey(nextPresentation)) } + + @Test + fun `savings transfer completion returns home and drops spending from back stack`() { + val navController = NavHostController(ApplicationProvider.getApplicationContext()).apply { + navigatorProvider.addNavigator(ComposeNavigator()) + graph = createGraph(startDestination = Routes.Home) { + composable {} + composable {} + navigation(startDestination = Routes.SavingsAvailability) { + composable {} + composable {} + } + } + } + navController.navigateTo(Routes.Spending) + navController.navigateToTransferSavingsAvailability() + navController.navigateTo(Routes.SavingsProgress) + + navController.navigateToHome() + + assertTrue(navController.currentDestination?.hasRoute() == true) + assertFalse(navController.currentBackStack.value.any { it.destination.hasRoute() }) + assertFalse(navController.currentBackStack.value.any { it.destination.hasRoute() }) + } } diff --git a/changelog.d/next/809.fixed.md b/changelog.d/next/809.fixed.md new file mode 100644 index 0000000000..2513889644 --- /dev/null +++ b/changelog.d/next/809.fixed.md @@ -0,0 +1 @@ +Fixed the OK button after moving funds from spending to savings returning to the spending wallet instead of the home screen. From 6cd61512e97df2a9991a746526731de52943768e Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Wed, 16 Sep 2026 15:48:00 -0300 Subject: [PATCH 2/7] test: guard savings transfer exit route Co-Authored-By: Claude Opus 5 (1M context) --- app/src/main/java/to/bitkit/ui/ContentView.kt | 6 ++++-- app/src/test/java/to/bitkit/ui/ContentViewTest.kt | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/ContentView.kt b/app/src/main/java/to/bitkit/ui/ContentView.kt index 7a90666bc9..73a64f2852 100644 --- a/app/src/main/java/to/bitkit/ui/ContentView.kt +++ b/app/src/main/java/to/bitkit/ui/ContentView.kt @@ -942,8 +942,8 @@ private fun RootNavHost( app = appViewModel, wallet = walletViewModel, transfer = transferViewModel, - onContinueClick = { navController.navigateToHome() }, - onTransferUnavailable = { navController.navigateToHome() }, + onContinueClick = { navController.navigateOnSavingsTransferExit() }, + onTransferUnavailable = { navController.navigateOnSavingsTransferExit() }, ) } deepLinkableComposable { @@ -2079,6 +2079,8 @@ fun NavController.navigateToTransferSavingsIntro() = navigateTo(Routes.SavingsIn fun NavController.navigateToTransferSavingsAvailability() = navigateTo(Routes.SavingsAvailability) +fun NavController.navigateOnSavingsTransferExit() = navigateToHome() + fun NavController.navigateToTransferSpendingStart(hasSeenSpendingIntro: Boolean) = navigateTo(transferSpendingStartRoute(hasSeenSpendingIntro)) diff --git a/app/src/test/java/to/bitkit/ui/ContentViewTest.kt b/app/src/test/java/to/bitkit/ui/ContentViewTest.kt index 8c18189a3b..1dbb4f6d08 100644 --- a/app/src/test/java/to/bitkit/ui/ContentViewTest.kt +++ b/app/src/test/java/to/bitkit/ui/ContentViewTest.kt @@ -114,7 +114,7 @@ class ContentViewTest { navController.navigateToTransferSavingsAvailability() navController.navigateTo(Routes.SavingsProgress) - navController.navigateToHome() + navController.navigateOnSavingsTransferExit() assertTrue(navController.currentDestination?.hasRoute() == true) assertFalse(navController.currentBackStack.value.any { it.destination.hasRoute() }) From 63aad7a5044d070707f687c5c207e84ef8a730dd Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Wed, 16 Sep 2026 17:22:12 -0300 Subject: [PATCH 3/7] chore: reword savings transfer changelog fragment Co-Authored-By: Claude Opus 5 (1M context) --- changelog.d/next/809.fixed.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.d/next/809.fixed.md b/changelog.d/next/809.fixed.md index 2513889644..5440f616a4 100644 --- a/changelog.d/next/809.fixed.md +++ b/changelog.d/next/809.fixed.md @@ -1 +1 @@ -Fixed the OK button after moving funds from spending to savings returning to the spending wallet instead of the home screen. +Tapping OK after moving funds from spending to savings now returns to the home screen instead of the spending wallet. From b0207069ff0bcc8b0be7e0a7179214fbf5b02f3d Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Thu, 17 Sep 2026 18:03:41 -0300 Subject: [PATCH 4/7] fix: ignore late savings transfer exit off the flow Co-Authored-By: Claude Opus 5 (1M context) --- app/src/main/java/to/bitkit/ui/ContentView.kt | 11 ++++- .../test/java/to/bitkit/ui/ContentViewTest.kt | 40 ++++++++++++++----- 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/ContentView.kt b/app/src/main/java/to/bitkit/ui/ContentView.kt index 73a64f2852..6ba3aa833b 100644 --- a/app/src/main/java/to/bitkit/ui/ContentView.kt +++ b/app/src/main/java/to/bitkit/ui/ContentView.kt @@ -2079,7 +2079,16 @@ fun NavController.navigateToTransferSavingsIntro() = navigateTo(Routes.SavingsIn fun NavController.navigateToTransferSavingsAvailability() = navigateTo(Routes.SavingsAvailability) -fun NavController.navigateOnSavingsTransferExit() = navigateToHome() +/** + * Exits the savings transfer to home. The coop close retry job holds on to this callback for up to + * 30 minutes, so it is ignored once the transfer flow is gone from the back stack, the way popping + * the transfer graph used to be a no-op there. + */ +fun NavController.navigateOnSavingsTransferExit() { + val isTransferFlowOpen = currentBackStack.value.any { it.destination.hasRoute() } + if (!isTransferFlowOpen) return + navigateToHome() +} fun NavController.navigateToTransferSpendingStart(hasSeenSpendingIntro: Boolean) = navigateTo(transferSpendingStartRoute(hasSeenSpendingIntro)) diff --git a/app/src/test/java/to/bitkit/ui/ContentViewTest.kt b/app/src/test/java/to/bitkit/ui/ContentViewTest.kt index 1dbb4f6d08..eb95b89af7 100644 --- a/app/src/test/java/to/bitkit/ui/ContentViewTest.kt +++ b/app/src/test/java/to/bitkit/ui/ContentViewTest.kt @@ -99,17 +99,7 @@ class ContentViewTest { @Test fun `savings transfer completion returns home and drops spending from back stack`() { - val navController = NavHostController(ApplicationProvider.getApplicationContext()).apply { - navigatorProvider.addNavigator(ComposeNavigator()) - graph = createGraph(startDestination = Routes.Home) { - composable {} - composable {} - navigation(startDestination = Routes.SavingsAvailability) { - composable {} - composable {} - } - } - } + val navController = transferNavController() navController.navigateTo(Routes.Spending) navController.navigateToTransferSavingsAvailability() navController.navigateTo(Routes.SavingsProgress) @@ -120,4 +110,32 @@ class ContentViewTest { assertFalse(navController.currentBackStack.value.any { it.destination.hasRoute() }) assertFalse(navController.currentBackStack.value.any { it.destination.hasRoute() }) } + + @Test + fun `savings transfer exit does nothing once the transfer flow is gone`() { + val navController = transferNavController() + navController.navigateTo(Routes.Spending) + navController.navigateToTransferSavingsAvailability() + navController.navigateTo(Routes.SavingsProgress) + navController.navigateOnSavingsTransferExit() + navController.navigateTo(Routes.Settings) + + navController.navigateOnSavingsTransferExit() + + assertTrue(navController.currentDestination?.hasRoute() == true) + } + + private fun transferNavController(): NavHostController = + NavHostController(ApplicationProvider.getApplicationContext()).apply { + navigatorProvider.addNavigator(ComposeNavigator()) + graph = createGraph(startDestination = Routes.Home) { + composable {} + composable {} + composable {} + navigation(startDestination = Routes.SavingsAvailability) { + composable {} + composable {} + } + } + } } From d745972ff58bdca925269f55f6baef95f3143462 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 18 Sep 2026 10:18:19 -0300 Subject: [PATCH 5/7] refactor: guard savings exit with public nav api Co-Authored-By: Claude Opus 5 (1M context) --- app/src/main/java/to/bitkit/ui/ContentView.kt | 9 +++++---- app/src/test/java/to/bitkit/ui/ContentViewTest.kt | 15 +++++++++++++-- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/ContentView.kt b/app/src/main/java/to/bitkit/ui/ContentView.kt index 6ba3aa833b..1777ecefc1 100644 --- a/app/src/main/java/to/bitkit/ui/ContentView.kt +++ b/app/src/main/java/to/bitkit/ui/ContentView.kt @@ -36,6 +36,7 @@ import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavController import androidx.navigation.NavDestination.Companion.hasRoute +import androidx.navigation.NavDestination.Companion.hierarchy import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.NavOptionsBuilder @@ -2081,12 +2082,12 @@ fun NavController.navigateToTransferSavingsAvailability() = navigateTo(Routes.Sa /** * Exits the savings transfer to home. The coop close retry job holds on to this callback for up to - * 30 minutes, so it is ignored once the transfer flow is gone from the back stack, the way popping - * the transfer graph used to be a no-op there. + * 30 minutes, so it is ignored once the transfer flow is no longer the destination on screen, the + * way popping the transfer graph used to be a no-op there. */ fun NavController.navigateOnSavingsTransferExit() { - val isTransferFlowOpen = currentBackStack.value.any { it.destination.hasRoute() } - if (!isTransferFlowOpen) return + val isOnTransferFlow = currentDestination?.hierarchy?.any { it.hasRoute() } == true + if (!isOnTransferFlow) return navigateToHome() } diff --git a/app/src/test/java/to/bitkit/ui/ContentViewTest.kt b/app/src/test/java/to/bitkit/ui/ContentViewTest.kt index eb95b89af7..ed17d35b46 100644 --- a/app/src/test/java/to/bitkit/ui/ContentViewTest.kt +++ b/app/src/test/java/to/bitkit/ui/ContentViewTest.kt @@ -107,8 +107,19 @@ class ContentViewTest { navController.navigateOnSavingsTransferExit() assertTrue(navController.currentDestination?.hasRoute() == true) - assertFalse(navController.currentBackStack.value.any { it.destination.hasRoute() }) - assertFalse(navController.currentBackStack.value.any { it.destination.hasRoute() }) + assertNull(navController.previousBackStackEntry) + } + + @Test + fun `savings transfer exit leaves a screen opened on top of the flow alone`() { + val navController = transferNavController() + navController.navigateToTransferSavingsAvailability() + navController.navigateTo(Routes.SavingsProgress) + navController.navigateTo(Routes.Settings) + + navController.navigateOnSavingsTransferExit() + + assertTrue(navController.currentDestination?.hasRoute() == true) } @Test From 096bf761dd1ffb7abe8051c0adc52fa736e9fedd Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 18 Sep 2026 10:47:31 -0300 Subject: [PATCH 6/7] docs: add transfer to savings returns home journey Co-Authored-By: Claude Opus 5 (1M context) --- journeys/README.md | 2 ++ .../transfer-to-savings-returns-home.xml | 28 +++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 journeys/transfer/transfer-to-savings-returns-home.xml diff --git a/journeys/README.md b/journeys/README.md index 403c5e2309..fdff33ec51 100644 --- a/journeys/README.md +++ b/journeys/README.md @@ -121,6 +121,7 @@ fixtures, push notifications) live in each suite's README. | [notification-permission](notification-permission) | 4 | Background-setup toggles | | [payment-requests](payment-requests) | 2 | Requires a linked fixture issuer; rejected shapes are unit fixtures | | [pubky-marketplace](pubky-marketplace) | 1 | Two-wallet Paykit marketplace payment; integration fixture required | +| [transfer](transfer) | 1 | Spending to Savings exit route; needs an open channel and closes it; no README | | [widgets](widgets) | 2 | Needs no backend — the quickest way to see the loop work; no README | ## Cross-platform @@ -142,6 +143,7 @@ Known differences in the corpus, as of the iOS port (synonymdev/bitkit-ios#691): | `hardware-wallet/receive-onchain.xml`, `hardware-wallet/send-onchain.xml` | not ported | | `payment-requests/requested-resolution-failure.xml` | not ported | | `deeplinks/*` | not ported — iOS registers the `bitkit` scheme but has no screen or sheet router | +| `transfer/transfer-to-savings-returns-home.xml` | not ported — iOS already resets navigation to home on the same OK, so the journey has no iOS counterpart yet | | — | `hardware-wallet/transfer-to-spending-over-max.xml` exists only on iOS | ### Running one on iOS diff --git a/journeys/transfer/transfer-to-savings-returns-home.xml b/journeys/transfer/transfer-to-savings-returns-home.xml new file mode 100644 index 0000000000..8c567f7b97 --- /dev/null +++ b/journeys/transfer/transfer-to-savings-returns-home.xml @@ -0,0 +1,28 @@ + + + Verifies that confirming a Spending to Savings transfer and tapping OK on the success screen + lands on the wallet home screen, not on the Spending wallet screen the flow was started from, + and that system back from there does not reopen the Spending screen or the transfer flow + (synonymdev/bitkit-android#809). iOS resets navigation to home on the same OK. + + Precondition: onboarded dev wallet with an open, usable channel to the LSP and a POSITIVE + Spending balance. The transfer closes that channel, so reopen one (Blocktank order through the + lsp helper, then mine blocks) afterwards if later work needs a Spending balance. Start on the + wallet home screen. + + + Verify the Spending balance card (testTag "ActivitySpending") shows a positive amount + Tap the Spending balance card (testTag "ActivitySpending") + Tap "Transfer To Savings" (testTag "TransferToSavings") + If the transfer to savings intro appears, tap "Get Started" (testTag "SavingsIntro-button") + Tap "Continue" (testTag "AvailabilityContinue") on the funds availability screen + On the confirm screen, swipe the "Swipe To Transfer" handle (testTag "GRAB") fully to the right + Wait up to 60 seconds for the "Transfer Successful" screen (testTag "TransferSuccess") with the OK button (testTag "TransferSuccess-button") + Tap OK (testTag "TransferSuccess-button") + Verify the home screen is visible with the Savings and Spending cards (testTags "ActivitySavings" and "ActivitySpending") + Verify there is no back button (testTag "NavigationBack") and no "Spending" screen title + Verify the Spending balance card (testTag "ActivitySpending") shows 0 + Press the system back button once + Verify the app is backgrounded to the launcher, and neither the Spending screen nor any transfer screen is shown + + From 0a2320e22e75753a3bd560a3e10bf6cd923f8dba Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 18 Sep 2026 11:52:06 -0300 Subject: [PATCH 7/7] fix: scope savings exit guard to the progress screen Co-Authored-By: Claude Opus 5 (1M context) --- app/src/main/java/to/bitkit/ui/ContentView.kt | 9 ++++----- app/src/test/java/to/bitkit/ui/ContentViewTest.kt | 15 +++++++++++++++ .../transfer/transfer-to-savings-returns-home.xml | 2 +- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/ContentView.kt b/app/src/main/java/to/bitkit/ui/ContentView.kt index ca91e6160a..02fed66981 100644 --- a/app/src/main/java/to/bitkit/ui/ContentView.kt +++ b/app/src/main/java/to/bitkit/ui/ContentView.kt @@ -37,7 +37,6 @@ import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavController import androidx.navigation.NavDestination.Companion.hasRoute -import androidx.navigation.NavDestination.Companion.hierarchy import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.NavOptionsBuilder @@ -2093,12 +2092,12 @@ fun NavController.navigateToTransferSavingsAvailability() = navigateTo(Routes.Sa /** * Exits the savings transfer to home. The coop close retry job holds on to this callback for up to - * 30 minutes, so it is ignored once the transfer flow is no longer the destination on screen, the - * way popping the transfer graph used to be a no-op there. + * 30 minutes, so it is ignored unless the savings progress screen that owns it is still on screen. + * Matching the whole transfer graph would also pop a transfer to spending the user started since. */ fun NavController.navigateOnSavingsTransferExit() { - val isOnTransferFlow = currentDestination?.hierarchy?.any { it.hasRoute() } == true - if (!isOnTransferFlow) return + val isOnSavingsProgress = currentDestination?.hasRoute() == true + if (!isOnSavingsProgress) return navigateToHome() } diff --git a/app/src/test/java/to/bitkit/ui/ContentViewTest.kt b/app/src/test/java/to/bitkit/ui/ContentViewTest.kt index ed17d35b46..270bc474b5 100644 --- a/app/src/test/java/to/bitkit/ui/ContentViewTest.kt +++ b/app/src/test/java/to/bitkit/ui/ContentViewTest.kt @@ -136,6 +136,20 @@ class ContentViewTest { assertTrue(navController.currentDestination?.hasRoute() == true) } + @Test + fun `savings transfer exit leaves a later transfer to spending alone`() { + val navController = transferNavController() + navController.navigateToTransferSavingsAvailability() + navController.navigateTo(Routes.SavingsProgress) + navController.navigateOnSavingsTransferExit() + navController.navigateTo(Routes.Spending) + navController.navigateTo(Routes.SpendingConfirm) + + navController.navigateOnSavingsTransferExit() + + assertTrue(navController.currentDestination?.hasRoute() == true) + } + private fun transferNavController(): NavHostController = NavHostController(ApplicationProvider.getApplicationContext()).apply { navigatorProvider.addNavigator(ComposeNavigator()) @@ -146,6 +160,7 @@ class ContentViewTest { navigation(startDestination = Routes.SavingsAvailability) { composable {} composable {} + composable {} } } } diff --git a/journeys/transfer/transfer-to-savings-returns-home.xml b/journeys/transfer/transfer-to-savings-returns-home.xml index 8c567f7b97..bca5412dd2 100644 --- a/journeys/transfer/transfer-to-savings-returns-home.xml +++ b/journeys/transfer/transfer-to-savings-returns-home.xml @@ -20,7 +20,7 @@ Wait up to 60 seconds for the "Transfer Successful" screen (testTag "TransferSuccess") with the OK button (testTag "TransferSuccess-button") Tap OK (testTag "TransferSuccess-button") Verify the home screen is visible with the Savings and Spending cards (testTags "ActivitySavings" and "ActivitySpending") - Verify there is no back button (testTag "NavigationBack") and no "Spending" screen title + Verify there is no back button (testTag "NavigationBack") and no "Transfer To Savings" button (testTag "TransferToSavings") Verify the Spending balance card (testTag "ActivitySpending") shows 0 Press the system back button once Verify the app is backgrounded to the launcher, and neither the Spending screen nor any transfer screen is shown