11package com.flipcash.app.internal.ui
22
3+ import androidx.compose.animation.EnterTransition
4+ import androidx.compose.animation.ExitTransition
5+ import androidx.compose.animation.slideInHorizontally
6+ import androidx.compose.animation.slideOutHorizontally
7+ import androidx.compose.animation.togetherWith
38import androidx.compose.foundation.layout.Box
4- import androidx.compose.foundation.layout.fillMaxSize
59import androidx.compose.runtime.Composable
610import androidx.compose.runtime.CompositionLocalProvider
711import androidx.compose.runtime.LaunchedEffect
@@ -20,15 +24,18 @@ import androidx.lifecycle.compose.LocalLifecycleOwner
2024import androidx.lifecycle.compose.collectAsStateWithLifecycle
2125import androidx.navigation3.runtime.NavBackStack
2226import androidx.navigation3.runtime.NavKey
27+ import androidx.navigation3.scene.OverlayScene
2328import androidx.navigation3.scene.SinglePaneSceneStrategy
2429import com.flipcash.app.analytics.rememberAnalytics
2530import com.flipcash.app.android.BuildConfig
2631import com.flipcash.app.bill.customization.BillPlaygroundScaffold
2732import com.flipcash.app.core.LocalUserManager
2833import com.flipcash.app.core.AppRoute
29- import com.flipcash.app.core.navigation.DeeplinkType
34+ import com.flipcash.app.contact.verification.EmailVerificationFlow
35+ import com.flipcash.app.core.navigation.DeeplinkAction
3036import com.flipcash.app.internal.ui.navigation.AppPreloads
3137import com.flipcash.app.internal.ui.navigation.appEntryProvider
38+ import com.flipcash.app.internal.ui.navigation.decorators.rememberNavBlockingOverlayEntryDecorator
3239import com.flipcash.app.internal.ui.navigation.decorators.rememberNavMessagingEntryDecorator
3340import com.flipcash.app.onramp.ExternalWalletOnRampHandler
3441import com.flipcash.app.onramp.LocalExternalWalletState
@@ -38,7 +45,6 @@ import com.flipcash.app.payments.PaymentScaffold
3845import com.flipcash.app.router.LocalRouter
3946import com.flipcash.app.session.LocalSessionController
4047import com.flipcash.app.theme.FlipcashTheme
41- import com.flipcash.app.updates.UpdateRequiredBlockingView
4248import com.flipcash.features.shareapp.R
4349import com.flipcash.services.user.AuthState
4450import com.getcode.libs.biometrics.BiometricsError
@@ -53,10 +59,10 @@ import com.getcode.solana.rpc.RpcConfig
5359import com.getcode.theme.CodeTheme
5460import com.getcode.ui.biometrics.LocalBiometricsState
5561import com.getcode.ui.biometrics.rememberBiometricsState
56- import com.getcode.ui.biometrics.views.BiometricsBlockingView
5762import com.getcode.ui.components.OnLifecycleEvent
5863import com.getcode.ui.components.bars.rememberBarManager
5964import com.getcode.ui.core.RestrictionType
65+ import com.flipcash.app.core.extensions.navigateTo
6066import dev.bmcreations.tipkit.TipScaffold
6167import dev.bmcreations.tipkit.engines.TipsEngine
6268import dev.theolm.rinku.DeepLink
@@ -87,15 +93,9 @@ internal fun App(
8793 }
8894
8995 var deepLink by remember { mutableStateOf<DeepLink ?>(null ) }
90- var loginRequest by remember { mutableStateOf<String ?>(null ) }
9196 val userManager = LocalUserManager .current!!
9297 DeepLinkListener {
9398 analytics.deeplinkOpened(it.data)
94- val type = router.processType(it)
95- analytics.deeplinkParsed(type, it.data)
96- if (type is DeeplinkType .Login ) {
97- loginRequest = type.entropy
98- }
9999 deepLink = it
100100 }
101101
@@ -145,8 +145,6 @@ internal fun App(
145145 state = externalWalletOnRamp,
146146 lifecycleOwner = LocalLifecycleOwner .current,
147147 navigator = codeNavigator,
148- router = router,
149- deepLink = deepLink,
150148 ) {
151149 AppNavHost (
152150 navigator = codeNavigator,
@@ -155,7 +153,8 @@ internal fun App(
155153 rememberNavMessagingEntryDecorator(
156154 codeNavigator.backStack,
157155 barManager
158- )
156+ ),
157+ rememberNavBlockingOverlayEntryDecorator(),
159158 ),
160159 sceneStrategy = ModalBottomSheetSceneStrategy <NavKey >(
161160 codeNavigator.resultStore
@@ -164,53 +163,90 @@ internal fun App(
164163 codeNavigator.backStack.lastIndex - 1
165164 )
166165 } then SinglePaneSceneStrategy (),
167- onBack = { codeNavigator.navigateBack() },
168- entryProvider = { key ->
169- appEntryProvider(
170- key = key,
171- resultStateRegistry = resultStateRegistry,
172- barManager = barManager,
173- deepLink = { deepLink },
174- )
166+ transitionSpec = {
167+ if (targetState is OverlayScene <* > || initialState is OverlayScene <* >) {
168+ EnterTransition .None togetherWith ExitTransition .None
169+ } else {
170+ slideInHorizontally(initialOffsetX = { it }) togetherWith
171+ slideOutHorizontally(targetOffsetX = { - it })
172+ }
173+ },
174+ popTransitionSpec = {
175+ if (targetState is OverlayScene <* > || initialState is OverlayScene <* >) {
176+ EnterTransition .None togetherWith ExitTransition .None
177+ } else {
178+ slideInHorizontally(initialOffsetX = { - it }) togetherWith
179+ slideOutHorizontally(targetOffsetX = { it })
180+ }
175181 },
182+ predictivePopTransitionSpec = {
183+ if (targetState is OverlayScene <* > || initialState is OverlayScene <* >) {
184+ EnterTransition .None togetherWith ExitTransition .None
185+ } else {
186+ slideInHorizontally(initialOffsetX = { - it }) togetherWith
187+ slideOutHorizontally(targetOffsetX = { it })
188+ }
189+ },
190+ onBack = { codeNavigator.navigateBack() },
191+ entryProvider = appEntryProvider(
192+ resultStateRegistry = resultStateRegistry,
193+ barManager = barManager,
194+ deepLink = { deepLink },
195+ ),
176196 )
177197 }
178198
179199 LaunchedEffect (deepLink) {
180- if (codeNavigator.currentRouteKey is AppRoute .Loading ) return @LaunchedEffect
181- if (deepLink != null ) {
182- val routes = router.processDestination(deepLink)
183- if (routes.isNotEmpty()) {
184- codeNavigator.replaceAll(routes)
185- }
186- deepLink = null
187- }
188- }
200+ val link = deepLink ? : return @LaunchedEffect
189201
190- LaunchedEffect (
191- loginRequest,
192- codeNavigator.lastItem,
193- userManager.authState
194- ) {
195- if (codeNavigator.currentRouteKey is AppRoute .Loading ) return @LaunchedEffect
196- if (userManager.authState !is AuthState .LoggedInWithUser ) {
197- loginRequest = null
202+ if (codeNavigator.currentRouteKey is AppRoute .Loading ) {
203+ // Cold start — MainRoot handles it via the deepLink lambda
198204 return @LaunchedEffect
199205 }
200- loginRequest?.let { entropy ->
201- viewModel.handleLoginEntropy(
202- entropy,
206+
207+ when (val action = router.dispatch(link)) {
208+ is DeeplinkAction .Navigate -> {
209+ // If a verification code targets a screen already open,
210+ // deliver via side-channel and skip navigation.
211+ val verification = action.routes
212+ .filterIsInstance<AppRoute .Verification >()
213+ .firstOrNull()
214+ val email = verification?.email
215+ val code = verification?.emailVerificationCode
216+ val delivered = if (email != null && code != null ) {
217+ EmailVerificationFlow .deliverCode(email, code)
218+ } else false
219+
220+ if (! delivered) {
221+ codeNavigator.navigateTo(action.routes)
222+ }
223+ }
224+ is DeeplinkAction .ExternalWallet -> externalWalletOnRamp.handleWalletDeeplink(action.type)
225+ is DeeplinkAction .Login -> viewModel.handleLoginEntropy(
226+ action.entropy,
203227 onSwitchAccount = {
204- loginRequest = null
205228 codeNavigator.replaceAll(
206229 AppRoute .Onboarding .Login (
207- entropy,
230+ action. entropy,
208231 fromDeeplink = true
209232 )
210233 )
211234 },
212- onDismissed = { loginRequest = null }
235+ onDismissed = { }
213236 )
237+ is DeeplinkAction .OpenCashLink -> session.openCashLink(action.entropy)
238+ DeeplinkAction .None -> {}
239+ }
240+ deepLink = null
241+ }
242+
243+ LaunchedEffect (userState.authState) {
244+ if (userState.authState == AuthState .LoggedOut ) {
245+ val current = codeNavigator.currentRouteKey
246+ if (current !is AppRoute .Loading && current !is AppRoute .Onboarding ) {
247+ codeNavigator.pendingSheetDismiss = null
248+ codeNavigator.replaceAll(AppRoute .Onboarding .Login ())
249+ }
214250 }
215251 }
216252
@@ -243,8 +279,6 @@ internal fun App(
243279 }
244280 }
245281
246- BiometricsBlockingView (modifier = Modifier .fillMaxSize(), biometricsState)
247- UpdateRequiredBlockingView (modifier = Modifier .fillMaxSize(), biometricsState = biometricsState)
248282 }
249283 }
250284}
0 commit comments