Skip to content

Commit 5bc2fce

Browse files
committed
feat: generic openAsSheet support with auto-adapting dismiss style
- Add openAsSheet(route) to open any AppRoute as a modal bottom sheet with animated dismiss-replace when a sheet is already open - Add FlowDismissStyle composition local so FlowHost auto-swaps back arrow for close (X) when at a sheet root first step - Rename navigateTo(list) to navigateAll for clarity; remove single-route navigateTo overload - Widen Main.Sheet.initialRoute from Sheets to AppRoute - Remove duplicate Sheets.TokenDiscovery route and TokenDiscoverySheet() - Remove dead Menu.Deposit route - Fix SaveableStateProvider duplicate-key crash in pendingSheetDismiss by removing sheetGeneration from key() block and using it as LaunchedEffect key instead - Guard onDismissRequest during pendingSheetDismiss to prevent double onBack() - Add SwapResult.OpenDeposit for cross-sheet navigation from swap flow Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent 5b74106 commit 5bc2fce

22 files changed

Lines changed: 193 additions & 148 deletions

File tree

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import com.flipcash.app.android.BuildConfig
3636
import com.flipcash.app.bill.customization.BillPlaygroundScaffold
3737
import com.flipcash.app.core.AppRoute
3838
import com.flipcash.app.core.LocalUserManager
39-
import com.flipcash.app.core.extensions.navigateTo
39+
import com.flipcash.app.core.extensions.navigateAll
4040
import com.flipcash.app.core.navigation.DeeplinkAction
4141
import com.flipcash.app.core.verification.email.LocalEmailCodeChannel
4242
import com.flipcash.app.featureflags.FeatureFlag
@@ -258,7 +258,7 @@ internal fun App(
258258
} else false
259259

