Skip to content

Commit e0993f2

Browse files
authored
feat: fire openCashLink eagerly on cold start instead of waiting for nav transition (#970)
On cold launch with a cash link deep link, openCashLink was deferred until the Loading → Scanner nav transition completed and App.kt's LaunchedEffect re-fired. This added visible latency before the bill appeared. Now buildNavGraphForLaunch carries a pendingAction for OpenCashLink and Login deep links, and MainRoot fires it immediately alongside the nav transition so the claim starts in parallel with the Scanner screen appearing. Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent 1737aac commit e0993f2

4 files changed

Lines changed: 54 additions & 10 deletions

File tree

apps/flipcash/app/src/main/kotlin/com/flipcash/app/internal/ui/App.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,28 @@ internal fun App(
227227
resultStateRegistry = resultStateRegistry,
228228
barManager = barManager,
229229
deepLink = { deepLink },
230+
onPendingAction = { action ->
231+
deeplinkHandled = true
232+
when (action) {
233+
is DeeplinkAction.OpenCashLink ->
234+
session.openCashLink(action.entropy)
235+
is DeeplinkAction.Login ->
236+
viewModel.handleLoginEntropy(
237+
action.entropy,
238+
onSwitchAccount = {
239+
codeNavigator.replaceAll(
240+
AppRoute.OnboardingFlow(
241+
seed = action.entropy,
242+
fromDeeplink = true
243+
)
244+
)
245+
},
246+
onDismissed = { }
247+
)
248+
else -> {}
249+
}
250+
deepLink = null
251+
},
230252
),
231253
)
232254

apps/flipcash/app/src/main/kotlin/com/flipcash/app/internal/ui/navigation/AppScreenContent.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import com.flipcash.app.cash.CashScreen
2727
import com.flipcash.app.contact.verification.VerificationFlowScreen
2828
import com.flipcash.app.currencycreator.CurrencyCreatorFlowScreen
2929
import com.flipcash.app.core.AppRoute
30+
import com.flipcash.app.core.navigation.DeeplinkAction
3031
import com.flipcash.app.currency.RegionSelectionScreen
3132
import com.flipcash.app.deposit.DepositFlowScreen
3233
import com.flipcash.app.directsend.SendFlowScreen
@@ -67,10 +68,11 @@ fun appEntryProvider(
6768
resultStateRegistry: NavResultStateRegistry,
6869
barManager: BarManager,
6970
deepLink: () -> DeepLink?,
71+
onPendingAction: (DeeplinkAction) -> Unit = {},
7072
): (NavKey) -> NavEntry<NavKey> = entryProvider {
7173

7274
// Loading / splash
73-
annotatedEntry<AppRoute.Loading> { MainRoot(deepLink) }
75+
annotatedEntry<AppRoute.Loading> { MainRoot(deepLink, onPendingAction) }
7476

7577
// Onboarding flow
7678
annotatedEntry<AppRoute.OnboardingFlow> { key ->

apps/flipcash/app/src/main/kotlin/com/flipcash/app/internal/ui/navigation/MainRoot.kt

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,10 @@ import kotlinx.coroutines.flow.onEach
4444
import kotlin.time.Duration.Companion.seconds
4545

4646
@Composable
47-
internal fun MainRoot(deepLink: () -> DeepLink?) {
47+
internal fun MainRoot(
48+
deepLink: () -> DeepLink?,
49+
onPendingAction: (DeeplinkAction) -> Unit = {},
50+
) {
4851
val navigator = LocalCodeNavigator.current
4952
val userManager = LocalUserManager.current!!
5053
var showLoading by remember { mutableStateOf(false) }
@@ -129,6 +132,13 @@ internal fun MainRoot(deepLink: () -> DeepLink?) {
129132
navigator.navigateAll(launch.deeplinkRoutes)
130133
}
131134
}
135+
136+
// Fire eagerly so the claim/login starts in parallel
137+
// with the nav transition instead of waiting for
138+
// App.kt's LaunchedEffect to see a non-Loading route.
139+
if (launch.pendingAction != null) {
140+
onPendingAction(launch.pendingAction)
141+
}
132142
}
133143
}.launchIn(this)
134144
}
@@ -144,6 +154,7 @@ internal fun MainRoot(deepLink: () -> DeepLink?) {
144154
internal data class LaunchNavGraph(
145155
val baseRoutes: List<NavKey>,
146156
val deeplinkRoutes: List<AppRoute> = emptyList(),
157+
val pendingAction: DeeplinkAction? = null,
147158
) {
148159
/**
149160
* Predict the final backstack that [baseRoutes] + [navigateTo(deeplinkRoutes)] will produce.
@@ -192,8 +203,13 @@ internal fun buildNavGraphForLaunch(
192203
baseRoutes = listOf(AppRoute.Main.Scanner),
193204
deeplinkRoutes = action.routes,
194205
)
195-
// OpenCashLink/Login/ExternalWallet are handled by App.kt's
196-
// LaunchedEffect(deepLink, currentRoute) once we leave Loading.
206+
207+
is DeeplinkAction.OpenCashLink,
208+
is DeeplinkAction.Login -> LaunchNavGraph(
209+
baseRoutes = listOf(AppRoute.Main.Scanner),
210+
pendingAction = action,
211+
)
212+
197213
else -> LaunchNavGraph(listOf(AppRoute.Main.Scanner))
198214
}
199215
} else {

apps/flipcash/app/src/test/kotlin/com/flipcash/app/internal/ui/navigation/BuildNavGraphForLaunchTest.kt

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,25 +56,29 @@ class BuildNavGraphForLaunchTest {
5656
}
5757

5858
@Test
59-
fun `logged in with OpenCashLink defers to App for dispatch`() {
59+
fun `logged in with OpenCashLink fires eagerly via pendingAction`() {
60+
val action = DeeplinkAction.OpenCashLink("testEntropy")
6061
val result = build(
6162
state = AuthState.Ready,
62-
action = DeeplinkAction.OpenCashLink("testEntropy"),
63+
action = action,
6364
deepLink = dummyLink,
6465
)!!
6566
assertEquals(listOf(AppRoute.Main.Scanner), result.baseRoutes)
66-
assertTrue(result.deeplinkRoutes.isEmpty(), "OpenCashLink must not be consumed by MainRoot")
67+
assertTrue(result.deeplinkRoutes.isEmpty())
68+
assertEquals(action, result.pendingAction)
6769
}
6870

6971
@Test
70-
fun `logged in with Login action defers to App for dispatch`() {
72+
fun `logged in with Login action fires eagerly via pendingAction`() {
73+
val action = DeeplinkAction.Login("seed")
7174
val result = build(
7275
state = AuthState.Ready,
73-
action = DeeplinkAction.Login("seed"),
76+
action = action,
7477
deepLink = dummyLink,
7578
)!!
7679
assertEquals(listOf(AppRoute.Main.Scanner), result.baseRoutes)
77-
assertTrue(result.deeplinkRoutes.isEmpty(), "Login must not be consumed by MainRoot")
80+
assertTrue(result.deeplinkRoutes.isEmpty())
81+
assertEquals(action, result.pendingAction)
7882
}
7983

8084
@Test

0 commit comments

Comments
 (0)