Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 47 additions & 97 deletions apps/flipcash/app/src/main/kotlin/com/flipcash/app/internal/ui/App.kt
Original file line numberDiff line numberDiff line change
Expand Up@@ -40,15 +40,12 @@ import com.flipcash.app.core.LocalUserManager
import com.flipcash.app.core.extensions.navigateAll
import com.flipcash.app.core.navigation.DeeplinkAction
import com.flipcash.app.core.navigation.NavBarButton
import com.flipcash.app.core.navigation.NavBarConfig
import com.flipcash.app.core.ui.NavigationBar
import com.flipcash.app.core.ui.rememberNavigationBarState
import com.flipcash.app.core.verification.email.LocalEmailCodeChannel
import com.flipcash.app.featureflags.FeatureFlag
import com.flipcash.app.featureflags.LocalFeatureFlags
import com.flipcash.app.featureflags.model.BackgroundResetTimeout
import com.flipcash.app.internal.ui.navigation.AppContent
import com.flipcash.app.internal.ui.navigation.NewAppContent
import com.flipcash.app.internal.ui.navigation.appEntryProvider
import com.flipcash.app.internal.ui.navigation.decorators.rememberNavBlockingOverlayEntryDecorator
import com.flipcash.app.internal.ui.navigation.decorators.rememberNavMessagingEntryDecorator
Expand DownExpand Up@@ -87,7 +84,6 @@ import kotlinx.coroutines.flow.first
internal fun App(
tipsEngine: TipsEngine,
) {
val features = LocalFeatureFlags.current
val router = LocalRouter.current!!
val analytics = rememberAnalytics()
val viewModel = getActivityScopedViewModel<HomeViewModel>()
Expand DownExpand Up@@ -118,12 +114,10 @@ internal fun App(
val session = LocalSessionController.current!!
val userState by userManager.state.collectAsStateWithLifecycle()

val isNewUi by features.observe(FeatureFlag.NewUi).collectAsStateWithLifecycle()

// Card-expand (iOS #587) is owned HERE rather than inside NewAppContent because the deeplink
// handling below sits outside the v1/v2 shells: a `/token` link opens the wallet's expanded card
// (see DeeplinkAction.OpenToken), which needs the controller. NewAppContent provides it to the
// tree; v1 has no expansion and never touches it.
// Card-expand (iOS #587) is owned HERE rather than inside AppContent because the deeplink
// handling below sits outside the shell: a `/token` link opens the wallet's expanded card
// (see DeeplinkAction.OpenToken), which needs the controller. AppContent provides it to the
// tree.
val context = LocalContext.current
val cardExpansion = remember(context) {
CardExpansionController().apply {
Expand DownExpand Up@@ -179,75 +173,42 @@ internal fun App(
LocalSharedTransitionScope provides this,
) {
CoinbaseOnRampHandler {
if (isNewUi) {
NewAppContent(
codeNavigator = codeNavigator,
resultStateRegistry = resultStateRegistry,
barManager = barManager,
cardExpansion = cardExpansion,
deepLink = { deepLink },
onPendingAction = { action ->
deeplinkHandled = true
when (action) {
// v2 cold start: the wallet is already the
// launch home, so the token just opens as its
// expanded card on top of it.
is DeeplinkAction.OpenToken ->
cardExpansion.beginExpanded(action.mint)
is DeeplinkAction.OpenCashLink ->
session.openCashLink(action.entropy)
is DeeplinkAction.PresentTipCard ->
session.resolveTipCard(action.userId)
is DeeplinkAction.Login ->
viewModel.handleLoginEntropy(
action.entropy,
onSwitchAccount = {
codeNavigator.replaceAll(
AppRoute.OnboardingFlow(
seed = action.entropy,
fromDeeplink = true
)
)
},
onDismissed = { }
)
else -> {}
}
deepLink = null
}
)
} else {
AppContent(
codeNavigator = codeNavigator,
resultStateRegistry = resultStateRegistry,
barManager = barManager,
deepLink = { deepLink },
onPendingAction = { action ->
deeplinkHandled = true
when (action) {
is DeeplinkAction.OpenCashLink ->
session.openCashLink(action.entropy)
is DeeplinkAction.PresentTipCard ->
session.resolveTipCard(action.userId)
is DeeplinkAction.Login ->
viewModel.handleLoginEntropy(
action.entropy,
onSwitchAccount = {
codeNavigator.replaceAll(
AppRoute.OnboardingFlow(
seed = action.entropy,
fromDeeplink = true
)
AppContent(
codeNavigator = codeNavigator,
resultStateRegistry = resultStateRegistry,
barManager = barManager,
cardExpansion = cardExpansion,
deepLink = { deepLink },
onPendingAction = { action ->
deeplinkHandled = true
when (action) {
// Cold start: the wallet is already the launch
// home, so the token just opens as its expanded
// card on top of it.
is DeeplinkAction.OpenToken ->
cardExpansion.beginExpanded(action.mint)
is DeeplinkAction.OpenCashLink ->
session.openCashLink(action.entropy)
is DeeplinkAction.PresentTipCard ->
session.resolveTipCard(action.userId)
is DeeplinkAction.Login ->
viewModel.handleLoginEntropy(
action.entropy,
onSwitchAccount = {
codeNavigator.replaceAll(
AppRoute.OnboardingFlow(
seed = action.entropy,
fromDeeplink = true
)
},
onDismissed = { }
)
else -> {}
}
deepLink = null
)
},
onDismissed = { }
)
else -> {}
}
)
}
deepLink = null
}
)

// The scrim + bill overlay are hosted per-entry by
// NavBillOverlayEntryDecorator (added to the AppNavHost
Expand DownExpand Up@@ -285,30 +246,19 @@ internal fun App(
} else false

if (!delivered) {
codeNavigator.navigateAll(
action.routes,
isNewUi = isNewUi,
)
codeNavigator.navigateAll(action.routes)
}
}

is DeeplinkAction.OpenToken -> {
if (isNewUi) {
// Land on the wallet tab (clearing anything pushed on
// it) and open the token as its EXPANDED CARD — the
// same overlay, chrome and dismissal a tap on the card
// gives. Pushing it instead reads as a modal on a stack
// the user never navigated. Mirrors iOS
// DeepLinkController's requestedCardMint.
codeNavigator.navigateAll(
listOf(AppRoute.Sheets.Wallet),
isNewUi = true,
)
cardExpansion.beginExpanded(action.mint)
} else {
// v1 has no card expansion — open the wallet sheet.
codeNavigator.navigateAll(action.routes)
}
// Land on the wallet tab (clearing anything pushed on
// it) and open the token as its EXPANDED CARD — the
// same overlay, chrome and dismissal a tap on the card
// gives. Pushing it instead reads as a modal on a stack
// the user never navigated. Mirrors iOS
// DeepLinkController's requestedCardMint.
codeNavigator.navigateAll(listOf(AppRoute.Sheets.Wallet))
cardExpansion.beginExpanded(action.mint)
}

is DeeplinkAction.Login -> viewModel.handleLoginEntropy(
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,13 +19,10 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.flipcash.app.core.AppRoute
import dev.chrisbanes.haze.HazeState
import com.flipcash.app.core.navigation.NavBarButton
import com.flipcash.app.core.navigation.NavBarConfig
import com.flipcash.app.core.navigation.asNavBarTab
import com.flipcash.app.core.navigation.destinationRoute
import com.flipcash.app.core.ui.NavigationBar
import com.flipcash.app.core.ui.rememberNavigationBarState
import com.flipcash.app.featureflags.FeatureFlag
import com.flipcash.app.featureflags.LocalFeatureFlags
import com.flipcash.app.session.LocalSessionController
import com.getcode.manager.BottomBarManager
import com.getcode.navigation.core.CodeNavigator
Expand All@@ -34,12 +31,11 @@ import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map

/**
* The hoisted v2 navigation bar — root chrome, not owned by any screen. It renders over whichever
* The hoisted navigation bar — root chrome, not owned by any screen. It renders over whichever
* top-level route is a tab home and switches tabs by **swapping the current screen** (single
* backstack, like a tab bar — hence [CodeNavigator.replaceAll], not a sheet).
*
* Only visible when [FeatureFlag.NewUi] is on and the current route maps to a tab; v1 keeps its
* in-screen bar (see ScannerNavigationBar). When v1 is dropped, this becomes the only nav bar.
* Only visible when the current route maps to a tab.
*
* Self-positions as a full-size, touch-transparent overlay pinned to the bottom, so it can be
* dropped into any container (it does not require a BoxScope from its caller).
Expand All@@ -57,7 +53,7 @@ internal fun AppNavigationBar(

// A BottomBar modal (e.g. Add Money) renders in the nav content, above this bar; hide the bar so
// it doesn't draw over the modal. Safe because the bar is a bottom overlay (not a scaffold
// bottomBar), so hiding it doesn't resize the content beneath (see NewAppContent).
// bottomBar), so hiding it doesn't resize the content beneath (see AppContent).
val bottomBarMessages by BottomBarManager.messages.collectAsStateWithLifecycle()

// A bill/tip card renders at the app root above everything; hide the bar so it doesn't show
Expand All@@ -67,8 +63,8 @@ internal fun AppNavigationBar(
session?.billState?.map { it.bill != null } ?: flowOf(false)
}.collectAsStateWithLifecycle(initialValue = false)

// Unread tip-DM count, badged onto the Chat tab the same source the v1 scanner bar badges its
// Tips button with, so the badge appears, updates and clears as conversations are read.
// Unread tip-DM count, badged onto the Chat tab, so the badge appears, updates and clears as
// conversations are read.
val tipUnreadCount by remember(session) {
session?.state?.map { it.tipsUnreadCount } ?: flowOf(0)
}.collectAsStateWithLifecycle(initialValue = 0)
Expand All@@ -84,8 +80,6 @@ internal fun AppNavigationBar(
exit = slideOutVertically { it } + fadeOut(),
) {
val state = rememberNavigationBarState(
isNewUi = true,
config = NavBarConfig(order = NavBarButton.v2Order),
selectedTab = selectedTab ?: NavBarButton.Wallet,
tipUnreadCount = tipUnreadCount,
)
Expand All@@ -97,7 +91,7 @@ internal fun AppNavigationBar(
state = state,
onButtonClick = { button ->
// Tab bar semantics: swap the current screen (single backstack).
button.destinationRoute()?.let(navigator::replaceAll)
navigator.replaceAll(button.destinationRoute())
},
hazeState = hazeState,
)
Expand Down
Loading