[#185] Refactor navigation
This moves navigation to its own file, simplifies the MainActivity, and moves Android integration composables to their own files. Navigation still does not have tests, so a separate change will need to be implemented to add testing to navigation.
This commit is contained in:
parent
1bd5e7518e
commit
334dc3c41f
|
@ -32,6 +32,7 @@ import cash.z.ecc.sdk.fixture.WalletAddressFixture
|
|||
import co.electriccoin.zcash.spackle.FirebaseTestLabUtil
|
||||
import co.electriccoin.zcash.test.UiTestPrerequisites
|
||||
import co.electriccoin.zcash.ui.MainActivity
|
||||
import co.electriccoin.zcash.ui.NavigationTargets
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.design.component.ConfigurationOverride
|
||||
import co.electriccoin.zcash.ui.design.component.UiMode
|
||||
|
@ -301,7 +302,7 @@ class ScreenshotTest : UiTestPrerequisites() {
|
|||
composeTestRule.waitUntil { composeTestRule.activity.walletViewModel.walletSnapshot.value != null }
|
||||
requestZecScreenshots(resContext, tag, composeTestRule)
|
||||
|
||||
navigateTo(MainActivity.NAV_HOME)
|
||||
navigateTo(NavigationTargets.HOME)
|
||||
composeTestRule.waitUntil { composeTestRule.activity.walletViewModel.secretState.value is SecretState.Ready }
|
||||
|
||||
composeTestRule.onNode(hasText(resContext.getString(R.string.home_button_send))).also {
|
||||
|
@ -313,7 +314,7 @@ class ScreenshotTest : UiTestPrerequisites() {
|
|||
composeTestRule.waitUntil { composeTestRule.activity.walletViewModel.walletSnapshot.value != null }
|
||||
sendZecScreenshots(resContext, tag, composeTestRule)
|
||||
|
||||
navigateTo(MainActivity.NAV_HOME)
|
||||
navigateTo(NavigationTargets.HOME)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
package co.electriccoin.zcash.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.os.SystemClock
|
||||
import androidx.activity.ComponentActivity
|
||||
|
@ -11,67 +8,31 @@ import androidx.activity.viewModels
|
|||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.NavOptionsBuilder
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import cash.z.ecc.sdk.model.ZecRequest
|
||||
import cash.z.ecc.sdk.send
|
||||
import co.electriccoin.zcash.ui.design.compat.FontCompat
|
||||
import co.electriccoin.zcash.ui.design.component.ConfigurationOverride
|
||||
import co.electriccoin.zcash.ui.design.component.GradientSurface
|
||||
import co.electriccoin.zcash.ui.design.component.Override
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.screen.about.WrapAbout
|
||||
import co.electriccoin.zcash.ui.screen.address.view.WalletAddresses
|
||||
import co.electriccoin.zcash.ui.screen.backup.WrapBackup
|
||||
import co.electriccoin.zcash.ui.screen.backup.copyToClipboard
|
||||
import co.electriccoin.zcash.ui.screen.home.WrapHome
|
||||
import co.electriccoin.zcash.ui.screen.home.model.spendableBalance
|
||||
import co.electriccoin.zcash.ui.screen.home.viewmodel.CheckUpdateViewModel
|
||||
import co.electriccoin.zcash.ui.screen.home.viewmodel.SecretState
|
||||
import co.electriccoin.zcash.ui.screen.home.viewmodel.WalletViewModel
|
||||
import co.electriccoin.zcash.ui.screen.onboarding.WrapOnboarding
|
||||
import co.electriccoin.zcash.ui.screen.profile.WrapProfile
|
||||
import co.electriccoin.zcash.ui.screen.request.view.Request
|
||||
import co.electriccoin.zcash.ui.screen.scan.WrapScan
|
||||
import co.electriccoin.zcash.ui.screen.seed.view.Seed
|
||||
import co.electriccoin.zcash.ui.screen.send.view.Send
|
||||
import co.electriccoin.zcash.ui.screen.settings.WrapSettings
|
||||
import co.electriccoin.zcash.ui.screen.support.WrapSupport
|
||||
import co.electriccoin.zcash.ui.screen.update.AppUpdateCheckerImp
|
||||
import co.electriccoin.zcash.ui.screen.update.WrapUpdate
|
||||
import co.electriccoin.zcash.ui.screen.update.model.UpdateState
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
val walletViewModel by viewModels<WalletViewModel>()
|
||||
|
||||
// TODO [#382]: https://github.com/zcash/secant-android-wallet/issues/382
|
||||
// TODO [#403]: https://github.com/zcash/secant-android-wallet/issues/403
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
val checkUpdateViewModel by viewModels<CheckUpdateViewModel> {
|
||||
CheckUpdateViewModel.CheckUpdateViewModelFactory(
|
||||
application,
|
||||
AppUpdateCheckerImp.new()
|
||||
)
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
|
||||
lateinit var navControllerForTesting: NavHostController
|
||||
|
||||
|
@ -128,11 +89,15 @@ class MainActivity : ComponentActivity() {
|
|||
SecretState.None -> {
|
||||
WrapOnboarding()
|
||||
}
|
||||
is SecretState.NeedsBackup -> WrapBackup(
|
||||
secretState.persistableWallet,
|
||||
onBackupComplete = { walletViewModel.persistBackupComplete() }
|
||||
)
|
||||
is SecretState.Ready -> Navigation()
|
||||
is SecretState.NeedsBackup -> {
|
||||
WrapBackup(
|
||||
secretState.persistableWallet,
|
||||
onBackupComplete = { walletViewModel.persistBackupComplete() }
|
||||
)
|
||||
}
|
||||
is SecretState.Ready -> {
|
||||
Navigation()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -148,282 +113,8 @@ class MainActivity : ComponentActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
@SuppressWarnings("LongMethod")
|
||||
private fun Navigation() {
|
||||
val navController = rememberNavController().also {
|
||||
// This suppress is necessary, as this is how we set up the nav controller for tests.
|
||||
@SuppressLint("RestrictedApi")
|
||||
navControllerForTesting = it
|
||||
}
|
||||
|
||||
NavHost(navController = navController, startDestination = NAV_HOME) {
|
||||
composable(NAV_HOME) {
|
||||
WrapHome(
|
||||
goScan = { navController.navigateJustOnce(NAV_SCAN) },
|
||||
goProfile = { navController.navigateJustOnce(NAV_PROFILE) },
|
||||
goSend = { navController.navigateJustOnce(NAV_SEND) },
|
||||
goRequest = { navController.navigateJustOnce(NAV_REQUEST) }
|
||||
)
|
||||
|
||||
WrapCheckForUpdate()
|
||||
}
|
||||
composable(NAV_PROFILE) {
|
||||
WrapProfile(
|
||||
onBack = { navController.popBackStackJustOnce(NAV_PROFILE) },
|
||||
onAddressDetails = { navController.navigateJustOnce(NAV_WALLET_ADDRESS_DETAILS) },
|
||||
onAddressBook = { },
|
||||
onSettings = { navController.navigateJustOnce(NAV_SETTINGS) },
|
||||
onCoinholderVote = { },
|
||||
onSupport = { navController.navigateJustOnce(NAV_SUPPORT) },
|
||||
onAbout = { navController.navigateJustOnce(NAV_ABOUT) }
|
||||
)
|
||||
}
|
||||
composable(NAV_WALLET_ADDRESS_DETAILS) {
|
||||
WrapWalletAddresses(
|
||||
goBack = {
|
||||
navController.popBackStackJustOnce(NAV_WALLET_ADDRESS_DETAILS)
|
||||
}
|
||||
)
|
||||
}
|
||||
composable(NAV_SETTINGS) {
|
||||
WrapSettings(
|
||||
goBack = {
|
||||
navController.popBackStackJustOnce(NAV_SETTINGS)
|
||||
},
|
||||
goWalletBackup = {
|
||||
navController.navigateJustOnce(NAV_SEED)
|
||||
}
|
||||
)
|
||||
}
|
||||
composable(NAV_SEED) {
|
||||
WrapSeed(
|
||||
goBack = {
|
||||
navController.popBackStackJustOnce(NAV_SEED)
|
||||
}
|
||||
)
|
||||
}
|
||||
composable(NAV_REQUEST) {
|
||||
WrapRequest(goBack = { navController.popBackStackJustOnce(NAV_REQUEST) })
|
||||
}
|
||||
composable(NAV_SEND) {
|
||||
WrapSend(goBack = { navController.popBackStackJustOnce(NAV_SEND) })
|
||||
}
|
||||
composable(NAV_SUPPORT) {
|
||||
// Pop back stack won't be right if we deep link into support
|
||||
WrapSupport(goBack = { navController.popBackStackJustOnce(NAV_SUPPORT) })
|
||||
}
|
||||
composable(NAV_ABOUT) {
|
||||
WrapAbout(goBack = { navController.popBackStackJustOnce(NAV_ABOUT) })
|
||||
}
|
||||
composable(NAV_SCAN) {
|
||||
WrapScanValidator(
|
||||
onScanValid = {
|
||||
// TODO [#449] https://github.com/zcash/secant-android-wallet/issues/449
|
||||
navController.navigateJustOnce(NAV_SEND) {
|
||||
popUpTo(NAV_HOME) { inclusive = false }
|
||||
}
|
||||
},
|
||||
goBack = { navController.popBackStackJustOnce(NAV_SCAN) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WrapScanValidator(
|
||||
onScanValid: (address: String) -> Unit,
|
||||
goBack: () -> Unit
|
||||
) {
|
||||
val synchronizer = walletViewModel.synchronizer.collectAsState().value
|
||||
if (synchronizer == null) {
|
||||
// Display loading indicator
|
||||
} else {
|
||||
WrapScan(
|
||||
onScanDone = { result ->
|
||||
lifecycleScope.launch {
|
||||
val isAddressValid = !synchronizer.validateAddress(result).isNotValid
|
||||
if (isAddressValid) {
|
||||
onScanValid(result)
|
||||
}
|
||||
}
|
||||
},
|
||||
goBack = goBack
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WrapCheckForUpdate() {
|
||||
val updateInfo = checkUpdateViewModel.updateInfo.collectAsState().value
|
||||
|
||||
updateInfo?.let {
|
||||
if (it.appUpdateInfo != null && it.state == UpdateState.Prepared) {
|
||||
WrapUpdate(updateInfo)
|
||||
}
|
||||
}
|
||||
|
||||
// Check for an app update asynchronously. We create an effect that matches the activity
|
||||
// lifecycle. If the wrapping compose recomposes, the check shouldn't run again.
|
||||
LaunchedEffect(true) {
|
||||
checkUpdateViewModel.checkForAppUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WrapWalletAddresses(
|
||||
goBack: () -> Unit
|
||||
) {
|
||||
val walletAddresses = walletViewModel.addresses.collectAsState().value
|
||||
if (null == walletAddresses) {
|
||||
// Display loading indicator
|
||||
} else {
|
||||
WalletAddresses(
|
||||
walletAddresses,
|
||||
goBack
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WrapSeed(
|
||||
goBack: () -> Unit
|
||||
) {
|
||||
val persistableWallet = run {
|
||||
val secretState = walletViewModel.secretState.collectAsState().value
|
||||
if (secretState is SecretState.Ready) {
|
||||
secretState.persistableWallet
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
val synchronizer = walletViewModel.synchronizer.collectAsState().value
|
||||
if (null == synchronizer || null == persistableWallet) {
|
||||
// Display loading indicator
|
||||
} else {
|
||||
Seed(
|
||||
persistableWallet = persistableWallet,
|
||||
onBack = goBack,
|
||||
onCopyToClipboard = {
|
||||
copyToClipboard(applicationContext, persistableWallet)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WrapRequest(
|
||||
goBack: () -> Unit
|
||||
) {
|
||||
val walletAddresses = walletViewModel.addresses.collectAsState().value
|
||||
if (null == walletAddresses) {
|
||||
// Display loading indicator
|
||||
} else {
|
||||
Request(
|
||||
walletAddresses.unified,
|
||||
goBack = goBack,
|
||||
onCreateAndSend = {
|
||||
val chooserIntent = Intent.createChooser(it.newShareIntent(applicationContext), null)
|
||||
|
||||
startActivity(chooserIntent)
|
||||
|
||||
goBack()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WrapSend(
|
||||
goBack: () -> Unit
|
||||
) {
|
||||
val synchronizer = walletViewModel.synchronizer.collectAsState().value
|
||||
val spendableBalance = walletViewModel.walletSnapshot.collectAsState().value?.spendableBalance()
|
||||
val spendingKey = walletViewModel.spendingKey.collectAsState().value
|
||||
if (null == synchronizer || null == spendableBalance || null == spendingKey) {
|
||||
// Display loading indicator
|
||||
} else {
|
||||
Send(
|
||||
mySpendableBalance = spendableBalance,
|
||||
goBack = goBack,
|
||||
onCreateAndSend = {
|
||||
synchronizer.send(spendingKey, it)
|
||||
|
||||
goBack()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@VisibleForTesting
|
||||
internal val SPLASH_SCREEN_DELAY = 0.seconds
|
||||
|
||||
@VisibleForTesting
|
||||
const val NAV_HOME = "home"
|
||||
|
||||
@VisibleForTesting
|
||||
const val NAV_PROFILE = "profile"
|
||||
|
||||
@VisibleForTesting
|
||||
const val NAV_WALLET_ADDRESS_DETAILS = "wallet_address_details"
|
||||
|
||||
@VisibleForTesting
|
||||
const val NAV_SETTINGS = "settings"
|
||||
|
||||
@VisibleForTesting
|
||||
const val NAV_SEED = "seed"
|
||||
|
||||
@VisibleForTesting
|
||||
const val NAV_REQUEST = "request"
|
||||
|
||||
@VisibleForTesting
|
||||
const val NAV_SEND = "send"
|
||||
|
||||
@VisibleForTesting
|
||||
const val NAV_SUPPORT = "support"
|
||||
|
||||
@VisibleForTesting
|
||||
const val NAV_ABOUT = "about"
|
||||
|
||||
@VisibleForTesting
|
||||
const val NAV_SCAN = "scan"
|
||||
}
|
||||
}
|
||||
|
||||
private fun ZecRequest.newShareIntent(context: Context) = runBlocking {
|
||||
Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
putExtra(Intent.EXTRA_TEXT, context.getString(R.string.request_template_format, toUri()))
|
||||
type = "text/plain"
|
||||
}
|
||||
}
|
||||
|
||||
private fun NavHostController.navigateJustOnce(
|
||||
route: String,
|
||||
navOptionsBuilder: (NavOptionsBuilder.() -> Unit)? = null
|
||||
) {
|
||||
if (currentDestination?.route == route) {
|
||||
return
|
||||
}
|
||||
|
||||
if (navOptionsBuilder != null) {
|
||||
navigate(route, navOptionsBuilder)
|
||||
} else {
|
||||
navigate(route)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pops up the current screen from the back stack. Parameter currentRouteToBePopped is meant to be
|
||||
* set only to the current screen so we can easily debounce multiple screen popping from the back stack.
|
||||
*
|
||||
* @param currentRouteToBePopped current screen which should be popped up.
|
||||
*/
|
||||
private fun NavHostController.popBackStackJustOnce(currentRouteToBePopped: String) {
|
||||
if (currentDestination?.route != currentRouteToBePopped) {
|
||||
return
|
||||
}
|
||||
popBackStack()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,173 @@
|
|||
package co.electriccoin.zcash.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.NavOptionsBuilder
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import co.electriccoin.zcash.ui.NavigationTargets.ABOUT
|
||||
import co.electriccoin.zcash.ui.NavigationTargets.HOME
|
||||
import co.electriccoin.zcash.ui.NavigationTargets.PROFILE
|
||||
import co.electriccoin.zcash.ui.NavigationTargets.REQUEST
|
||||
import co.electriccoin.zcash.ui.NavigationTargets.SCAN
|
||||
import co.electriccoin.zcash.ui.NavigationTargets.SEED
|
||||
import co.electriccoin.zcash.ui.NavigationTargets.SEND
|
||||
import co.electriccoin.zcash.ui.NavigationTargets.SETTINGS
|
||||
import co.electriccoin.zcash.ui.NavigationTargets.SUPPORT
|
||||
import co.electriccoin.zcash.ui.NavigationTargets.WALLET_ADDRESS_DETAILS
|
||||
import co.electriccoin.zcash.ui.screen.about.WrapAbout
|
||||
import co.electriccoin.zcash.ui.screen.address.WrapWalletAddresses
|
||||
import co.electriccoin.zcash.ui.screen.home.WrapHome
|
||||
import co.electriccoin.zcash.ui.screen.profile.WrapProfile
|
||||
import co.electriccoin.zcash.ui.screen.request.WrapRequest
|
||||
import co.electriccoin.zcash.ui.screen.scan.WrapScanValidator
|
||||
import co.electriccoin.zcash.ui.screen.seed.WrapSeed
|
||||
import co.electriccoin.zcash.ui.screen.send.WrapSend
|
||||
import co.electriccoin.zcash.ui.screen.settings.WrapSettings
|
||||
import co.electriccoin.zcash.ui.screen.support.WrapSupport
|
||||
import co.electriccoin.zcash.ui.screen.update.WrapCheckForUpdate
|
||||
|
||||
@Composable
|
||||
@Suppress("LongMethod")
|
||||
internal fun MainActivity.Navigation() {
|
||||
val navController = rememberNavController().also {
|
||||
// This suppress is necessary, as this is how we set up the nav controller for tests.
|
||||
@SuppressLint("RestrictedApi")
|
||||
navControllerForTesting = it
|
||||
}
|
||||
|
||||
NavHost(navController = navController, startDestination = HOME) {
|
||||
composable(HOME) {
|
||||
WrapHome(
|
||||
goScan = { navController.navigateJustOnce(SCAN) },
|
||||
goProfile = { navController.navigateJustOnce(PROFILE) },
|
||||
goSend = { navController.navigateJustOnce(SEND) },
|
||||
goRequest = { navController.navigateJustOnce(REQUEST) }
|
||||
)
|
||||
|
||||
WrapCheckForUpdate()
|
||||
}
|
||||
composable(PROFILE) {
|
||||
WrapProfile(
|
||||
onBack = { navController.popBackStackJustOnce(PROFILE) },
|
||||
onAddressDetails = { navController.navigateJustOnce(WALLET_ADDRESS_DETAILS) },
|
||||
onAddressBook = { },
|
||||
onSettings = { navController.navigateJustOnce(SETTINGS) },
|
||||
onCoinholderVote = { },
|
||||
onSupport = { navController.navigateJustOnce(SUPPORT) },
|
||||
onAbout = { navController.navigateJustOnce(ABOUT) }
|
||||
)
|
||||
}
|
||||
composable(WALLET_ADDRESS_DETAILS) {
|
||||
WrapWalletAddresses(
|
||||
goBack = {
|
||||
navController.popBackStackJustOnce(WALLET_ADDRESS_DETAILS)
|
||||
}
|
||||
)
|
||||
}
|
||||
composable(SETTINGS) {
|
||||
WrapSettings(
|
||||
goBack = {
|
||||
navController.popBackStackJustOnce(SETTINGS)
|
||||
},
|
||||
goWalletBackup = {
|
||||
navController.navigateJustOnce(SEED)
|
||||
}
|
||||
)
|
||||
}
|
||||
composable(SEED) {
|
||||
WrapSeed(
|
||||
goBack = {
|
||||
navController.popBackStackJustOnce(SEED)
|
||||
}
|
||||
)
|
||||
}
|
||||
composable(REQUEST) {
|
||||
WrapRequest(goBack = { navController.popBackStackJustOnce(REQUEST) })
|
||||
}
|
||||
composable(SEND) {
|
||||
WrapSend(goBack = { navController.popBackStackJustOnce(SEND) })
|
||||
}
|
||||
composable(SUPPORT) {
|
||||
// Pop back stack won't be right if we deep link into support
|
||||
WrapSupport(goBack = { navController.popBackStackJustOnce(SUPPORT) })
|
||||
}
|
||||
composable(ABOUT) {
|
||||
WrapAbout(goBack = { navController.popBackStackJustOnce(ABOUT) })
|
||||
}
|
||||
composable(SCAN) {
|
||||
WrapScanValidator(
|
||||
onScanValid = {
|
||||
// TODO [#449] https://github.com/zcash/secant-android-wallet/issues/449
|
||||
navController.navigateJustOnce(SEND) {
|
||||
popUpTo(HOME) { inclusive = false }
|
||||
}
|
||||
},
|
||||
goBack = { navController.popBackStackJustOnce(SCAN) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun NavHostController.navigateJustOnce(
|
||||
route: String,
|
||||
navOptionsBuilder: (NavOptionsBuilder.() -> Unit)? = null
|
||||
) {
|
||||
if (currentDestination?.route == route) {
|
||||
return
|
||||
}
|
||||
|
||||
if (navOptionsBuilder != null) {
|
||||
navigate(route, navOptionsBuilder)
|
||||
} else {
|
||||
navigate(route)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pops up the current screen from the back stack. Parameter currentRouteToBePopped is meant to be
|
||||
* set only to the current screen so we can easily debounce multiple screen popping from the back stack.
|
||||
*
|
||||
* @param currentRouteToBePopped current screen which should be popped up.
|
||||
*/
|
||||
private fun NavHostController.popBackStackJustOnce(currentRouteToBePopped: String) {
|
||||
if (currentDestination?.route != currentRouteToBePopped) {
|
||||
return
|
||||
}
|
||||
popBackStack()
|
||||
}
|
||||
|
||||
object NavigationTargets {
|
||||
@VisibleForTesting
|
||||
const val HOME = "home"
|
||||
|
||||
@VisibleForTesting
|
||||
const val PROFILE = "profile"
|
||||
|
||||
@VisibleForTesting
|
||||
const val WALLET_ADDRESS_DETAILS = "wallet_address_details"
|
||||
|
||||
@VisibleForTesting
|
||||
const val SETTINGS = "settings"
|
||||
|
||||
@VisibleForTesting
|
||||
const val SEED = "seed"
|
||||
|
||||
@VisibleForTesting
|
||||
const val REQUEST = "request"
|
||||
|
||||
@VisibleForTesting
|
||||
const val SEND = "send"
|
||||
|
||||
@VisibleForTesting
|
||||
const val SUPPORT = "support"
|
||||
|
||||
@VisibleForTesting
|
||||
const val ABOUT = "about"
|
||||
|
||||
@VisibleForTesting
|
||||
const val SCAN = "scan"
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
@file:Suppress("ktlint:filename")
|
||||
|
||||
package co.electriccoin.zcash.ui.screen.address
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import co.electriccoin.zcash.ui.MainActivity
|
||||
import co.electriccoin.zcash.ui.screen.address.view.WalletAddresses
|
||||
import co.electriccoin.zcash.ui.screen.home.viewmodel.WalletViewModel
|
||||
|
||||
@Composable
|
||||
internal fun MainActivity.WrapWalletAddresses(
|
||||
goBack: () -> Unit
|
||||
) {
|
||||
WrapWalletAddresses(this, goBack)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WrapWalletAddresses(
|
||||
activity: ComponentActivity,
|
||||
goBack: () -> Unit
|
||||
) {
|
||||
val walletViewModel by activity.viewModels<WalletViewModel>()
|
||||
|
||||
val walletAddresses = walletViewModel.addresses.collectAsState().value
|
||||
if (null == walletAddresses) {
|
||||
// Display loading indicator
|
||||
} else {
|
||||
WalletAddresses(
|
||||
walletAddresses,
|
||||
goBack
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
@file:Suppress("ktlint:filename")
|
||||
|
||||
package co.electriccoin.zcash.ui.screen.request
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import cash.z.ecc.sdk.model.ZecRequest
|
||||
import co.electriccoin.zcash.ui.MainActivity
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.screen.home.viewmodel.WalletViewModel
|
||||
import co.electriccoin.zcash.ui.screen.request.view.Request
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
@Composable
|
||||
internal fun MainActivity.WrapRequest(
|
||||
goBack: () -> Unit
|
||||
) {
|
||||
WrapRequest(this, goBack)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WrapRequest(
|
||||
activity: ComponentActivity,
|
||||
goBack: () -> Unit
|
||||
) {
|
||||
val walletViewModel by activity.viewModels<WalletViewModel>()
|
||||
val walletAddresses = walletViewModel.addresses.collectAsState().value
|
||||
|
||||
if (null == walletAddresses) {
|
||||
// Display loading indicator
|
||||
} else {
|
||||
Request(
|
||||
walletAddresses.unified,
|
||||
goBack = goBack,
|
||||
onCreateAndSend = {
|
||||
val chooserIntent = Intent.createChooser(
|
||||
it.newShareIntent(activity.applicationContext),
|
||||
null
|
||||
)
|
||||
|
||||
activity.startActivity(chooserIntent)
|
||||
|
||||
goBack()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun ZecRequest.newShareIntent(context: Context) = runBlocking {
|
||||
Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
putExtra(Intent.EXTRA_TEXT, context.getString(R.string.request_template_format, toUri()))
|
||||
type = "text/plain"
|
||||
}
|
||||
}
|
|
@ -3,22 +3,57 @@
|
|||
package co.electriccoin.zcash.ui.screen.scan
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import co.electriccoin.zcash.ui.MainActivity
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.screen.home.viewmodel.WalletViewModel
|
||||
import co.electriccoin.zcash.ui.screen.scan.util.SettingsUtil
|
||||
import co.electriccoin.zcash.ui.screen.scan.view.Scan
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
internal fun MainActivity.WrapScan(
|
||||
goBack: () -> Unit,
|
||||
onScanDone: (result: String) -> Unit
|
||||
internal fun MainActivity.WrapScanValidator(
|
||||
onScanValid: (address: String) -> Unit,
|
||||
goBack: () -> Unit
|
||||
) {
|
||||
WrapScan(this, onScanDone, goBack)
|
||||
WrapScanValidator(
|
||||
this,
|
||||
onScanValid = onScanValid,
|
||||
goBack = goBack
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WrapScanValidator(
|
||||
activity: ComponentActivity,
|
||||
onScanValid: (address: String) -> Unit,
|
||||
goBack: () -> Unit
|
||||
) {
|
||||
val walletViewModel by activity.viewModels<WalletViewModel>()
|
||||
|
||||
val synchronizer = walletViewModel.synchronizer.collectAsState().value
|
||||
if (synchronizer == null) {
|
||||
// Display loading indicator
|
||||
} else {
|
||||
WrapScan(
|
||||
activity,
|
||||
onScanned = { result ->
|
||||
activity.lifecycleScope.launch {
|
||||
val isAddressValid = !synchronizer.validateAddress(result).isNotValid
|
||||
if (isAddressValid) {
|
||||
onScanValid(result)
|
||||
}
|
||||
}
|
||||
},
|
||||
goBack = goBack
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
@file:Suppress("ktlint:filename")
|
||||
|
||||
package co.electriccoin.zcash.ui.screen.seed
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import co.electriccoin.zcash.ui.MainActivity
|
||||
import co.electriccoin.zcash.ui.screen.backup.copyToClipboard
|
||||
import co.electriccoin.zcash.ui.screen.home.viewmodel.SecretState
|
||||
import co.electriccoin.zcash.ui.screen.home.viewmodel.WalletViewModel
|
||||
import co.electriccoin.zcash.ui.screen.seed.view.Seed
|
||||
|
||||
@Composable
|
||||
internal fun MainActivity.WrapSeed(
|
||||
goBack: () -> Unit
|
||||
) {
|
||||
WrapSeed(this, goBack)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WrapSeed(
|
||||
activity: ComponentActivity,
|
||||
goBack: () -> Unit
|
||||
) {
|
||||
val walletViewModel by activity.viewModels<WalletViewModel>()
|
||||
|
||||
val persistableWallet = run {
|
||||
val secretState = walletViewModel.secretState.collectAsState().value
|
||||
if (secretState is SecretState.Ready) {
|
||||
secretState.persistableWallet
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
val synchronizer = walletViewModel.synchronizer.collectAsState().value
|
||||
if (null == synchronizer || null == persistableWallet) {
|
||||
// Display loading indicator
|
||||
} else {
|
||||
Seed(
|
||||
persistableWallet = persistableWallet,
|
||||
onBack = goBack,
|
||||
onCopyToClipboard = {
|
||||
copyToClipboard(activity.applicationContext, persistableWallet)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
@file:Suppress("ktlint:filename")
|
||||
|
||||
package co.electriccoin.zcash.ui.screen.send
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import cash.z.ecc.sdk.send
|
||||
import co.electriccoin.zcash.ui.MainActivity
|
||||
import co.electriccoin.zcash.ui.screen.home.model.spendableBalance
|
||||
import co.electriccoin.zcash.ui.screen.home.viewmodel.WalletViewModel
|
||||
import co.electriccoin.zcash.ui.screen.send.view.Send
|
||||
|
||||
@Composable
|
||||
internal fun MainActivity.WrapSend(
|
||||
goBack: () -> Unit
|
||||
) {
|
||||
WrapSend(this, goBack)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WrapSend(
|
||||
activity: ComponentActivity,
|
||||
goBack: () -> Unit
|
||||
) {
|
||||
val walletViewModel by activity.viewModels<WalletViewModel>()
|
||||
|
||||
val synchronizer = walletViewModel.synchronizer.collectAsState().value
|
||||
val spendableBalance = walletViewModel.walletSnapshot.collectAsState().value?.spendableBalance()
|
||||
val spendingKey = walletViewModel.spendingKey.collectAsState().value
|
||||
if (null == synchronizer || null == spendableBalance || null == spendingKey) {
|
||||
// Display loading indicator
|
||||
} else {
|
||||
Send(
|
||||
mySpendableBalance = spendableBalance,
|
||||
goBack = goBack,
|
||||
onCreateAndSend = {
|
||||
synchronizer.send(spendingKey, it)
|
||||
|
||||
goBack()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
|
@ -3,13 +3,16 @@ package co.electriccoin.zcash.ui.screen.update
|
|||
import android.content.Context
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.viewModels
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import co.electriccoin.zcash.ui.MainActivity
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.screen.home.viewmodel.CheckUpdateViewModel
|
||||
import co.electriccoin.zcash.ui.screen.update.model.UpdateInfo
|
||||
import co.electriccoin.zcash.ui.screen.update.model.UpdateState
|
||||
import co.electriccoin.zcash.ui.screen.update.util.PlayStoreUtil
|
||||
|
@ -19,17 +22,39 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
internal fun MainActivity.WrapUpdate(
|
||||
updateInfo: UpdateInfo
|
||||
) {
|
||||
WrapUpdate(
|
||||
activity = this,
|
||||
inputUpdateInfo = updateInfo
|
||||
)
|
||||
internal fun MainActivity.WrapCheckForUpdate() {
|
||||
WrapCheckForUpdate(this)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun WrapUpdate(
|
||||
private fun WrapCheckForUpdate(activity: ComponentActivity) {
|
||||
// TODO [#382]: https://github.com/zcash/secant-android-wallet/issues/382
|
||||
// TODO [#403]: https://github.com/zcash/secant-android-wallet/issues/403
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
val checkUpdateViewModel by activity.viewModels<CheckUpdateViewModel> {
|
||||
CheckUpdateViewModel.CheckUpdateViewModelFactory(
|
||||
activity.application,
|
||||
AppUpdateCheckerImp.new()
|
||||
)
|
||||
}
|
||||
|
||||
val updateInfo = checkUpdateViewModel.updateInfo.collectAsState().value
|
||||
|
||||
updateInfo?.let {
|
||||
if (it.appUpdateInfo != null && it.state == UpdateState.Prepared) {
|
||||
WrapUpdate(activity, updateInfo)
|
||||
}
|
||||
}
|
||||
|
||||
// Check for an app update asynchronously. We create an effect that matches the activity
|
||||
// lifecycle. If the wrapping compose recomposes, the check shouldn't run again.
|
||||
LaunchedEffect(true) {
|
||||
checkUpdateViewModel.checkForAppUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WrapUpdate(
|
||||
activity: ComponentActivity,
|
||||
inputUpdateInfo: UpdateInfo
|
||||
) {
|
||||
|
|
Loading…
Reference in New Issue