260260
if (!delivered) {
261-
codeNavigator.navigateTo(action.routes)
261+
codeNavigator.navigateAll(action.routes)
262262
}
263263
}
264264

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ import com.flipcash.app.currency.RegionSelectionScreen
3131
import com.flipcash.app.deposit.DepositDestinationScreen
3232
import com.flipcash.app.deposit.DepositFlowScreen
3333
import com.flipcash.app.discovery.TokenDiscoveryScreen
34-
import com.flipcash.app.discovery.TokenDiscoverySheet
3534
import com.flipcash.app.internal.ui.navigation.decorators.rememberNavMessagingEntryDecorator
3635
import com.flipcash.app.lab.LabsScreen
3736
import com.flipcash.app.lab.StandaloneLabsScreen
@@ -100,7 +99,7 @@ fun appEntryProvider(
10099
annotatedEntry<AppRoute.Sheets.ShareApp> { ShareAppScreen() }
101100
annotatedEntry<AppRoute.Sheets.Menu> { MenuScreen() }
102101
annotatedEntry<AppRoute.Sheets.Lab> { StandaloneLabsScreen() }
103-
annotatedEntry<AppRoute.Sheets.TokenDiscovery> { TokenDiscoverySheet() }
102+
104103

105104
// Tokens
106105
annotatedEntry<AppRoute.Token.Info> { key ->

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import com.flipcash.app.android.R
2525
import com.flipcash.app.core.LocalUserManager
2626
import com.flipcash.app.core.AppRoute
2727
import com.flipcash.app.core.navigation.DeeplinkAction
28-
import com.flipcash.app.core.extensions.navigateTo
28+
import com.flipcash.app.core.extensions.navigateAll
2929
import com.flipcash.app.core.extensions.resolveRoutes
3030
import com.flipcash.app.router.LocalRouter
3131
import com.flipcash.app.router.Router
@@ -128,7 +128,7 @@ internal fun MainRoot(deepLink: () -> DeepLink?) {
128128
if (!current.startsWith(target)) {
129129
navigator.replaceAll(launch.baseRoutes)
130130
if (launch.deeplinkRoutes.isNotEmpty()) {
131-
navigator.navigateTo(launch.deeplinkRoutes)
131+
navigator.navigateAll(launch.deeplinkRoutes)
132132
}
133133
}
134134
}

apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/AppRoute.kt

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ sealed interface AppRoute : NavKey, Parcelable {
7676
@Serializable
7777
@Parcelize
7878
data class Sheet(
79-
val initialRoute: Sheets,
79+
val initialRoute: AppRoute,
8080
val innerRoutes: List<AppRoute> = emptyList(),
8181
) : Main, com.getcode.navigation.Sheet
8282
}
@@ -114,9 +114,6 @@ sealed interface AppRoute : NavKey, Parcelable {
114114
@Serializable
115115
data object Lab : Sheets
116116

117-
@Serializable
118-
data object TokenDiscovery: Sheets
119-
120117
@Serializable
121118
data object ShareApp : Sheets
122119
}
@@ -196,8 +193,6 @@ sealed interface AppRoute : NavKey, Parcelable {
196193
@Serializable
197194
data object MyAccount : Menu
198195
@Serializable
199-
data class Deposit(val mint: Mint) : Menu
200-
@Serializable
201196
data object BackupKey : Menu
202197
@Serializable
203198
data object AppSettings : Menu

apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/extensions/CodeNavigator.kt

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,24 @@ import com.getcode.navigation.core.CodeNavigator
77
import com.getcode.navigation.core.NavOptions
88

99
/**
10-
* Navigate to a route, wrapping [AppRoute.Sheets] in [AppRoute.Main.Sheet]
11-
* so the [ModalBottomSheetSceneStrategy] renders them in a bottom sheet.
10+
* Open any [AppRoute] as a modal bottom sheet.
11+
*
12+
* Wraps [route] in [AppRoute.Main.Sheet] and navigates to it. If a sheet is already
13+
* open, the current sheet is animated closed before the new one opens.
1214
*/
13-
fun CodeNavigator.navigateTo(route: NavKey, options: NavOptions = NavOptions()) {
14-
val destination = if (route is AppRoute.Sheets) {
15-
AppRoute.Main.Sheet(route)
16-
} else {
17-
route
18-
}
19-
val needsSheet = destination is AppRoute.Main.Sheet
15+
fun CodeNavigator.openAsSheet(route: AppRoute, innerRoutes: List<AppRoute> = emptyList()) {
16+
val destination = AppRoute.Main.Sheet(route, innerRoutes)
2017
val hasSheet = backStack.any { it is AppRoute.Main.Sheet }
2118

22-
if (hasSheet && needsSheet) {
19+
if (hasSheet) {
2320
pendingSheetDismiss = {
2421
Snapshot.withMutableSnapshot {
2522
sheetGeneration++
26-
navigate(destination, options)
23+
navigate(destination)
2724
}
2825
}
2926
} else {
30-
navigate(destination, options)
27+
navigate(destination)
3128
}
3229
}
3330

@@ -37,20 +34,16 @@ fun CodeNavigator.navigateTo(route: NavKey, options: NavOptions = NavOptions())
3734
* so they appear inside the sheet rather than on the root backstack.
3835
*
3936
* If a sheet is already open and the new routes include a sheet, the current sheet
40-
* is animated closed before the new one opens. For direct navigation without
41-
* dismiss handling, use [navigate] directly.
37+
* is animated closed before the new one opens.
4238
*/
43-
fun CodeNavigator.navigateTo(routes: List<NavKey>, options: NavOptions = NavOptions()) {
39+
fun CodeNavigator.navigateAll(routes: List<NavKey>, options: NavOptions = NavOptions()) {
4440
if (routes.isEmpty()) return
4541

4642
val resolved = resolveRoutes(routes)
4743
val needsSheet = resolved.any { it is AppRoute.Main.Sheet }
4844
val hasSheet = backStack.any { it is AppRoute.Main.Sheet }
4945

5046
if (hasSheet && needsSheet) {
51-
// Animate the current sheet down, then open the new one.
52-
// The callback is invoked by ModalBottomSheetScene after the dismiss
53-
// animation completes and the old entry is removed from the backstack.
5447
pendingSheetDismiss = {
5548
Snapshot.withMutableSnapshot {
5649
sheetGeneration++

apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/tokens/SwapResult.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.flipcash.app.core.tokens
22

33
import android.os.Parcelable
4+
import com.getcode.solana.keys.Mint
45
import kotlinx.parcelize.Parcelize
56
import kotlinx.serialization.Serializable
67

@@ -13,4 +14,8 @@ sealed interface SwapResult : Parcelable {
1314
@Parcelize
1415
@Serializable
1516
data object Canceled : SwapResult
17+
18+
@Parcelize
19+
@Serializable
20+
data class OpenDeposit(val mint: Mint) : SwapResult
1621
}

apps/flipcash/features/contact-verification/src/main/kotlin/com/flipcash/app/contact/verification/VerificationFlowScreen.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ fun VerificationFlowScreen(
3636
FlowHost(
3737
initialStack = initialStack,
3838
resultStateRegistry = resultStateRegistry,
39-
onExit = { reason ->
39+
onExit = { reason, _ ->
4040
val result: VerificationResult = when (reason) {
4141
is FlowExitReason.Completed -> reason.result
4242
FlowExitReason.Canceled,

apps/flipcash/features/currency-creator/src/main/kotlin/com/flipcash/app/currencycreator/CurrencyCreatorFlowScreen.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ fun CurrencyCreatorFlowScreen(
142142
FlowHost(
143143
initialStack = initialStack,
144144
resultStateRegistry = resultStateRegistry,
145-
onExit = { reason ->
145+
onExit = { reason, _ ->
146146
val result: CurrencyCreatorResult = when (reason) {
147147
is FlowExitReason.Completed -> reason.result
148148
FlowExitReason.Canceled,

apps/flipcash/features/deposit/src/main/kotlin/com/flipcash/app/deposit/DepositFlowScreen.kt

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -57,22 +57,26 @@ fun DepositFlowScreen(
5757
FlowHost(
5858
initialStack = initialStack,
5959
resultStateRegistry = resultStateRegistry,
60-
onExit = { reason ->
60+
onExit = { reason, isSheetRoot ->
6161
val result: DepositResult = when (reason) {
6262
is FlowExitReason.Completed -> reason.result
6363
FlowExitReason.Canceled,
6464
FlowExitReason.BackedOutOfRoot -> DepositResult.Canceled
6565
}
66-
outerNavigator.deliverFlowResult(
67-
route = route,
68-
value = NavResultOrCanceled.ReturnValue(result),
69-
)
70-
when (result) {
71-
DepositResult.Success -> {
72-
outerNavigator.popUntil { it == AppRoute.Sheets.Menu }
73-
}
74-
DepositResult.Canceled -> {
75-
outerNavigator.pop()
66+
if (isSheetRoot) {
67+
outerNavigator.pop()
68+
} else {
69+
outerNavigator.deliverFlowResult(
70+
route = route,
71+
value = NavResultOrCanceled.ReturnValue(result),
72+
)
73+
when (result) {
74+
DepositResult.Success -> {
75+
outerNavigator.popUntil { it == AppRoute.Sheets.Menu }
76+
}
77+
DepositResult.Canceled -> {
78+
outerNavigator.pop()
79+
}
7680
}
7781
}
7882
},

apps/flipcash/features/discovery/src/main/kotlin/com/flipcash/app/discovery/TokenDiscoveryScreen.kt

Lines changed: 20 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.Column
44
import androidx.compose.foundation.layout.fillMaxSize
55
import androidx.compose.runtime.Composable
66
import androidx.compose.runtime.LaunchedEffect
7+
import androidx.compose.runtime.remember
78
import androidx.compose.ui.Alignment
89
import androidx.compose.ui.Modifier
910
import androidx.compose.ui.res.stringResource
@@ -22,46 +23,34 @@ import kotlinx.coroutines.flow.launchIn
2223
import kotlinx.coroutines.flow.map
2324
import kotlinx.coroutines.flow.onEach
2425

25-
26-
@Composable
27-
fun TokenDiscoverySheet() {
28-
val navigator = LocalCodeNavigator.current
29-
val viewModel = hiltViewModel<TokenDiscoveryViewModel>()
30-
31-
Column(
32-
modifier = Modifier.fillMaxSize(),
33-
horizontalAlignment = Alignment.CenterHorizontally,
34-
) {
35-
AppBarWithTitle(
36-
title = stringResource(R.string.title_discoverCurrencies),
37-
isInModal = true,
38-
titleAlignment = Alignment.CenterHorizontally,
39-
endContent = {
40-
AppBarDefaults.Close { navigator.hide() }
41-
},
42-
)
43-
TokenDiscoveryScreen(viewModel)
44-
}
45-
46-
TokenDiscoveryEventHandler(viewModel, navigator)
47-
}
48-
4926
@Composable
5027
fun TokenDiscoveryScreen() {
5128
val navigator = LocalCodeNavigator.current
5229
val viewModel = hiltViewModel<TokenDiscoveryViewModel>()
30+
val isSheetRoot = remember { navigator.backStack.size <= 1 }
5331

5432
Column(
5533
modifier = Modifier.fillMaxSize(),
5634
horizontalAlignment = Alignment.CenterHorizontally,
5735
) {
58-
AppBarWithTitle(
59-
title = stringResource(R.string.title_discoverCurrencies),
60-
isInModal = true,
61-
titleAlignment = Alignment.CenterHorizontally,
62-
backButton = true,
63-
onBackIconClicked = { navigator.pop() },
64-
)
36+
if (isSheetRoot) {
37+
AppBarWithTitle(
38+
title = stringResource(R.string.title_discoverCurrencies),
39+
isInModal = true,
40+
titleAlignment = Alignment.CenterHorizontally,
41+
endContent = {
42+
AppBarDefaults.Close { navigator.hide() }
43+
},
44+
)
45+
} else {
46+
AppBarWithTitle(
47+
title = stringResource(R.string.title_discoverCurrencies),
48+
isInModal = true,
49+
titleAlignment = Alignment.CenterHorizontally,
50+
backButton = true,
51+
onBackIconClicked = { navigator.pop() },
52+
)
53+
}
6554
TokenDiscoveryScreen(viewModel)
6655
}
6756

0 commit comments

Comments
 (0)