@@ -15,9 +15,11 @@ import androidx.compose.runtime.CompositionLocalProvider
1515import androidx.compose.runtime.LaunchedEffect
1616import androidx.compose.runtime.collectAsState
1717import androidx.compose.runtime.getValue
18+ import androidx.compose.runtime.mutableLongStateOf
1819import androidx.compose.runtime.mutableStateOf
1920import androidx.compose.runtime.remember
2021import androidx.compose.runtime.setValue
22+ import androidx.compose.runtime.snapshotFlow
2123import androidx.compose.ui.Modifier
2224import androidx.compose.ui.res.stringResource
2325import androidx.compose.ui.semantics.semantics
@@ -32,45 +34,50 @@ import androidx.navigation3.scene.SinglePaneSceneStrategy
3234import com.flipcash.app.analytics.rememberAnalytics
3335import com.flipcash.app.android.BuildConfig
3436import com.flipcash.app.bill.customization.BillPlaygroundScaffold
35- import com.flipcash.app.core.LocalUserManager
3637import com.flipcash.app.core.AppRoute
37- import com.flipcash.app.core.verification.email.LocalEmailCodeChannel
38+ import com.flipcash.app.core.LocalUserManager
39+ import com.flipcash.app.core.extensions.navigateTo
3840import com.flipcash.app.core.navigation.DeeplinkAction
41+ import com.flipcash.app.core.verification.email.LocalEmailCodeChannel
42+ import com.flipcash.app.featureflags.FeatureFlag
43+ import com.flipcash.app.featureflags.LocalFeatureFlags
44+ import com.flipcash.app.featureflags.model.BackgroundResetTimeout
3945import com.flipcash.app.internal.ui.navigation.appEntryProvider
4046import com.flipcash.app.internal.ui.navigation.decorators.rememberNavBlockingOverlayEntryDecorator
4147import com.flipcash.app.internal.ui.navigation.decorators.rememberNavMessagingEntryDecorator
4248import com.flipcash.app.onramp.CoinbaseOnRampHandler
4349import com.flipcash.app.onramp.ExternalWalletOnRampHandler
4450import com.flipcash.app.onramp.LocalExternalWalletOnRampController
45- import com.flipcash.app.onramp.LocalCoinbaseOnRampController
4651import com.flipcash.app.router.LocalRouter
4752import com.flipcash.app.session.LocalSessionController
4853import com.flipcash.app.theme.FlipcashTheme
4954import com.flipcash.features.shareapp.R
5055import com.flipcash.services.user.AuthState
56+ import com.getcode.animation.LocalSharedTransitionScope
5157import com.getcode.libs.biometrics.BiometricsError
5258import com.getcode.libs.qr.rememberQrBitmapPainter
5359import com.getcode.navigation.AppNavHost
60+ import com.getcode.navigation.Sheet
61+ import com.getcode.navigation.core.CodeNavigator
5462import com.getcode.navigation.core.LocalCodeNavigator
5563import com.getcode.navigation.core.rememberCodeNavigator
5664import com.getcode.navigation.extensions.getActivityScopedViewModel
5765import com.getcode.navigation.results.rememberNavResultStateRegistry
5866import com.getcode.navigation.scenes.ModalBottomSheetSceneStrategy
67+ import com.getcode.navigation.scrim.LocalScrimController
68+ import com.getcode.navigation.scrim.ScrimController
69+ import com.getcode.navigation.scrim.ScrimOverlay
5970import com.getcode.theme.CodeTheme
6071import com.getcode.ui.biometrics.LocalBiometricsState
6172import com.getcode.ui.biometrics.rememberBiometricsState
6273import com.getcode.ui.components.OnLifecycleEvent
6374import com.getcode.ui.components.bars.rememberBarManager
6475import com.getcode.ui.core.RestrictionType
65- import com.flipcash.app.core.extensions.navigateTo
66- import com.getcode.animation.LocalSharedTransitionScope
67- import com.getcode.navigation.scrim.LocalScrimController
68- import com.getcode.navigation.scrim.ScrimController
69- import com.getcode.navigation.scrim.ScrimOverlay
7076import dev.bmcreations.tipkit.TipScaffold
7177import dev.bmcreations.tipkit.engines.TipsEngine
7278import dev.theolm.rinku.DeepLink
7379import dev.theolm.rinku.compose.ext.DeepLinkListener
80+ import kotlinx.coroutines.flow.first
7481
7582@Composable
7683internal fun App (
@@ -96,6 +103,7 @@ internal fun App(
96103 }
97104
98105 var deepLink by remember { mutableStateOf<DeepLink ?>(null ) }
106+ var deeplinkHandled by remember { mutableStateOf(false ) }
99107 val userManager = LocalUserManager .current!!
100108 DeepLinkListener {
101109 analytics.deeplinkOpened(it.data)
@@ -239,7 +247,9 @@ internal fun App(
239247 return @LaunchedEffect
240248 }
241249
242- when (val action = router.dispatch(link)) {
250+ val action = router.dispatch(link)
251+ deeplinkHandled = action != DeeplinkAction .None
252+ when (action) {
243253 is DeeplinkAction .Navigate -> {
244254 // If a verification code targets a screen already open,
245255 // deliver via side-channel and skip navigation.
@@ -323,6 +333,13 @@ internal fun App(
323333 else -> Unit
324334 }
325335 }
336+
337+ BackgroundResetEffect (
338+ navigator = codeNavigator,
339+ deepLink = { deepLink },
340+ deeplinkHandled = { deeplinkHandled },
341+ onReset = { deeplinkHandled = false },
342+ )
326343 }
327344 }
328345 }
@@ -331,3 +348,51 @@ internal fun App(
331348 }
332349 }
333350
351+ @Composable
352+ private fun BackgroundResetEffect (
353+ navigator : CodeNavigator ,
354+ deepLink : () -> DeepLink ? ,
355+ deeplinkHandled : () -> Boolean ,
356+ onReset : () -> Unit ,
357+ ) {
358+ val featureFlags = LocalFeatureFlags .current
359+ val option by featureFlags.getOption(FeatureFlag .BackgroundReset )
360+ .collectAsStateWithLifecycle()
361+
362+ var pendingReset by remember { mutableStateOf(false ) }
363+ var backgroundedAt by remember { mutableLongStateOf(0L ) }
364+
365+ OnLifecycleEvent { _, event ->
366+ when (event) {
367+ Lifecycle .Event .ON_STOP -> {
368+ backgroundedAt = System .currentTimeMillis()
369+ }
370+ Lifecycle .Event .ON_RESUME -> {
371+ val timeout = runCatching { BackgroundResetTimeout .valueOf(option) }
372+ .getOrNull()
373+ ?.duration
374+
375+ if (timeout != null && backgroundedAt > 0L ) {
376+ val elapsed = System .currentTimeMillis() - backgroundedAt
377+ if (elapsed >= timeout.inWholeMilliseconds) {
378+ pendingReset = true
379+ }
380+ }
381+ backgroundedAt = 0L
382+ }
383+ else -> Unit
384+ }
385+ }
386+
387+ LaunchedEffect (pendingReset) {
388+ if (! pendingReset) return @LaunchedEffect
389+ // Wait for any pending deeplink to be consumed before deciding
390+ snapshotFlow { deepLink() }.first { it == null }
391+ if (! deeplinkHandled()) {
392+ navigator.popUntil { it !is Sheet }
393+ }
394+ onReset()
395+ pendingReset = false
396+ }
397+ }
398+
0 commit comments