Skip to content

Commit d54f349

Browse files
committed
refactor: injectable Navigator instead of effects
1 parent 7485bef commit d54f349

File tree

7 files changed

+96
-136
lines changed

7 files changed

+96
-136
lines changed

app/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ dependencies {
192192
implementation(libs.material)
193193
implementation(libs.datastore.preferences)
194194
implementation(libs.kotlinx.datetime)
195+
implementation(libs.kotlinx.collections.immutable)
195196
implementation(libs.biometric)
196197
implementation(libs.zxing)
197198
implementation(libs.barcode.scanning)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package to.bitkit.di
2+
3+
import androidx.compose.runtime.mutableStateListOf
4+
import androidx.navigation3.runtime.NavKey
5+
import dagger.Module
6+
import dagger.Provides
7+
import dagger.hilt.InstallIn
8+
import dagger.hilt.components.SingletonComponent
9+
import to.bitkit.ui.nav.Navigator
10+
import to.bitkit.ui.nav.Routes
11+
import javax.inject.Singleton
12+
13+
@Module
14+
@InstallIn(SingletonComponent::class)
15+
object NavigationModule {
16+
@Singleton
17+
@Provides
18+
fun provideNavigator(): Navigator {
19+
val backStack = mutableStateListOf<NavKey>(Routes.Home)
20+
return Navigator(backStack)
21+
}
22+
}

app/src/main/java/to/bitkit/ui/ContentView.kt

Lines changed: 2 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,10 @@ import androidx.lifecycle.compose.LocalLifecycleOwner
2727
import androidx.lifecycle.compose.collectAsStateWithLifecycle
2828
import androidx.navigation3.runtime.NavKey
2929
import androidx.navigation3.runtime.entryProvider
30-
import androidx.navigation3.runtime.rememberNavBackStack
3130
import androidx.navigation3.ui.NavDisplay
32-
import kotlinx.coroutines.delay
3331
import to.bitkit.models.NodeLifecycleState
3432
import to.bitkit.ui.components.DrawerMenu
3533
import to.bitkit.ui.components.TabBar
36-
import to.bitkit.ui.nav.MS_NAV_DELAY
3734
import to.bitkit.ui.nav.Navigator
3835
import to.bitkit.ui.nav.Routes
3936
import to.bitkit.ui.nav.SheetSceneStrategy
@@ -54,16 +51,15 @@ import to.bitkit.viewmodels.AppViewModel
5451
import to.bitkit.viewmodels.BackupsViewModel
5552
import to.bitkit.viewmodels.BlocktankViewModel
5653
import to.bitkit.viewmodels.CurrencyViewModel
57-
import to.bitkit.viewmodels.MainScreenEffect
5854
import to.bitkit.viewmodels.RestoreState
59-
import to.bitkit.viewmodels.SendEffect
6055
import to.bitkit.viewmodels.SettingsViewModel
6156
import to.bitkit.viewmodels.TransferViewModel
6257
import to.bitkit.viewmodels.WalletViewModel
6358

6459
@Suppress("CyclomaticComplexMethod", "LongMethod")
6560
@Composable
6661
fun ContentView(
62+
navigator: Navigator,
6763
appViewModel: AppViewModel,
6864
walletViewModel: WalletViewModel,
6965
blocktankViewModel: BlocktankViewModel,
@@ -74,8 +70,6 @@ fun ContentView(
7470
backupsViewModel: BackupsViewModel,
7571
modifier: Modifier = Modifier,
7672
) {
77-
val backStack = rememberNavBackStack(Routes.Home)
78-
val navigator = remember(backStack) { Navigator(backStack) }
7973
val lightningConnectionsViewModel = hiltViewModel<LightningConnectionsViewModel>()
8074

8175
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
@@ -124,48 +118,6 @@ fun ContentView(
124118

125119
LaunchedEffect(Unit) { walletViewModel.handleHideBalanceOnOpen() }
126120

127-
LaunchedEffect(appViewModel) {
128-
appViewModel.mainScreenEffect.collect {
129-
when (it) {
130-
is MainScreenEffect.Navigate -> navigator.navigate(it.route)
131-
is MainScreenEffect.NavigateAndClearBackstack -> navigator.navigateAndClearBackstack(it.route)
132-
is MainScreenEffect.ProcessClipboardAutoRead -> {
133-
if (!navigator.isAtHome()) {
134-
navigator.navigateToHome()
135-
delay(MS_NAV_DELAY)
136-
}
137-
appViewModel.onScanResult(it.data)
138-
}
139-
140-
else -> Unit
141-
}
142-
}
143-
}
144-
145-
// Handle Send flow navigation effects
146-
LaunchedEffect(appViewModel, navigator) {
147-
appViewModel.sendEffect.collect { effect ->
148-
when (effect) {
149-
is SendEffect.NavigateToAddress -> navigator.navigate(Routes.Send.Address)
150-
is SendEffect.NavigateToAmount -> navigator.navigate(Routes.Send.Amount())
151-
is SendEffect.NavigateToScan -> navigator.navigate(Routes.Send.QrScanner)
152-
is SendEffect.NavigateToCoinSelection -> navigator.navigate(Routes.Send.CoinSelection)
153-
is SendEffect.NavigateToConfirm -> navigator.navigate(Routes.Send.Confirm)
154-
is SendEffect.NavigateToQuickPay -> navigator.navigate(Routes.Send.QuickPay)
155-
is SendEffect.NavigateToWithdrawConfirm -> navigator.navigate(Routes.Send.WithdrawConfirm)
156-
is SendEffect.NavigateToWithdrawError -> navigator.navigate(Routes.Send.WithdrawError)
157-
is SendEffect.NavigateToFee -> navigator.navigate(Routes.Send.FeeRate)
158-
is SendEffect.NavigateToFeeCustom -> navigator.navigate(Routes.Send.FeeCustom)
159-
is SendEffect.PaymentSuccess -> {
160-
appViewModel.clearClipboardForAutoRead()
161-
navigator.navigate(Routes.Send.Success)
162-
}
163-
164-
is SendEffect.PopBack -> navigator.popBackTo(effect.route)
165-
}
166-
}
167-
}
168-
169121
var walletIsInitializing by remember { mutableStateOf(nodeLifecycleState == NodeLifecycleState.Initializing) }
170122
var walletInitShouldFinish by remember { mutableStateOf(false) }
171123

@@ -257,7 +209,7 @@ fun ContentView(
257209
Box(modifier = modifier.fillMaxSize()) {
258210
Box(modifier = Modifier.fillMaxSize()) {
259211
NavDisplay(
260-
backStack = backStack,
212+
backStack = navigator.navBackStack,
261213
modifier = Modifier.fillMaxSize(),
262214
sceneStrategy = SheetSceneStrategy<NavKey>(),
263215
transitionSpec = Transitions.screenDefault,
@@ -305,7 +257,6 @@ fun ContentView(
305257
}
306258
)
307259

308-
// Use derivedStateOf to ensure reactive observation of backStack changes
309260
val showTabBar by remember {
310261
derivedStateOf { navigator.shouldShowTabBar() }
311262
}

app/src/main/java/to/bitkit/ui/MainActivity.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import dagger.hilt.android.AndroidEntryPoint
2626
import dev.chrisbanes.haze.hazeSource
2727
import dev.chrisbanes.haze.rememberHazeState
2828
import kotlinx.coroutines.CoroutineScope
29+
import javax.inject.Inject
2930
import kotlinx.coroutines.flow.map
3031
import kotlinx.coroutines.launch
3132
import to.bitkit.androidServices.LightningNodeService
@@ -57,6 +58,8 @@ import to.bitkit.viewmodels.WalletViewModel
5758

5859
@AndroidEntryPoint
5960
class MainActivity : FragmentActivity() {
61+
@Inject lateinit var navigator: Navigator
62+
6063
private val appViewModel by viewModels<AppViewModel>()
6164
private val walletViewModel by viewModels<WalletViewModel>()
6265
private val blocktankViewModel by viewModels<BlocktankViewModel>()
@@ -117,6 +120,7 @@ class MainActivity : FragmentActivity() {
117120
IsOnlineTracker(appViewModel)
118121
InactivityTracker(appViewModel, settingsViewModel) {
119122
ContentView(
123+
navigator = navigator,
120124
appViewModel = appViewModel,
121125
walletViewModel = walletViewModel,
122126
blocktankViewModel = blocktankViewModel,

app/src/main/java/to/bitkit/ui/nav/Navigator.kt

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,64 +2,69 @@ package to.bitkit.ui.nav
22

33
import androidx.compose.animation.core.AnimationConstants
44
import androidx.compose.runtime.Stable
5-
import androidx.navigation3.runtime.NavBackStack
65
import androidx.navigation3.runtime.NavKey
6+
import kotlinx.collections.immutable.ImmutableList
7+
import kotlinx.collections.immutable.toImmutableList
78

89
@Stable
9-
class Navigator(@PublishedApi internal val backStack: NavBackStack<NavKey>) {
10+
class Navigator(private val _backStack: MutableList<NavKey>) {
11+
12+
val backStack: ImmutableList<NavKey> get() = _backStack.toImmutableList()
13+
14+
/** Observable list for NavDisplay */
15+
val navBackStack: List<NavKey> get() = _backStack
1016

1117
fun navigate(route: Routes) {
12-
if (backStack.lastOrNull() != route) {
13-
backStack.add(route)
18+
if (_backStack.lastOrNull() != route) {
19+
_backStack.add(route)
1420
}
1521
}
1622

17-
fun goBack(): Boolean = backStack.removeLastOrNull() != null
23+
fun goBack(): Boolean = _backStack.removeLastOrNull() != null
1824

1925
fun popBackTo(route: Routes, inclusive: Boolean = false): Boolean {
20-
val index = backStack.indexOfFirst { it == route }
26+
val index = _backStack.indexOfFirst { it == route }
2127
if (index == -1) return false
2228

2329
val removeCount = if (inclusive) {
24-
backStack.size - index
30+
_backStack.size - index
2531
} else {
26-
backStack.size - index - 1
32+
_backStack.size - index - 1
2733
}
2834

2935
repeat(removeCount) {
30-
backStack.removeLastOrNull()
36+
_backStack.removeLastOrNull()
3137
}
3238
return true
3339
}
3440

3541
fun navigateToHome() {
36-
val homeIndex = backStack.indexOfFirst { it is Routes.Home }
42+
val homeIndex = _backStack.indexOfFirst { it is Routes.Home }
3743
if (homeIndex != -1) {
38-
while (backStack.size > homeIndex + 1) {
39-
backStack.removeLastOrNull()
44+
while (_backStack.size > homeIndex + 1) {
45+
_backStack.removeLastOrNull()
4046
}
4147
} else {
42-
while (backStack.size > 1) {
43-
backStack.removeLastOrNull()
48+
while (_backStack.size > 1) {
49+
_backStack.removeLastOrNull()
4450
}
45-
if (backStack.lastOrNull() !is Routes.Home) {
46-
backStack.add(Routes.Home)
51+
if (_backStack.lastOrNull() !is Routes.Home) {
52+
_backStack.add(Routes.Home)
4753
}
4854
}
4955
}
5056

51-
fun isAtHome(): Boolean = backStack.lastOrNull() is Routes.Home
57+
fun isAtHome(): Boolean = _backStack.lastOrNull() is Routes.Home
5258

5359
fun navigateAndClearBackstack(route: Routes) {
54-
backStack.clear()
55-
backStack.add(route)
60+
_backStack.clear()
61+
_backStack.add(route)
5662
}
5763

58-
fun shouldShowTabBar(): Boolean = when (backStack.lastOrNull()) {
64+
fun shouldShowTabBar(): Boolean = when (_backStack.lastOrNull()) {
5965
is Routes.Home, is Routes.Savings, is Routes.Spending, is Routes.Activity.All -> true
6066
else -> false
6167
}
6268
}
6369

64-
const val MS_NAV_DELAY = 100L
6570
const val MS_TRANSITION_SCREEN = AnimationConstants.DefaultDurationMillis.toLong() // 300ms

0 commit comments

Comments
 (0)