Skip to content

Commit 292d428

Browse files
committed
feat: migrate to Nav3
Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent e384e5a commit 292d428

110 files changed

Lines changed: 3477 additions & 3818 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/flipcash/app/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ dependencies {
200200
implementation(project(":libs:quickresponse"))
201201
implementation(project(":ui:biometrics"))
202202
implementation(project(":ui:components"))
203+
implementation(project(":ui:navigation"))
203204
implementation(project(":ui:scanner"))
204205
implementation(project(":ui:resources"))
205206
implementation(project(":ui:theme"))

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

Lines changed: 116 additions & 184 deletions
Large diffs are not rendered by default.
Lines changed: 14 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,28 @@
11
package com.flipcash.app.internal.ui.navigation
22

3-
import android.os.Parcelable
43
import androidx.compose.runtime.Composable
54
import androidx.compose.runtime.rememberCoroutineScope
6-
import cafe.adriel.voyager.core.registry.ScreenRegistry
7-
import cafe.adriel.voyager.core.screen.Screen
85
import com.flipcash.app.core.AppRoute
96
import com.flipcash.app.internal.ui.HomeViewModel
107
import com.getcode.navigation.core.LocalCodeNavigator
118
import com.getcode.navigation.extensions.getActivityScopedViewModel
12-
import com.getcode.navigation.screens.AppScreen
139
import com.getcode.ui.components.restrictions.ContentRestrictedView
1410
import com.getcode.ui.core.RestrictionType
1511
import kotlinx.coroutines.launch
16-
import kotlinx.parcelize.IgnoredOnParcel
17-
import kotlinx.parcelize.Parcelize
1812

19-
@Parcelize
20-
class AppRestrictedScreen(private val restrictionType: RestrictionType): AppScreen, Parcelable {
21-
22-
@IgnoredOnParcel
23-
override val testTag: String = "app_restricted_screen"
24-
25-
@Composable
26-
override fun ScreenContent() {
27-
val homeViewModel = getActivityScopedViewModel<HomeViewModel>()
28-
val navigator = LocalCodeNavigator.current
29-
val coroutineScope = rememberCoroutineScope()
30-
ContentRestrictedView(restrictionType) {
31-
coroutineScope.launch {
32-
homeViewModel.logout()
33-
.onSuccess {
34-
navigator.replaceAll(
35-
ScreenRegistry.get(
36-
AppRoute.Onboarding.Login()
37-
)
38-
)
39-
}
40-
}
13+
@Composable
14+
fun AppRestrictedScreen(restrictionType: RestrictionType) {
15+
val homeViewModel = getActivityScopedViewModel<HomeViewModel>()
16+
val navigator = LocalCodeNavigator.current
17+
val coroutineScope = rememberCoroutineScope()
18+
ContentRestrictedView(restrictionType) {
19+
coroutineScope.launch {
20+
homeViewModel.logout()
21+
.onSuccess {
22+
navigator.replaceAll(
23+
AppRoute.Onboarding.Login()
24+
)
25+
}
4126
}
4227
}
43-
}
28+
}
Lines changed: 163 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
package com.flipcash.app.internal.ui.navigation
22

3+
import androidx.activity.compose.BackHandler
4+
import androidx.compose.animation.slideInHorizontally
5+
import androidx.compose.animation.slideOutHorizontally
6+
import androidx.compose.animation.togetherWith
37
import androidx.compose.runtime.Composable
4-
import cafe.adriel.voyager.core.registry.ScreenRegistry
5-
import cafe.adriel.voyager.core.screen.Screen
8+
import androidx.compose.runtime.CompositionLocalProvider
9+
import androidx.compose.runtime.remember
10+
import androidx.navigation3.runtime.NavEntry
11+
import androidx.navigation3.runtime.NavKey
12+
import androidx.navigation3.runtime.rememberNavBackStack
13+
import androidx.navigation3.scene.SinglePaneSceneStrategy
614
import com.flipcash.app.advanced.AdvancedFeaturesScreen
715
import com.flipcash.app.appsettings.AppSettingsScreen
816
import com.flipcash.app.backupkey.BackupKeyScreen
@@ -13,6 +21,7 @@ import com.flipcash.app.contact.verification.VerificationFlowScreen
1321
import com.flipcash.app.core.AppRoute
1422
import com.flipcash.app.currency.RegionSelectionScreen
1523
import com.flipcash.app.deposit.DepositScreen
24+
import com.flipcash.app.internal.ui.navigation.decorators.rememberNavMessagingEntryDecorator
1625
import com.flipcash.app.lab.LabsScreen
1726
import com.flipcash.app.lab.PreloadLabs
1827
import com.flipcash.app.lab.StandaloneLabsScreen
@@ -39,151 +48,211 @@ import com.flipcash.app.withdrawal.WithdrawalConfirmationScreen
3948
import com.flipcash.app.withdrawal.WithdrawalDestinationScreen
4049
import com.flipcash.app.withdrawal.WithdrawalEntryScreen
4150
import com.flipcash.app.withdrawal.WithdrawalFlow
42-
import com.getcode.navigation.screens.ModalScreen
51+
import com.getcode.navigation.AppNavHost
52+
import com.getcode.navigation.core.LocalCodeNavigator
53+
import com.getcode.navigation.core.rememberCodeNavigator
54+
import com.getcode.navigation.metadata
55+
import com.getcode.navigation.results.NavResultStateRegistry
56+
import com.getcode.navigation.scenes.LocalBottomSheetDismissDispatcher
57+
import com.getcode.navigation.scenes.ModalBottomSheetSceneStrategy
58+
import com.getcode.ui.components.bars.BarManager
59+
import dev.theolm.rinku.DeepLink
4360

4461
@Composable
45-
internal fun AppScreenContent(content: @Composable () -> Unit) {
46-
ScreenRegistry {
47-
register<AppRoute.Onboarding.Login> {
48-
LoginRouter(it.seed, it.fromDeeplink)
62+
fun AppPreloads() {
63+
PreloadBalance()
64+
PreloadLabs()
65+
}
66+
67+
fun appEntryProvider(
68+
key: NavKey,
69+
resultStateRegistry: NavResultStateRegistry,
70+
barManager: BarManager,
71+
deepLink: () -> DeepLink?,
72+
): NavEntry<NavKey> {
73+
return when (key) {
74+
is AppRoute.Loading -> NavEntry(key = key, metadata = key.metadata()) {
75+
MainRoot(deepLink)
4976
}
5077

51-
register<AppRoute.Onboarding.SeedInput> {
78+
is AppRoute.Onboarding.Login -> NavEntry(key = key, metadata = key.metadata()) {
79+
LoginRouter(key.seed, key.fromDeeplink)
80+
}
81+
is AppRoute.Onboarding.SeedInput -> NavEntry(key = key, metadata = key.metadata()) {
5282
SeedInputScreen()
5383
}
54-
55-
register<AppRoute.Onboarding.AccessKey> {
84+
is AppRoute.Onboarding.AccessKey -> NavEntry(key = key, metadata = key.metadata()) {
5685
AccessKeyScreen()
5786
}
58-
59-
register<AppRoute.Onboarding.AccessKeySavedLocation> {
87+
is AppRoute.Onboarding.AccessKeySavedLocation -> NavEntry(key = key, metadata = key.metadata()) {
6088
PhotoAccessKeyScreen()
6189
}
62-
63-
register<AppRoute.Onboarding.Purchase> {
64-
PurchaseAccountScreen(it.fromLogin)
90+
is AppRoute.Onboarding.Purchase -> NavEntry(key = key, metadata = key.metadata()) {
91+
PurchaseAccountScreen(key.fromLogin)
6592
}
66-
67-
register<AppRoute.Sheets.Lab> {
68-
StandaloneLabsScreen()
93+
is AppRoute.Onboarding.NotificationPermission,
94+
is AppRoute.Onboarding.CameraPermission -> NavEntry(key = key, metadata = key.metadata()) {
95+
// Deprecated — permissions requested at time of use
6996
}
7097

71-
register<AppRoute.Main.AppRestricted> {
72-
AppRestrictedScreen(it.restrictionType)
98+
is AppRoute.Main.Sheet -> sheetEntry(key, resultStateRegistry, barManager)
99+
is AppRoute.Main.AppRestricted -> NavEntry(key = key, metadata = key.metadata()) {
100+
AppRestrictedScreen(key.restrictionType)
73101
}
74-
75-
register<AppRoute.Main.Scanner> {
76-
ScannerScreen(it.deeplink)
102+
is AppRoute.Main.Scanner -> NavEntry(key = key, metadata = key.metadata()) {
103+
ScannerScreen(key.deeplink)
77104
}
78-
79-
register<AppRoute.Main.Give> {
80-
CashScreen(it.mint, it.fromTokenInfo)
105+
is AppRoute.Sheets.Give -> NavEntry(key = key, metadata = key.metadata()) {
106+
CashScreen(key.mint, key.fromTokenInfo)
81107
}
82-
83-
register<AppRoute.Token.Info> {
84-
TokenInfoScreen(it.mint, it.forNeededFunds, it.fromDeeplink)
108+
is AppRoute.Main.RegionSelection -> NavEntry(key = key, metadata = key.metadata()) {
109+
RegionSelectionScreen(key.kind)
85110
}
86111

87-
register<AppRoute.Token.Transactions> {
88-
TransactionHistoryScreen(it.mint)
112+
is AppRoute.Token.Info -> NavEntry(key = key, metadata = key.metadata()) {
113+
TokenInfoScreen(key.mint, key.forNeededFunds, key.fromDeeplink)
89114
}
90-
91-
register<AppRoute.Token.SwapTransact> {
92-
BuySellFlow.start(it.forNeededFunds)
93-
TokenBuySellEntryScreen(it.purpose)
115+
is AppRoute.Token.Transactions -> NavEntry(key = key, metadata = key.metadata()) {
116+
TransactionHistoryScreen(key.mint)
94117
}
95-
96-
register<AppRoute.Token.TxProcessing> {
97-
TokenTxProcessingScreen(it.swapId, it.awaitExternalWallet)
118+
is AppRoute.Token.SwapTransact -> NavEntry(key = key, metadata = key.metadata()) {
119+
remember { BuySellFlow.start(key.forNeededFunds) }
120+
TokenBuySellEntryScreen(key.purpose)
98121
}
99-
100-
register<AppRoute.Token.SellReceipt> {
122+
is AppRoute.Token.TxProcessing -> NavEntry(key = key, metadata = key.metadata()) {
123+
TokenTxProcessingScreen(key.swapId, key.awaitExternalWallet)
124+
}
125+
is AppRoute.Token.SellReceipt -> NavEntry(key = key, metadata = key.metadata()) {
101126
TokenSellReceiptScreen()
102127
}
103128

104-
register<AppRoute.Sheets.TokenSelection> {
105-
TokenSelectScreen(it.purpose)
129+
is AppRoute.Sheets.TokenSelection -> NavEntry(key = key, metadata = key.metadata()) {
130+
TokenSelectScreen(key.purpose)
106131
}
107-
108-
register<AppRoute.Sheets.Wallet> {
132+
is AppRoute.Sheets.Wallet -> NavEntry(key = key, metadata = key.metadata()) {
109133
BalanceScreen()
110134
}
111-
112-
register<AppRoute.Main.RegionSelection> {
113-
RegionSelectionScreen(it.kind)
114-
}
115-
116-
register<AppRoute.Sheets.ShareApp> {
135+
is AppRoute.Sheets.ShareApp -> NavEntry(key = key, metadata = key.metadata()) {
117136
ShareAppScreen()
118137
}
138+
is AppRoute.Sheets.Menu -> NavEntry(key = key, metadata = key.metadata()) {
139+
MenuScreen()
140+
}
141+
is AppRoute.Sheets.Lab -> NavEntry(key = key, metadata = key.metadata()) {
142+
StandaloneLabsScreen()
143+
}
119144

120-
register<AppRoute.Verification> {
145+
is AppRoute.Verification -> NavEntry(key = key, metadata = key.metadata()) {
121146
VerificationFlowScreen(
122-
origin = it.origin,
123-
target = it.target,
124-
includePhone = it.includePhone,
125-
includeEmail = it.includeEmail,
126-
emailAddress = it.email,
127-
emailVerificationCode = it.emailVerificationCode
147+
origin = key.origin,
148+
target = key.target,
149+
includePhone = key.includePhone,
150+
includeEmail = key.includeEmail,
151+
emailAddress = key.email,
152+
emailVerificationCode = key.emailVerificationCode,
128153
)
129154
}
130155

131-
register<AppRoute.OnRamp.ProviderList> {
132-
OnRampFlowTracker.start(it.from)
156+
is AppRoute.OnRamp.ProviderList -> NavEntry(key = key, metadata = key.metadata()) {
157+
remember { OnRampFlowTracker.start(key.from) }
133158
OnRampProviderListScreen(
134-
neededAmount = it.neededAmount?.quarks,
135-
neededCurrency = it.neededAmount?.currencyCode
159+
neededAmount = key.neededAmount?.quarks,
160+
neededCurrency = key.neededAmount?.currencyCode,
136161
)
137162
}
138-
139-
register<AppRoute.OnRamp.AmountEntry> {
163+
is AppRoute.OnRamp.AmountEntry -> NavEntry(key = key, metadata = key.metadata()) {
140164
OnRampCustomAmountScreen()
141165
}
142166

143-
register<AppRoute.Sheets.Menu> {
144-
MenuScreen()
145-
}
146-
147-
register<AppRoute.Menu.AppSettings> {
167+
is AppRoute.Menu.AppSettings -> NavEntry(key = key, metadata = key.metadata()) {
148168
AppSettingsScreen()
149169
}
150-
151-
register<AppRoute.Menu.Lab> {
170+
is AppRoute.Menu.Lab -> NavEntry(key = key, metadata = key.metadata()) {
152171
LabsScreen()
153172
}
154-
155-
register<AppRoute.Transfers.Withdrawal.Amount> {
156-
WithdrawalFlow.start()
157-
WithdrawalEntryScreen(it.mint)
173+
is AppRoute.Menu.MyAccount -> NavEntry(key = key, metadata = key.metadata()) {
174+
MyAccountScreen()
175+
}
176+
is AppRoute.Menu.Deposit -> NavEntry(key = key, metadata = key.metadata()) {
177+
DepositScreen(key.mint)
178+
}
179+
is AppRoute.Menu.BackupKey -> NavEntry(key = key, metadata = key.metadata()) {
180+
BackupKeyScreen()
181+
}
182+
is AppRoute.Menu.AdvancedFeatures -> NavEntry(key = key, metadata = key.metadata()) {
183+
AdvancedFeaturesScreen()
158184
}
159185

160-
register<AppRoute.Transfers.Withdrawal.Destination> {
186+
is AppRoute.Transfers.Withdrawal.Amount -> NavEntry(key = key, metadata = key.metadata()) {
187+
remember { WithdrawalFlow.start() }
188+
WithdrawalEntryScreen(key.mint)
189+
}
190+
is AppRoute.Transfers.Withdrawal.Destination -> NavEntry(key = key, metadata = key.metadata()) {
161191
WithdrawalDestinationScreen()
162192
}
163-
164-
register<AppRoute.Transfers.Withdrawal.Confirmation> {
193+
is AppRoute.Transfers.Withdrawal.Confirmation -> NavEntry(key = key, metadata = key.metadata()) {
165194
WithdrawalConfirmationScreen()
166195
}
167196

168-
register<AppRoute.Menu.MyAccount> {
169-
MyAccountScreen()
170-
}
171-
172-
register<AppRoute.Menu.Deposit> {
173-
DepositScreen(it.mint)
197+
else -> error("Unknown route: $key")
198+
}
199+
}
200+
201+
/**
202+
* Sheet entry with nested [AppNavHost] for inner-sheet navigation.
203+
* Uses slide transitions for intra-sheet navigation.
204+
*/
205+
private fun sheetEntry(
206+
key: AppRoute.Main.Sheet,
207+
resultStateRegistry: NavResultStateRegistry,
208+
barManager: BarManager,
209+
): NavEntry<NavKey> {
210+
return NavEntry(key = key, metadata = key.metadata()) {
211+
val sheetDismissDispatcher = LocalBottomSheetDismissDispatcher.current
212+
val backStack = rememberNavBackStack(key.initialRoute)
213+
val navigator = rememberCodeNavigator(
214+
backStack = backStack,
215+
resultStateRegistry = resultStateRegistry,
216+
onRootReached = { sheetDismissDispatcher() },
217+
)
218+
219+
val onBack = {
220+
if (backStack.size > 1) {
221+
backStack.removeAt(backStack.lastIndex)
222+
} else {
223+
sheetDismissDispatcher()
224+
}
174225
}
175226

176-
register<AppRoute.Menu.BackupKey> {
177-
BackupKeyScreen()
178-
}
227+
CompositionLocalProvider(LocalCodeNavigator provides navigator) {
228+
AppNavHost(
229+
navigator = navigator,
230+
resultStateRegistry = resultStateRegistry,
231+
decorators = listOf(
232+
rememberNavMessagingEntryDecorator(navigator.backStack, barManager)
233+
),
234+
sceneStrategy = ModalBottomSheetSceneStrategy<NavKey>(navigator.resultStore) {
235+
navigator.backStack.getOrNull(navigator.backStack.lastIndex - 1)
236+
} then SinglePaneSceneStrategy(),
237+
transitionSpec = {
238+
slideInHorizontally(initialOffsetX = { it }) togetherWith
239+
slideOutHorizontally(targetOffsetX = { -it })
240+
},
241+
popTransitionSpec = {
242+
slideInHorizontally(initialOffsetX = { -it }) togetherWith
243+
slideOutHorizontally(targetOffsetX = { it })
244+
},
245+
predictivePopTransitionSpec = {
246+
slideInHorizontally(initialOffsetX = { -it }) togetherWith
247+
slideOutHorizontally(targetOffsetX = { it })
248+
},
249+
onBack = { onBack() },
250+
entryProvider = { innerKey ->
251+
appEntryProvider(innerKey, resultStateRegistry, barManager, deepLink = { null })
252+
}
253+
)
179254

180-
register<AppRoute.Menu.AdvancedFeatures> {
181-
AdvancedFeaturesScreen()
255+
BackHandler { onBack() }
182256
}
183257
}
184-
185-
PreloadBalance()
186-
PreloadLabs()
187-
188-
content()
189-
}
258+
}

0 commit comments

Comments
 (0)