[#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:
Carter Jernigan 2022-08-09 15:23:38 -04:00 committed by Carter Jernigan
parent 1bd5e7518e
commit 334dc3c41f
9 changed files with 446 additions and 332 deletions

View File

@ -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)
}
}

View File

@ -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()
}

View File

@ -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"
}

View File

@ -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
)
}
}

View File

@ -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"
}
}

View File

@ -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

View File

@ -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)
}
)
}
}

View File

@ -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()
}
)
}
}

View File

@ -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
) {