Skip to content

Commit

Permalink
Navigation refactor - NavigationRouter added (#1698)
Browse files Browse the repository at this point in the history
  • Loading branch information
Milan-Cerovsky authored Dec 2, 2024
1 parent 8a77a38 commit f60ba75
Show file tree
Hide file tree
Showing 24 changed files with 131 additions and 268 deletions.
4 changes: 4 additions & 0 deletions ui-lib/src/main/java/co/electriccoin/zcash/di/CoreModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import co.electriccoin.zcash.global.newInstance
import co.electriccoin.zcash.preference.EncryptedPreferenceProvider
import co.electriccoin.zcash.preference.StandardPreferenceProvider
import co.electriccoin.zcash.preference.model.entry.PreferenceKey
import co.electriccoin.zcash.ui.NavigationRouter
import co.electriccoin.zcash.ui.NavigationRouterImpl
import co.electriccoin.zcash.ui.preference.PersistableWalletPreferenceDefault
import co.electriccoin.zcash.ui.screen.update.AppUpdateChecker
import co.electriccoin.zcash.ui.screen.update.AppUpdateCheckerImpl
Expand Down Expand Up @@ -37,4 +39,6 @@ val coreModule =
factoryOf(::AppUpdateCheckerImpl) bind AppUpdateChecker::class

factory { AndroidConfigurationFactory.new() }

singleOf(::NavigationRouterImpl) bind NavigationRouter::class
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ val viewModelModule =
args = args,
observeAddressBookContacts = get(),
observeContactPicked = get(),
navigationRouter = get()
)
}
viewModel { (address: String?) ->
Expand All @@ -75,6 +76,7 @@ val viewModelModule =
validateContactAddress = get(),
validateContactName = get(),
saveContact = get(),
navigationRouter = get()
)
}
viewModelOf(::UpdateContactViewModel)
Expand Down
23 changes: 15 additions & 8 deletions ui-lib/src/main/java/co/electriccoin/zcash/ui/Navigation.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController
import androidx.navigation.NavOptionsBuilder
import androidx.navigation.NavType
Expand Down Expand Up @@ -101,6 +102,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json
import org.koin.compose.koinInject

// TODO [#1297]: Consider: Navigation passing complex data arguments different way
// TODO [#1297]: https://github.com/Electric-Coin-Company/zashi-android/issues/1297
Expand All @@ -117,16 +119,21 @@ internal fun MainActivity.Navigation() {
rememberSaveable { mutableStateOf(false) }
val (deleteWalletAuthentication, setDeleteWalletAuthentication) =
rememberSaveable { mutableStateOf(false) }
val navigationRouter = koinInject<NavigationRouter>()

LaunchedEffect(Unit) {
walletViewModel.navigationCommand.collect {
navController.navigateJustOnce(it)
}
}

LaunchedEffect(Unit) {
walletViewModel.backNavigationCommand.collect {
navController.popBackStack()
navigationRouter.observe().collect {
when (it) {
is NavigationCommand.Forward -> navController.navigate(it.route)
is NavigationCommand.Replace ->
navController.navigate(it.route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
restoreState = true
}
NavigationCommand.Back -> navController.popBackStack()
}
}
}

Expand Down
53 changes: 53 additions & 0 deletions ui-lib/src/main/java/co/electriccoin/zcash/ui/NavigationRouter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package co.electriccoin.zcash.ui

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.consumeAsFlow
import kotlinx.coroutines.launch

interface NavigationRouter {
fun forward(route: String)

fun replace(route: String)

fun back()

fun observe(): Flow<NavigationCommand>
}

class NavigationRouterImpl : NavigationRouter {
private val scope = CoroutineScope(Dispatchers.Main.immediate + SupervisorJob())

private val channel = Channel<NavigationCommand>()

override fun forward(route: String) {
scope.launch {
channel.send(NavigationCommand.Forward(route))
}
}

override fun replace(route: String) {
scope.launch {
channel.send(NavigationCommand.Replace(route))
}
}

override fun back() {
scope.launch {
channel.send(NavigationCommand.Back)
}
}

override fun observe() = channel.consumeAsFlow()
}

sealed interface NavigationCommand {
data class Forward(val route: String) : NavigationCommand

data class Replace(val route: String) : NavigationCommand

data object Back : NavigationCommand
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import cash.z.ecc.sdk.ANDROID_STATE_FLOW_TIMEOUT
import co.electriccoin.zcash.preference.StandardPreferenceProvider
import co.electriccoin.zcash.preference.model.entry.NullableBooleanPreferenceDefault
import co.electriccoin.zcash.spackle.Twig
import co.electriccoin.zcash.ui.NavigationRouter
import co.electriccoin.zcash.ui.NavigationTargets.EXCHANGE_RATE_OPT_IN
import co.electriccoin.zcash.ui.common.wallet.ExchangeRateState
import co.electriccoin.zcash.ui.common.wallet.RefreshLock
Expand All @@ -15,7 +16,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.WhileSubscribed
Expand All @@ -36,10 +36,6 @@ import kotlinx.coroutines.launch
import kotlin.time.Duration.Companion.minutes

interface ExchangeRateRepository {
val navigationCommand: MutableSharedFlow<String>

val backNavigationCommand: MutableSharedFlow<Unit>

val isExchangeRateUsdOptedIn: StateFlow<Boolean?>

val state: StateFlow<ExchangeRateState>
Expand All @@ -54,6 +50,7 @@ interface ExchangeRateRepository {
class ExchangeRateRepositoryImpl(
private val walletRepository: WalletRepository,
private val standardPreferenceProvider: StandardPreferenceProvider,
private val navigationRouter: NavigationRouter,
) : ExchangeRateRepository {
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())

Expand Down Expand Up @@ -164,10 +161,6 @@ class ExchangeRateRepositoryImpl(
initialValue = ExchangeRateState.OptedOut
)

override val navigationCommand = MutableSharedFlow<String>()

override val backNavigationCommand = MutableSharedFlow<Unit>()

override fun refreshExchangeRateUsd() {
refreshExchangeRateUsdInternal()
}
Expand All @@ -182,27 +175,20 @@ class ExchangeRateRepositoryImpl(
}

override fun optInExchangeRateUsd(optIn: Boolean) {
scope.launch {
setNullableBooleanPreference(StandardPreferenceKeys.EXCHANGE_RATE_OPTED_IN, optIn)
backNavigationCommand.emit(Unit)
}
setNullableBooleanPreference(StandardPreferenceKeys.EXCHANGE_RATE_OPTED_IN, optIn)
navigationRouter.back()
}

override fun dismissOptInExchangeRateUsd() {
scope.launch {
setNullableBooleanPreference(StandardPreferenceKeys.EXCHANGE_RATE_OPTED_IN, false)
backNavigationCommand.emit(Unit)
}
setNullableBooleanPreference(StandardPreferenceKeys.EXCHANGE_RATE_OPTED_IN, false)
navigationRouter.back()
}

private fun dismissWidgetOptInExchangeRateUsd() {
setNullableBooleanPreference(StandardPreferenceKeys.EXCHANGE_RATE_OPTED_IN, false)
}

private fun showOptInExchangeRateUsd() =
scope.launch {
navigationCommand.emit(EXCHANGE_RATE_OPT_IN)
}
private fun showOptInExchangeRateUsd() = navigationRouter.forward(EXCHANGE_RATE_OPT_IN)

private fun nullableBooleanStateFlow(default: NullableBooleanPreferenceDefault): StateFlow<Boolean?> =
flow {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,6 @@ class WalletViewModel(
private val isFlexaAvailable: IsFlexaAvailableUseCase,
private val getSynchronizer: GetSynchronizerUseCase
) : AndroidViewModel(application) {
val navigationCommand = exchangeRateRepository.navigationCommand

val backNavigationCommand = exchangeRateRepository.backNavigationCommand

val synchronizer = walletRepository.synchronizer

val walletRestoringState = walletRepository.walletRestoringState
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@ package co.electriccoin.zcash.ui.screen.addressbook

import androidx.activity.compose.BackHandler
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import co.electriccoin.zcash.di.koinActivityViewModel
import co.electriccoin.zcash.ui.common.compose.LocalNavController
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
import co.electriccoin.zcash.ui.screen.addressbook.view.AddressBookView
import co.electriccoin.zcash.ui.screen.addressbook.viewmodel.AddressBookViewModel
Expand All @@ -17,24 +15,11 @@ import org.koin.core.parameter.parametersOf

@Composable
internal fun WrapAddressBook(args: AddressBookArgs) {
val navController = LocalNavController.current
val walletViewModel = koinActivityViewModel<WalletViewModel>()
val viewModel = koinViewModel<AddressBookViewModel> { parametersOf(args) }
val walletState by walletViewModel.walletStateInformation.collectAsStateWithLifecycle()
val state by viewModel.state.collectAsStateWithLifecycle()

LaunchedEffect(Unit) {
viewModel.navigationCommand.collect {
navController.navigate(it)
}
}

LaunchedEffect(Unit) {
viewModel.backNavigationCommand.collect {
navController.popBackStack()
}
}

BackHandler {
state.onBack()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package co.electriccoin.zcash.ui.screen.addressbook.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import cash.z.ecc.sdk.ANDROID_STATE_FLOW_TIMEOUT
import co.electriccoin.zcash.ui.NavigationRouter
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.common.model.AddressBookContact
import co.electriccoin.zcash.ui.common.usecase.ObserveAddressBookContactsUseCase
Expand All @@ -16,7 +17,6 @@ import co.electriccoin.zcash.ui.screen.contact.AddContactArgs
import co.electriccoin.zcash.ui.screen.contact.UpdateContactArgs
import co.electriccoin.zcash.ui.screen.scan.ScanNavigationArgs
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.WhileSubscribed
import kotlinx.coroutines.flow.flowOn
Expand All @@ -27,7 +27,8 @@ import kotlinx.coroutines.launch
class AddressBookViewModel(
observeAddressBookContacts: ObserveAddressBookContactsUseCase,
private val args: AddressBookArgs,
private val observeContactPicked: ObserveContactPickedUseCase
private val observeContactPicked: ObserveContactPickedUseCase,
private val navigationRouter: NavigationRouter,
) : ViewModel() {
val state =
observeAddressBookContacts()
Expand All @@ -39,10 +40,6 @@ class AddressBookViewModel(
initialValue = createState(contacts = null)
)

val navigationCommand = MutableSharedFlow<String>()

val backNavigationCommand = MutableSharedFlow<Unit>()

private fun createState(contacts: List<AddressBookContact>?) =
AddressBookState(
isLoading = contacts == null,
Expand Down Expand Up @@ -80,34 +77,25 @@ class AddressBookViewModel(
.joinToString(separator = "")
)

private fun onBack() =
viewModelScope.launch {
backNavigationCommand.emit(Unit)
}
private fun onBack() = navigationRouter.back()

private fun onContactClick(contact: AddressBookContact) =
viewModelScope.launch {
when (args) {
AddressBookArgs.DEFAULT -> {
navigationCommand.emit(UpdateContactArgs(contact.address))
navigationRouter.forward(UpdateContactArgs(contact.address))
}

AddressBookArgs.PICK_CONTACT -> {
observeContactPicked.onContactPicked(contact)
backNavigationCommand.emit(Unit)
navigationRouter.back()
}
}
}

private fun onAddContactManuallyClick() =
viewModelScope.launch {
navigationCommand.emit(AddContactArgs(null))
}
private fun onAddContactManuallyClick() = navigationRouter.forward(AddContactArgs(null))

private fun onScanContactClick() =
viewModelScope.launch {
navigationCommand.emit(ScanNavigationArgs(ScanNavigationArgs.ADDRESS_BOOK))
}
private fun onScanContactClick() = navigationRouter.forward(ScanNavigationArgs(ScanNavigationArgs.ADDRESS_BOOK))
}

private const val ADDRESS_MAX_LENGTH = 20
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ package co.electriccoin.zcash.ui.screen.advancedsettings

import androidx.activity.compose.BackHandler
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import co.electriccoin.zcash.di.koinActivityViewModel
import co.electriccoin.zcash.ui.common.compose.LocalNavController
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
import co.electriccoin.zcash.ui.screen.advancedsettings.view.AdvancedSettings
import co.electriccoin.zcash.ui.screen.advancedsettings.viewmodel.AdvancedSettingsViewModel
Expand All @@ -20,7 +18,6 @@ internal fun WrapAdvancedSettings(
goExportPrivateData: () -> Unit,
goSeedRecovery: () -> Unit,
) {
val navController = LocalNavController.current
val walletViewModel = koinActivityViewModel<WalletViewModel>()
val viewModel = koinViewModel<AdvancedSettingsViewModel>()
val walletState = walletViewModel.walletStateInformation.collectAsStateWithLifecycle().value
Expand All @@ -42,18 +39,6 @@ internal fun WrapAdvancedSettings(
viewModel.onBack()
}

LaunchedEffect(Unit) {
viewModel.navigationCommand.collect {
navController.navigate(it)
}
}

LaunchedEffect(Unit) {
viewModel.backNavigationCommand.collect {
navController.popBackStack()
}
}

AdvancedSettings(
state = state,
topAppBarSubTitleState = walletState,
Expand Down
Loading

0 comments on commit f60ba75

Please sign in to comment.