Co-authored-by: Honza <rychnovsky.honza@gmail.com>
This commit is contained in:
parent
2145698bba
commit
981d70727b
|
@ -43,8 +43,10 @@ class TestOnboardingActivity : ComponentActivity() {
|
|||
if (!onboardingViewModel.isImporting.collectAsState().value) {
|
||||
Onboarding(
|
||||
onboardingState = onboardingViewModel.onboardingState,
|
||||
isDebugMenuEnabled = false,
|
||||
onImportWallet = { onboardingViewModel.isImporting.value = true },
|
||||
onCreateWallet = {}
|
||||
onCreateWallet = {},
|
||||
onFixtureWallet = {}
|
||||
)
|
||||
|
||||
reportFullyDrawn()
|
||||
|
|
|
@ -38,8 +38,11 @@ class OnboardingTestSetup(
|
|||
ZcashTheme {
|
||||
Onboarding(
|
||||
onboardingState,
|
||||
isDebugMenuEnabled = false,
|
||||
onCreateWallet = { onCreateWalletCallbackCount.incrementAndGet() },
|
||||
onImportWallet = { onImportWalletCallbackCount.incrementAndGet() }
|
||||
onImportWallet = { onImportWalletCallbackCount.incrementAndGet() },
|
||||
// We aren't testing this because it is for debug builds only.
|
||||
onFixtureWallet = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package co.electriccoin.zcash.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
|
@ -23,13 +22,8 @@ import androidx.navigation.NavHostController
|
|||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
||||
import cash.z.ecc.sdk.fixture.SeedPhraseFixture
|
||||
import cash.z.ecc.sdk.model.PersistableWallet
|
||||
import cash.z.ecc.sdk.model.SeedPhrase
|
||||
import cash.z.ecc.sdk.model.ZecRequest
|
||||
import cash.z.ecc.sdk.send
|
||||
import cash.z.ecc.sdk.type.fromResources
|
||||
import co.electriccoin.zcash.spackle.EmulatorWtfUtil
|
||||
import co.electriccoin.zcash.spackle.FirebaseTestLabUtil
|
||||
import co.electriccoin.zcash.ui.design.compat.FontCompat
|
||||
|
@ -45,13 +39,9 @@ import co.electriccoin.zcash.ui.screen.home.view.Home
|
|||
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.view.Onboarding
|
||||
import co.electriccoin.zcash.ui.screen.onboarding.viewmodel.OnboardingViewModel
|
||||
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.restore.view.RestoreWallet
|
||||
import co.electriccoin.zcash.ui.screen.restore.viewmodel.CompleteWordSetState
|
||||
import co.electriccoin.zcash.ui.screen.restore.viewmodel.RestoreViewModel
|
||||
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
|
||||
|
@ -161,97 +151,6 @@ class MainActivity : ComponentActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WrapOnboarding() {
|
||||
val onboardingViewModel by viewModels<OnboardingViewModel>()
|
||||
|
||||
// TODO [#383]: https://github.com/zcash/secant-android-wallet/issues/383
|
||||
if (!onboardingViewModel.isImporting.collectAsState().value) {
|
||||
Onboarding(
|
||||
onboardingState = onboardingViewModel.onboardingState,
|
||||
onImportWallet = {
|
||||
// In the case of the app currently being messed with by the robo test runner on
|
||||
// Firebase Test Lab or Google Play pre-launch report, we want to skip creating
|
||||
// a new or restoring an existing wallet screens by persisting an existing wallet
|
||||
// with a mock seed.
|
||||
if (FirebaseTestLabUtil.isFirebaseTestLab(applicationContext)) {
|
||||
persistExistingWalletWithSeedPhrase(SeedPhraseFixture.new())
|
||||
return@Onboarding
|
||||
}
|
||||
|
||||
onboardingViewModel.isImporting.value = true
|
||||
},
|
||||
onCreateWallet = {
|
||||
if (FirebaseTestLabUtil.isFirebaseTestLab(applicationContext)) {
|
||||
persistExistingWalletWithSeedPhrase(SeedPhraseFixture.new())
|
||||
return@Onboarding
|
||||
}
|
||||
|
||||
walletViewModel.persistNewWallet()
|
||||
}
|
||||
)
|
||||
|
||||
reportFullyDrawn()
|
||||
} else {
|
||||
WrapRestore()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WrapRestore() {
|
||||
val onboardingViewModel by viewModels<OnboardingViewModel>()
|
||||
val restoreViewModel by viewModels<RestoreViewModel>()
|
||||
|
||||
when (val completeWordList = restoreViewModel.completeWordList.collectAsState().value) {
|
||||
CompleteWordSetState.Loading -> {
|
||||
// Although it might perform IO, it should be relatively fast.
|
||||
// Consider whether to display indeterminate progress here.
|
||||
// Another option would be to go straight to the restore screen with autocomplete
|
||||
// disabled for a few milliseconds. Users would probably never notice due to the
|
||||
// time it takes to re-orient on the new screen, unless users were doing this
|
||||
// on a daily basis and become very proficient at our UI. The Therac-25 has
|
||||
// historical precedent on how that could cause problems.
|
||||
}
|
||||
is CompleteWordSetState.Loaded -> {
|
||||
RestoreWallet(
|
||||
completeWordList.list,
|
||||
restoreViewModel.userWordList,
|
||||
onBack = { onboardingViewModel.isImporting.value = false },
|
||||
paste = {
|
||||
val clipboardManager = getSystemService(ClipboardManager::class.java)
|
||||
return@RestoreWallet clipboardManager?.primaryClip?.toString()
|
||||
},
|
||||
onFinished = {
|
||||
persistExistingWalletWithSeedPhrase(
|
||||
SeedPhrase(restoreViewModel.userWordList.current.value)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Persists existing wallet together with the backup complete flag to disk. Be aware of that, it
|
||||
* triggers navigation changes, as we observe the WalletViewModel.secretState.
|
||||
*
|
||||
* Write the backup complete flag first, then the seed phrase. That avoids the UI flickering to
|
||||
* the backup screen. Assume if a user is restoring from a backup, then the user has a valid backup.
|
||||
*
|
||||
* @param seedPhrase to be persisted along with the wallet object
|
||||
*/
|
||||
private fun persistExistingWalletWithSeedPhrase(seedPhrase: SeedPhrase) {
|
||||
walletViewModel.persistBackupComplete()
|
||||
|
||||
val network = ZcashNetwork.fromResources(application)
|
||||
val restoredWallet = PersistableWallet(
|
||||
network,
|
||||
null,
|
||||
seedPhrase
|
||||
)
|
||||
walletViewModel.persistExistingWallet(restoredWallet)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
@SuppressWarnings("LongMethod")
|
||||
|
|
|
@ -0,0 +1,156 @@
|
|||
package co.electriccoin.zcash.ui.screen.onboarding
|
||||
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
||||
import cash.z.ecc.sdk.fixture.SeedPhraseFixture
|
||||
import cash.z.ecc.sdk.model.PersistableWallet
|
||||
import cash.z.ecc.sdk.model.SeedPhrase
|
||||
import cash.z.ecc.sdk.type.fromResources
|
||||
import co.electriccoin.zcash.spackle.EmulatorWtfUtil
|
||||
import co.electriccoin.zcash.spackle.FirebaseTestLabUtil
|
||||
import co.electriccoin.zcash.ui.BuildConfig
|
||||
import co.electriccoin.zcash.ui.MainActivity
|
||||
import co.electriccoin.zcash.ui.screen.home.viewmodel.WalletViewModel
|
||||
import co.electriccoin.zcash.ui.screen.onboarding.view.Onboarding
|
||||
import co.electriccoin.zcash.ui.screen.onboarding.viewmodel.OnboardingViewModel
|
||||
import co.electriccoin.zcash.ui.screen.restore.view.RestoreWallet
|
||||
import co.electriccoin.zcash.ui.screen.restore.viewmodel.CompleteWordSetState
|
||||
import co.electriccoin.zcash.ui.screen.restore.viewmodel.RestoreViewModel
|
||||
|
||||
@Composable
|
||||
internal fun MainActivity.WrapOnboarding() {
|
||||
WrapOnboarding(this)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun WrapOnboarding(
|
||||
activity: ComponentActivity
|
||||
) {
|
||||
val walletViewModel by activity.viewModels<WalletViewModel>()
|
||||
val onboardingViewModel by activity.viewModels<OnboardingViewModel>()
|
||||
|
||||
val applicationContext = LocalContext.current.applicationContext
|
||||
|
||||
// We might eventually want to check the debuggable property of the manifest instead
|
||||
// of relying on BuildConfig.
|
||||
val isDebugMenuEnabled = BuildConfig.DEBUG &&
|
||||
!FirebaseTestLabUtil.isFirebaseTestLab(applicationContext) &&
|
||||
!EmulatorWtfUtil.isEmulatorWtf(applicationContext)
|
||||
|
||||
// TODO [#383]: https://github.com/zcash/secant-android-wallet/issues/383
|
||||
if (!onboardingViewModel.isImporting.collectAsState().value) {
|
||||
Onboarding(
|
||||
onboardingState = onboardingViewModel.onboardingState,
|
||||
isDebugMenuEnabled = isDebugMenuEnabled,
|
||||
onImportWallet = {
|
||||
// In the case of the app currently being messed with by the robo test runner on
|
||||
// Firebase Test Lab or Google Play pre-launch report, we want to skip creating
|
||||
// a new or restoring an existing wallet screens by persisting an existing wallet
|
||||
// with a mock seed.
|
||||
if (FirebaseTestLabUtil.isFirebaseTestLab(applicationContext)) {
|
||||
persistExistingWalletWithSeedPhrase(
|
||||
applicationContext,
|
||||
walletViewModel,
|
||||
SeedPhraseFixture.new()
|
||||
)
|
||||
return@Onboarding
|
||||
}
|
||||
|
||||
onboardingViewModel.isImporting.value = true
|
||||
},
|
||||
onCreateWallet = {
|
||||
if (FirebaseTestLabUtil.isFirebaseTestLab(applicationContext)) {
|
||||
persistExistingWalletWithSeedPhrase(
|
||||
applicationContext,
|
||||
walletViewModel,
|
||||
SeedPhraseFixture.new()
|
||||
)
|
||||
return@Onboarding
|
||||
}
|
||||
|
||||
walletViewModel.persistNewWallet()
|
||||
},
|
||||
onFixtureWallet = {
|
||||
persistExistingWalletWithSeedPhrase(
|
||||
applicationContext,
|
||||
walletViewModel,
|
||||
SeedPhraseFixture.new()
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
activity.reportFullyDrawn()
|
||||
} else {
|
||||
WrapRestore(activity)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WrapRestore(activity: ComponentActivity) {
|
||||
val walletViewModel by activity.viewModels<WalletViewModel>()
|
||||
val onboardingViewModel by activity.viewModels<OnboardingViewModel>()
|
||||
val restoreViewModel by activity.viewModels<RestoreViewModel>()
|
||||
|
||||
val applicationContext = LocalContext.current.applicationContext
|
||||
|
||||
when (val completeWordList = restoreViewModel.completeWordList.collectAsState().value) {
|
||||
CompleteWordSetState.Loading -> {
|
||||
// Although it might perform IO, it should be relatively fast.
|
||||
// Consider whether to display indeterminate progress here.
|
||||
// Another option would be to go straight to the restore screen with autocomplete
|
||||
// disabled for a few milliseconds. Users would probably never notice due to the
|
||||
// time it takes to re-orient on the new screen, unless users were doing this
|
||||
// on a daily basis and become very proficient at our UI. The Therac-25 has
|
||||
// historical precedent on how that could cause problems.
|
||||
}
|
||||
is CompleteWordSetState.Loaded -> {
|
||||
RestoreWallet(
|
||||
completeWordList.list,
|
||||
restoreViewModel.userWordList,
|
||||
onBack = { onboardingViewModel.isImporting.value = false },
|
||||
paste = {
|
||||
val clipboardManager = applicationContext.getSystemService(ClipboardManager::class.java)
|
||||
return@RestoreWallet clipboardManager?.primaryClip?.toString()
|
||||
},
|
||||
onFinished = {
|
||||
persistExistingWalletWithSeedPhrase(
|
||||
applicationContext,
|
||||
walletViewModel,
|
||||
SeedPhrase(restoreViewModel.userWordList.current.value)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Persists existing wallet together with the backup complete flag to disk. Be aware of that, it
|
||||
* triggers navigation changes, as we observe the WalletViewModel.secretState.
|
||||
*
|
||||
* Write the backup complete flag first, then the seed phrase. That avoids the UI flickering to
|
||||
* the backup screen. Assume if a user is restoring from a backup, then the user has a valid backup.
|
||||
*
|
||||
* @param seedPhrase to be persisted along with the wallet object
|
||||
*/
|
||||
private fun persistExistingWalletWithSeedPhrase(
|
||||
context: Context,
|
||||
walletViewModel: WalletViewModel,
|
||||
seedPhrase: SeedPhrase
|
||||
) {
|
||||
walletViewModel.persistBackupComplete()
|
||||
|
||||
val network = ZcashNetwork.fromResources(context)
|
||||
val restoredWallet = PersistableWallet(
|
||||
network,
|
||||
null,
|
||||
seedPhrase
|
||||
)
|
||||
walletViewModel.persistExistingWallet(restoredWallet)
|
||||
}
|
|
@ -7,19 +7,30 @@ import androidx.compose.foundation.background
|
|||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SmallTopAppBar
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
|
@ -48,8 +59,10 @@ fun ComposablePreview() {
|
|||
GradientSurface {
|
||||
Onboarding(
|
||||
OnboardingState(OnboardingStage.UnifiedAddresses),
|
||||
false,
|
||||
onImportWallet = {},
|
||||
onCreateWallet = {}
|
||||
onCreateWallet = {},
|
||||
onFixtureWallet = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -63,20 +76,35 @@ private const val IS_NAVIGATION_IN_APP_BAR = false
|
|||
* @param onImportWallet Callback when the user decides to import an existing wallet.
|
||||
* @param onCreateWallet Callback when the user decides to create a new wallet.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun Onboarding(
|
||||
onboardingState: OnboardingState,
|
||||
isDebugMenuEnabled: Boolean,
|
||||
onImportWallet: () -> Unit,
|
||||
onCreateWallet: () -> Unit
|
||||
onCreateWallet: () -> Unit,
|
||||
onFixtureWallet: () -> Unit,
|
||||
) {
|
||||
Column {
|
||||
OnboardingTopAppBar(onboardingState)
|
||||
OnboardingMainContent(onboardingState, onImportWallet = onImportWallet, onCreateWallet = onCreateWallet)
|
||||
Scaffold(
|
||||
topBar = {
|
||||
OnboardingTopAppBar(onboardingState, isDebugMenuEnabled, onFixtureWallet)
|
||||
}
|
||||
) { paddingValues ->
|
||||
OnboardingMainContent(
|
||||
paddingValues,
|
||||
onboardingState,
|
||||
onImportWallet = onImportWallet,
|
||||
onCreateWallet = onCreateWallet
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OnboardingTopAppBar(onboardingState: OnboardingState) {
|
||||
private fun OnboardingTopAppBar(
|
||||
onboardingState: OnboardingState,
|
||||
isDebugMenuEnabled: Boolean,
|
||||
onFixtureWallet: () -> Unit
|
||||
) {
|
||||
val currentStage = onboardingState.current.collectAsState().value
|
||||
|
||||
SmallTopAppBar(
|
||||
|
@ -98,21 +126,47 @@ private fun OnboardingTopAppBar(onboardingState: OnboardingState) {
|
|||
if (IS_NAVIGATION_IN_APP_BAR && currentStage.hasNext()) {
|
||||
NavigationButton(onboardingState::goToEnd, stringResource(R.string.onboarding_skip))
|
||||
}
|
||||
|
||||
if (isDebugMenuEnabled) {
|
||||
DebugMenu(onFixtureWallet)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DebugMenu(onFixtureWallet: () -> Unit) {
|
||||
var expanded by rememberSaveable { mutableStateOf(false) }
|
||||
IconButton(onClick = { expanded = true }) {
|
||||
Icon(Icons.Default.MoreVert, contentDescription = null)
|
||||
}
|
||||
|
||||
DropdownMenu(
|
||||
expanded = expanded,
|
||||
onDismissRequest = { expanded = false }
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = { Text("Import wallet with fixture seed phrase") },
|
||||
onClick = onFixtureWallet
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param onImportWallet Callback when the user decides to import an existing wallet.
|
||||
* @param onCreateWallet Callback when the user decides to create a new wallet.
|
||||
*/
|
||||
@Composable
|
||||
fun OnboardingMainContent(
|
||||
paddingValues: PaddingValues,
|
||||
onboardingState: OnboardingState,
|
||||
onImportWallet: () -> Unit,
|
||||
onCreateWallet: () -> Unit
|
||||
) {
|
||||
Column {
|
||||
Column(
|
||||
Modifier
|
||||
.padding(top = paddingValues.calculateTopPadding())
|
||||
) {
|
||||
if (!IS_NAVIGATION_IN_APP_BAR) {
|
||||
TopNavButtons(onboardingState)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue