[#762] Simplified seed backup UX

Because we’re making UI text changes, I’ve forked our views into “long” and “short” versions that preserve the previous experience while allowing the shorter experience to be different.

One limitation of the current approach is that the screenshot tests are limited to the current configuration, so there is some risk of ‘bit rot’ with the screenshot tests for the longer onboarding.  For this PR, I manually switched the feature flags and re-ran the screenshot tests to make sure they still worked.

---------

Co-authored-by: Honza <rychnovsky.honza@gmail.com>
This commit is contained in:
Carter Jernigan 2023-03-03 08:06:03 -05:00 committed by GitHub
parent 4acd5d3593
commit 8e17d07ced
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 942 additions and 360 deletions

View File

@ -0,0 +1,28 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name=":ui-benchmark-test:connectedBenchmarkAndroidTest" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="env">
<map>
<entry key="ORG_GRADLE_PROJECT_IS_SIGN_RELEASE_BUILD_WITH_DEBUG_KEY" value="true" />
</map>
</option>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value=":ui-benchmark-test:connectedBenchmarkAndroidTest" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2" />
</configuration>
</component>

View File

@ -0,0 +1,28 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name=":ui-screenshot-test:connectedCheck" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="env">
<map>
<entry key="ORG_GRADLE_PROJECT_IS_SIGN_RELEASE_BUILD_WITH_DEBUG_KEY" value="true" />
</map>
</option>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value=":ui-screenshot-test:connectedCheck" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2" />
</configuration>
</component>

View File

@ -1,55 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="ui-benchmark-test:connectedBenchmarkAndroidTest" type="AndroidTestRunConfigurationType" factoryName="Android Instrumented Tests">
<module name="zcash-android-app.ui-benchmark-test" />
<option name="TESTING_TYPE" value="0" />
<option name="METHOD_NAME" value="" />
<option name="CLASS_NAME" value="" />
<option name="PACKAGE_NAME" value="" />
<option name="TEST_NAME_REGEX" value="" />
<option name="INSTRUMENTATION_RUNNER_CLASS" value="" />
<option name="EXTRA_OPTIONS" value="" />
<option name="RETENTION_ENABLED" value="No" />
<option name="RETENTION_MAX_SNAPSHOTS" value="2" />
<option name="RETENTION_COMPRESS_SNAPSHOTS" value="false" />
<option name="CLEAR_LOGCAT" value="false" />
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
<option name="INSPECTION_WITHOUT_ACTIVITY_RESTART" value="false" />
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
<option name="DEBUGGER_TYPE" value="Auto" />
<Auto>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
</Auto>
<Hybrid>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
</Hybrid>
<Java />
<Native>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
</Native>
<Profilers>
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
<option name="STARTUP_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Java/Kotlin Method Sample (legacy)" />
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
</Profilers>
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
</configuration>
</component>

View File

@ -1,20 +1,17 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="ui-design-lib:connectedCheck" type="AndroidTestRunConfigurationType" factoryName="Android Instrumented Tests"> <configuration default="false" name="ui-design-lib:connectedCheck" type="AndroidTestRunConfigurationType" factoryName="Android Instrumented Tests">
<module name="zcash-android-app.ui-design-lib" />
<option name="TESTING_TYPE" value="0" /> <option name="TESTING_TYPE" value="0" />
<option name="METHOD_NAME" value="" /> <option name="METHOD_NAME" value="" />
<option name="CLASS_NAME" value="" /> <option name="CLASS_NAME" value="" />
<option name="PACKAGE_NAME" value="" /> <option name="PACKAGE_NAME" value="" />
<option name="TEST_NAME_REGEX" value="" />
<option name="INSTRUMENTATION_RUNNER_CLASS" value="" /> <option name="INSTRUMENTATION_RUNNER_CLASS" value="" />
<option name="EXTRA_OPTIONS" value="" /> <option name="EXTRA_OPTIONS" value="" />
<option name="INCLUDE_GRADLE_EXTRA_OPTIONS" value="true" />
<option name="RETENTION_ENABLED" value="No" /> <option name="RETENTION_ENABLED" value="No" />
<option name="RETENTION_MAX_SNAPSHOTS" value="2" /> <option name="RETENTION_MAX_SNAPSHOTS" value="2" />
<option name="RETENTION_COMPRESS_SNAPSHOTS" value="false" /> <option name="RETENTION_COMPRESS_SNAPSHOTS" value="false" />
<option name="CLEAR_LOGCAT" value="false" /> <option name="CLEAR_LOGCAT" value="false" />
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" /> <option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
<option name="SKIP_NOOP_APK_INSTALLATIONS" value="true" />
<option name="FORCE_STOP_RUNNING_APP" value="true" />
<option name="INSPECTION_WITHOUT_ACTIVITY_RESTART" value="false" /> <option name="INSPECTION_WITHOUT_ACTIVITY_RESTART" value="false" />
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" /> <option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" /> <option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />

View File

@ -1,57 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="ui-screenshot-test:connectedCheck" type="AndroidTestRunConfigurationType" factoryName="Android Instrumented Tests">
<module name="zcash-android-app.ui-screenshot-test" />
<option name="TESTING_TYPE" value="0" />
<option name="METHOD_NAME" value="" />
<option name="CLASS_NAME" value="" />
<option name="PACKAGE_NAME" value="" />
<option name="INSTRUMENTATION_RUNNER_CLASS" value="" />
<option name="EXTRA_OPTIONS" value="" />
<option name="INCLUDE_GRADLE_EXTRA_OPTIONS" value="true" />
<option name="RETENTION_ENABLED" value="No" />
<option name="RETENTION_MAX_SNAPSHOTS" value="2" />
<option name="RETENTION_COMPRESS_SNAPSHOTS" value="false" />
<option name="CLEAR_LOGCAT" value="false" />
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
<option name="SKIP_NOOP_APK_INSTALLATIONS" value="true" />
<option name="FORCE_STOP_RUNNING_APP" value="true" />
<option name="INSPECTION_WITHOUT_ACTIVITY_RESTART" value="false" />
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
<option name="DEBUGGER_TYPE" value="Auto" />
<Auto>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
</Auto>
<Hybrid>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
</Hybrid>
<Java />
<Native>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
</Native>
<Profilers>
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
<option name="STARTUP_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Java/Kotlin Method Sample (legacy)" />
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
</Profilers>
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
</configuration>
</component>

View File

@ -31,10 +31,10 @@ android {
"src/main/res/ui/common", "src/main/res/ui/common",
"src/main/res/ui/home", "src/main/res/ui/home",
"src/main/res/ui/onboarding", "src/main/res/ui/onboarding",
"src/main/res/ui/scan",
"src/main/res/ui/receive", "src/main/res/ui/receive",
"src/main/res/ui/restore",
"src/main/res/ui/request", "src/main/res/ui/request",
"src/main/res/ui/restore",
"src/main/res/ui/scan",
"src/main/res/ui/seed", "src/main/res/ui/seed",
"src/main/res/ui/send", "src/main/res/ui/send",
"src/main/res/ui/settings", "src/main/res/ui/settings",

View File

@ -9,7 +9,7 @@ import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.common.UiTestingActivity import co.electriccoin.zcash.ui.common.UiTestingActivity
import co.electriccoin.zcash.ui.fixture.TestChoicesFixture import co.electriccoin.zcash.ui.fixture.TestChoicesFixture
import co.electriccoin.zcash.ui.screen.backup.model.BackupStage import co.electriccoin.zcash.ui.screen.backup.model.BackupStage
import co.electriccoin.zcash.ui.screen.backup.view.BackupTestSetup import co.electriccoin.zcash.ui.screen.backup.view.LongBackupTestSetup
import co.electriccoin.zcash.ui.test.getStringResource import co.electriccoin.zcash.ui.test.getStringResource
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Rule import org.junit.Rule
@ -20,8 +20,8 @@ class BackupActivityTest : UiTestPrerequisites() {
@get:Rule @get:Rule
val composeTestRule = createAndroidComposeRule<UiTestingActivity>() val composeTestRule = createAndroidComposeRule<UiTestingActivity>()
private fun newTestSetup(): BackupTestSetup { private fun newTestSetup(): LongBackupTestSetup {
return BackupTestSetup( return LongBackupTestSetup(
composeTestRule, composeTestRule,
BackupStage.EducationOverview, BackupStage.EducationOverview,
TestChoicesFixture.new(TestChoicesFixture.INITIAL_CHOICES) TestChoicesFixture.new(TestChoicesFixture.INITIAL_CHOICES)

View File

@ -16,7 +16,7 @@ import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.fixture.TestChoicesFixture import co.electriccoin.zcash.ui.fixture.TestChoicesFixture
import co.electriccoin.zcash.ui.screen.backup.BackupTag import co.electriccoin.zcash.ui.screen.backup.BackupTag
import co.electriccoin.zcash.ui.screen.backup.model.BackupStage import co.electriccoin.zcash.ui.screen.backup.model.BackupStage
import co.electriccoin.zcash.ui.screen.backup.view.BackupTestSetup import co.electriccoin.zcash.ui.screen.backup.view.LongBackupTestSetup
import co.electriccoin.zcash.ui.test.getStringResource import co.electriccoin.zcash.ui.test.getStringResource
import org.junit.Rule import org.junit.Rule
import kotlin.test.Test import kotlin.test.Test
@ -29,8 +29,8 @@ class BackupIntegrationTest : UiTestPrerequisites() {
@get:Rule @get:Rule
val composeTestRule = createComposeRule() val composeTestRule = createComposeRule()
private fun newTestSetup(initialStage: BackupStage): BackupTestSetup { private fun newTestSetup(initialStage: BackupStage): LongBackupTestSetup {
return BackupTestSetup( return LongBackupTestSetup(
composeTestRule, composeTestRule,
initialStage, initialStage,
TestChoicesFixture.new(TestChoicesFixture.INITIAL_CHOICES) TestChoicesFixture.new(TestChoicesFixture.INITIAL_CHOICES)

View File

@ -9,7 +9,7 @@ import co.electriccoin.zcash.ui.screen.backup.state.BackupState
import co.electriccoin.zcash.ui.screen.backup.state.TestChoices import co.electriccoin.zcash.ui.screen.backup.state.TestChoices
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
class BackupTestSetup( class LongBackupTestSetup(
private val composeTestRule: ComposeContentTestRule, private val composeTestRule: ComposeContentTestRule,
initialStage: BackupStage, initialStage: BackupStage,
private val initialChoices: TestChoices private val initialChoices: TestChoices
@ -53,7 +53,7 @@ class BackupTestSetup(
@Suppress("TestFunctionName") @Suppress("TestFunctionName")
fun DefaultContent() { fun DefaultContent() {
ZcashTheme { ZcashTheme {
BackupWallet( LongNewWalletBackup(
PersistableWalletFixture.new(), PersistableWalletFixture.new(),
state, state,
initialChoices, initialChoices,

View File

@ -21,12 +21,12 @@ import org.junit.Assert.assertEquals
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
class BackupViewTest : UiTestPrerequisites() { class LongBackupViewTest : UiTestPrerequisites() {
@get:Rule @get:Rule
val composeTestRule = createComposeRule() val composeTestRule = createComposeRule()
private fun newTestSetup(initialStage: BackupStage): BackupTestSetup { private fun newTestSetup(initialStage: BackupStage): LongBackupTestSetup {
return BackupTestSetup(composeTestRule, initialStage, TestChoicesFixture.new(mutableMapOf())).apply { return LongBackupTestSetup(composeTestRule, initialStage, TestChoicesFixture.new(mutableMapOf())).apply {
setDefaultContent() setDefaultContent()
} }
} }
@ -212,17 +212,18 @@ class BackupViewTest : UiTestPrerequisites() {
} }
} }
fun ComposeContentTestRule.clickCopyToBuffer() { private fun ComposeContentTestRule.clickCopyToBuffer() {
// open menu // open menu
onNodeWithContentDescription( onNodeWithContentDescription(
getStringResource(R.string.new_wallet_toolbar_more_button_content_description) getStringResource(R.string.new_wallet_toolbar_more_button_content_description)
).also { moreMenu -> ).also { moreMenu ->
moreMenu.performClick() moreMenu.performClick()
// click menu button }
onNodeWithText(
getStringResource(R.string.new_wallet_3_button_copy) // click menu button
).also { menuButton -> onNodeWithText(
menuButton.performClick() getStringResource(R.string.new_wallet_3_button_copy)
} ).also { menuButton ->
menuButton.performClick()
} }
} }

View File

@ -19,7 +19,7 @@ import org.junit.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
class BackupViewsSecuredScreenTest : UiTestPrerequisites() { class LongBackupViewsSecuredScreenTest : UiTestPrerequisites() {
@get:Rule @get:Rule
val composeTestRule = createComposeRule() val composeTestRule = createComposeRule()
@ -54,7 +54,7 @@ class BackupViewsSecuredScreenTest : UiTestPrerequisites() {
composeTestRule.setContent { composeTestRule.setContent {
CompositionLocalProvider(LocalScreenSecurity provides screenSecurity) { CompositionLocalProvider(LocalScreenSecurity provides screenSecurity) {
ZcashTheme { ZcashTheme {
BackupWallet( LongNewWalletBackup(
PersistableWalletFixture.new(), PersistableWalletFixture.new(),
state, state,
TestChoicesFixture.new(mutableMapOf()), TestChoicesFixture.new(mutableMapOf()),

View File

@ -0,0 +1,44 @@
package co.electriccoin.zcash.ui.screen.backup.view
import androidx.compose.runtime.Composable
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import cash.z.ecc.sdk.fixture.PersistableWalletFixture
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import java.util.concurrent.atomic.AtomicInteger
class ShortBackupTestSetup(
private val composeTestRule: ComposeContentTestRule,
) {
private val onCopyToClipboardCount = AtomicInteger(0)
private val onCompleteCallbackCount = AtomicInteger(0)
fun getOnCopyToClipboardCount(): Int {
composeTestRule.waitForIdle()
return onCopyToClipboardCount.get()
}
fun getOnCompleteCallbackCount(): Int {
composeTestRule.waitForIdle()
return onCompleteCallbackCount.get()
}
@Composable
@Suppress("TestFunctionName")
fun DefaultContent() {
ZcashTheme {
ShortNewWalletBackup(
PersistableWalletFixture.new(),
onCopyToClipboard = { onCopyToClipboardCount.incrementAndGet() },
onComplete = { onCompleteCallbackCount.incrementAndGet() },
)
}
}
fun setDefaultContent() {
composeTestRule.setContent {
DefaultContent()
}
}
}

View File

@ -0,0 +1,64 @@
package co.electriccoin.zcash.ui.screen.backup.view
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.filters.MediumTest
import co.electriccoin.zcash.test.UiTestPrerequisites
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.test.getStringResource
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
class ShortBackupViewTest : UiTestPrerequisites() {
@get:Rule
val composeTestRule = createComposeRule()
private fun newTestSetup(): ShortBackupTestSetup {
return ShortBackupTestSetup(composeTestRule).apply {
setDefaultContent()
}
}
@Test
@MediumTest
fun copy_to_clipboard() {
val testSetup = newTestSetup()
composeTestRule.copyToClipboard()
assertEquals(1, testSetup.getOnCopyToClipboardCount())
}
@Test
@MediumTest
fun click_finish() {
val testSetup = newTestSetup()
composeTestRule.onNodeWithText(getStringResource(R.string.new_wallet_short_button_finished)).also {
it.performClick()
}
assertEquals(0, testSetup.getOnCopyToClipboardCount())
assertEquals(1, testSetup.getOnCompleteCallbackCount())
}
}
private fun ComposeContentTestRule.copyToClipboard() {
// open menu
onNodeWithContentDescription(
getStringResource(R.string.new_wallet_toolbar_more_button_content_description)
).also { moreMenu ->
moreMenu.performClick()
}
// click menu button
onNodeWithText(
getStringResource(R.string.new_wallet_short_copy)
).also { menuButton ->
menuButton.performClick()
}
}

View File

@ -0,0 +1,55 @@
package co.electriccoin.zcash.ui.screen.backup.view
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.filters.MediumTest
import cash.z.ecc.sdk.fixture.PersistableWalletFixture
import co.electriccoin.zcash.test.UiTestPrerequisites
import co.electriccoin.zcash.ui.common.LocalScreenSecurity
import co.electriccoin.zcash.ui.common.ScreenSecurity
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
import kotlin.test.assertEquals
@OptIn(ExperimentalCoroutinesApi::class)
class ShortBackupViewsSecuredScreenTest : UiTestPrerequisites() {
@get:Rule
val composeTestRule = createComposeRule()
private fun newTestSetup() =
TestSetup(composeTestRule).apply {
setContentView()
}
@Test
@MediumTest
fun acquireScreenSecurity() = runTest {
val testSetup = newTestSetup()
assertEquals(1, testSetup.getSecureScreenCount())
}
private class TestSetup(private val composeTestRule: ComposeContentTestRule) {
private val screenSecurity = ScreenSecurity()
fun getSecureScreenCount() = screenSecurity.referenceCount.value
fun setContentView() {
composeTestRule.setContent {
CompositionLocalProvider(LocalScreenSecurity provides screenSecurity) {
ZcashTheme {
ShortNewWalletBackup(
PersistableWalletFixture.new(),
onCopyToClipboard = { },
onComplete = { }
)
}
}
}
}
}
}

View File

@ -5,21 +5,13 @@ import androidx.compose.ui.test.junit4.ComposeContentTestRule
import co.electriccoin.zcash.ui.design.theme.ZcashTheme import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import co.electriccoin.zcash.ui.screen.onboarding.model.OnboardingStage import co.electriccoin.zcash.ui.screen.onboarding.model.OnboardingStage
import co.electriccoin.zcash.ui.screen.onboarding.state.OnboardingState import co.electriccoin.zcash.ui.screen.onboarding.state.OnboardingState
import co.electriccoin.zcash.ui.screen.onboarding.view.Onboarding import co.electriccoin.zcash.ui.screen.onboarding.view.LongOnboarding
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
class OnboardingTestSetup( class LongOnboardingTestSetup(
private val composeTestRule: ComposeContentTestRule, private val composeTestRule: ComposeContentTestRule,
private val isFullOnboardingEnabled: Boolean,
initialStage: OnboardingStage initialStage: OnboardingStage
) { ) {
init {
if (!isFullOnboardingEnabled) {
require(initialStage == OnboardingStage.Wallet) {
"When full onboarding is disabled, the initial stage must be Wallet"
}
}
}
private val onboardingState = OnboardingState(initialStage) private val onboardingState = OnboardingState(initialStage)
@ -45,8 +37,7 @@ class OnboardingTestSetup(
@Suppress("TestFunctionName") @Suppress("TestFunctionName")
fun DefaultContent() { fun DefaultContent() {
ZcashTheme { ZcashTheme {
Onboarding( LongOnboarding(
isFullOnboardingEnabled,
onboardingState, onboardingState,
isDebugMenuEnabled = false, isDebugMenuEnabled = false,
onCreateWallet = { onCreateWalletCallbackCount.incrementAndGet() }, onCreateWallet = { onCreateWalletCallbackCount.incrementAndGet() },

View File

@ -0,0 +1,44 @@
package co.electriccoin.zcash.ui.screen.onboarding
import androidx.compose.runtime.Composable
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import co.electriccoin.zcash.ui.screen.onboarding.view.ShortOnboarding
import java.util.concurrent.atomic.AtomicInteger
class ShortOnboardingTestSetup(
private val composeTestRule: ComposeContentTestRule,
) {
private val onCreateWalletCallbackCount = AtomicInteger(0)
private val onImportWalletCallbackCount = AtomicInteger(0)
fun getOnCreateWalletCallbackCount(): Int {
composeTestRule.waitForIdle()
return onCreateWalletCallbackCount.get()
}
fun getOnImportWalletCallbackCount(): Int {
composeTestRule.waitForIdle()
return onImportWalletCallbackCount.get()
}
@Composable
@Suppress("TestFunctionName")
fun DefaultContent() {
ZcashTheme {
ShortOnboarding(
isDebugMenuEnabled = false,
onCreateWallet = { onCreateWalletCallbackCount.incrementAndGet() },
onImportWallet = { onImportWalletCallbackCount.incrementAndGet() },
// We aren't testing this because it is for debug builds only.
onFixtureWallet = {}
)
}
}
fun setDefaultContent() {
composeTestRule.setContent {
DefaultContent()
}
}
}

View File

@ -7,7 +7,7 @@ import androidx.test.filters.MediumTest
import co.electriccoin.zcash.test.UiTestPrerequisites import co.electriccoin.zcash.test.UiTestPrerequisites
import co.electriccoin.zcash.ui.R import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.common.UiTestingActivity import co.electriccoin.zcash.ui.common.UiTestingActivity
import co.electriccoin.zcash.ui.screen.onboarding.OnboardingTestSetup import co.electriccoin.zcash.ui.screen.onboarding.LongOnboardingTestSetup
import co.electriccoin.zcash.ui.screen.onboarding.model.OnboardingStage import co.electriccoin.zcash.ui.screen.onboarding.model.OnboardingStage
import co.electriccoin.zcash.ui.test.getStringResource import co.electriccoin.zcash.ui.test.getStringResource
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
@ -18,9 +18,8 @@ class OnboardingActivityTest : UiTestPrerequisites() {
@get:Rule @get:Rule
val composeTestRule = createAndroidComposeRule<UiTestingActivity>() val composeTestRule = createAndroidComposeRule<UiTestingActivity>()
private fun newTestSetup() = OnboardingTestSetup( private fun newTestSetup() = LongOnboardingTestSetup(
composeTestRule, composeTestRule,
isFullOnboardingEnabled = true,
OnboardingStage.ShieldedByDefault OnboardingStage.ShieldedByDefault
) )

View File

@ -7,7 +7,7 @@ import androidx.compose.ui.test.performClick
import androidx.test.filters.MediumTest import androidx.test.filters.MediumTest
import co.electriccoin.zcash.test.UiTestPrerequisites import co.electriccoin.zcash.test.UiTestPrerequisites
import co.electriccoin.zcash.ui.R import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.screen.onboarding.OnboardingTestSetup import co.electriccoin.zcash.ui.screen.onboarding.LongOnboardingTestSetup
import co.electriccoin.zcash.ui.screen.onboarding.model.OnboardingStage import co.electriccoin.zcash.ui.screen.onboarding.model.OnboardingStage
import co.electriccoin.zcash.ui.test.getStringResource import co.electriccoin.zcash.ui.test.getStringResource
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
@ -18,9 +18,8 @@ class OnboardingIntegrationTest : UiTestPrerequisites() {
@get:Rule @get:Rule
val composeTestRule = createComposeRule() val composeTestRule = createComposeRule()
private fun newTestSetup(initialStage: OnboardingStage) = OnboardingTestSetup( private fun newTestSetup(initialStage: OnboardingStage) = LongOnboardingTestSetup(
composeTestRule, composeTestRule,
isFullOnboardingEnabled = true,
initialStage initialStage
) )

View File

@ -9,19 +9,19 @@ import androidx.compose.ui.test.performClick
import androidx.test.filters.MediumTest import androidx.test.filters.MediumTest
import co.electriccoin.zcash.test.UiTestPrerequisites import co.electriccoin.zcash.test.UiTestPrerequisites
import co.electriccoin.zcash.ui.R import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.screen.onboarding.OnboardingTestSetup import co.electriccoin.zcash.ui.screen.onboarding.LongOnboardingTestSetup
import co.electriccoin.zcash.ui.screen.onboarding.model.OnboardingStage import co.electriccoin.zcash.ui.screen.onboarding.model.OnboardingStage
import co.electriccoin.zcash.ui.test.getStringResource import co.electriccoin.zcash.ui.test.getStringResource
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
class OnboardingViewTest : UiTestPrerequisites() { class LongOnboardingViewTest : UiTestPrerequisites() {
@get:Rule @get:Rule
val composeTestRule = createComposeRule() val composeTestRule = createComposeRule()
private fun newTestSetup(isFullOnboardingEnabled: Boolean = true, initialStage: OnboardingStage): OnboardingTestSetup { private fun newTestSetup(initialStage: OnboardingStage): LongOnboardingTestSetup {
return OnboardingTestSetup(composeTestRule, isFullOnboardingEnabled, initialStage).apply { return LongOnboardingTestSetup(composeTestRule, initialStage).apply {
setDefaultContent() setDefaultContent()
} }
} }
@ -180,36 +180,6 @@ class OnboardingViewTest : UiTestPrerequisites() {
} }
} }
@Test
@MediumTest
fun stage_4_layout_short_onboarding() {
newTestSetup(isFullOnboardingEnabled = false, initialStage = OnboardingStage.Wallet)
composeTestRule.onNodeWithText(getStringResource(R.string.onboarding_skip)).also {
it.assertDoesNotExist()
}
composeTestRule.onNodeWithText(getStringResource(R.string.onboarding_next)).also {
it.assertDoesNotExist()
}
composeTestRule.onNodeWithContentDescription(getStringResource(R.string.onboarding_back)).also {
it.assertDoesNotExist()
}
composeTestRule.onNodeWithText(getStringResource(R.string.onboarding_4_create_new_wallet)).also {
it.assertExists()
it.assertIsEnabled()
it.assertHasClickAction()
}
composeTestRule.onNodeWithText(getStringResource(R.string.onboarding_4_import_existing_wallet)).also {
it.assertExists()
it.assertIsEnabled()
it.assertHasClickAction()
}
}
@Test @Test
@MediumTest @MediumTest
fun stage_1_skip() { fun stage_1_skip() {

View File

@ -0,0 +1,68 @@
package co.electriccoin.zcash.ui.screen.onboarding.view
import androidx.compose.ui.test.assertHasClickAction
import androidx.compose.ui.test.assertIsEnabled
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.filters.MediumTest
import co.electriccoin.zcash.test.UiTestPrerequisites
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.screen.onboarding.ShortOnboardingTestSetup
import co.electriccoin.zcash.ui.test.getStringResource
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
class ShortOnboardingViewTest : UiTestPrerequisites() {
@get:Rule
val composeTestRule = createComposeRule()
private fun newTestSetup(): ShortOnboardingTestSetup {
return ShortOnboardingTestSetup(composeTestRule).apply {
setDefaultContent()
}
}
@Test
@MediumTest
fun layout() {
newTestSetup()
composeTestRule.onNodeWithText(getStringResource(R.string.onboarding_short_import_existing_wallet)).also {
it.assertExists()
it.assertIsEnabled()
it.assertHasClickAction()
}
composeTestRule.onNodeWithText(getStringResource(R.string.onboarding_short_create_new_wallet)).also {
it.assertExists()
it.assertIsEnabled()
it.assertHasClickAction()
}
}
@Test
@MediumTest
fun click_create_wallet() {
val testSetup = newTestSetup()
val newWalletButton = composeTestRule.onNodeWithText(getStringResource(R.string.onboarding_short_create_new_wallet))
newWalletButton.performClick()
assertEquals(1, testSetup.getOnCreateWalletCallbackCount())
assertEquals(0, testSetup.getOnImportWalletCallbackCount())
}
@Test
@MediumTest
fun click_import_wallet() {
val testSetup = newTestSetup()
val newWalletButton = composeTestRule.onNodeWithText(getStringResource(R.string.onboarding_short_import_existing_wallet))
newWalletButton.performClick()
assertEquals(1, testSetup.getOnImportWalletCallbackCount())
assertEquals(0, testSetup.getOnCreateWalletCallbackCount())
}
}

View File

@ -24,7 +24,7 @@ import co.electriccoin.zcash.ui.design.component.ConfigurationOverride
import co.electriccoin.zcash.ui.design.component.GradientSurface import co.electriccoin.zcash.ui.design.component.GradientSurface
import co.electriccoin.zcash.ui.design.component.Override import co.electriccoin.zcash.ui.design.component.Override
import co.electriccoin.zcash.ui.design.theme.ZcashTheme import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import co.electriccoin.zcash.ui.screen.backup.WrapBackup import co.electriccoin.zcash.ui.screen.backup.WrapNewWallet
import co.electriccoin.zcash.ui.screen.home.viewmodel.HomeViewModel import co.electriccoin.zcash.ui.screen.home.viewmodel.HomeViewModel
import co.electriccoin.zcash.ui.screen.home.viewmodel.SecretState import co.electriccoin.zcash.ui.screen.home.viewmodel.SecretState
import co.electriccoin.zcash.ui.screen.home.viewmodel.WalletViewModel import co.electriccoin.zcash.ui.screen.home.viewmodel.WalletViewModel
@ -129,7 +129,7 @@ class MainActivity : ComponentActivity() {
WrapOnboarding() WrapOnboarding()
} }
is SecretState.NeedsBackup -> { is SecretState.NeedsBackup -> {
WrapBackup( WrapNewWallet(
secretState.persistableWallet, secretState.persistableWallet,
onBackupComplete = { walletViewModel.persistBackupComplete() } onBackupComplete = { walletViewModel.persistBackupComplete() }
) )

View File

@ -9,7 +9,12 @@ object ConfigurationEntries {
/* /*
* The full onboarding flow is functional and tested, but it is disabled by default for an initially minimal feature set. * The full onboarding flow is functional and tested, but it is disabled by default for an initially minimal feature set.
*/ */
val IS_FULL_ONBOARDING_ENABLED = BooleanConfigurationEntry(ConfigKey("is_full_onboarding_enabled"), false) val IS_SHORT_ONBOARDING_UX = BooleanConfigurationEntry(ConfigKey("is_short_onboarding_ux"), true)
/*
* The full new wallet flow is functional and tested, but it is disabled by default for an initially minimal feature set.
*/
val IS_SHORT_NEW_WALLET_BACKUP_UX = BooleanConfigurationEntry(ConfigKey("is_short_new_wallet_backup_ux"), true)
/* /*
* A troubleshooting step. If we fix our bugs, this should be unnecessary. * A troubleshooting step. If we fix our bugs, this should be unnecessary.

View File

@ -11,27 +11,48 @@ import androidx.compose.runtime.saveable.rememberSaveable
import cash.z.ecc.android.sdk.model.PersistableWallet import cash.z.ecc.android.sdk.model.PersistableWallet
import co.electriccoin.zcash.ui.MainActivity import co.electriccoin.zcash.ui.MainActivity
import co.electriccoin.zcash.ui.R import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.configuration.ConfigurationEntries
import co.electriccoin.zcash.ui.configuration.RemoteConfig
import co.electriccoin.zcash.ui.screen.backup.ext.Saver import co.electriccoin.zcash.ui.screen.backup.ext.Saver
import co.electriccoin.zcash.ui.screen.backup.state.BackupState import co.electriccoin.zcash.ui.screen.backup.state.BackupState
import co.electriccoin.zcash.ui.screen.backup.state.TestChoices import co.electriccoin.zcash.ui.screen.backup.state.TestChoices
import co.electriccoin.zcash.ui.screen.backup.view.BackupWallet import co.electriccoin.zcash.ui.screen.backup.view.LongNewWalletBackup
import co.electriccoin.zcash.ui.screen.backup.view.ShortNewWalletBackup
@Composable @Composable
internal fun MainActivity.WrapBackup( internal fun MainActivity.WrapNewWallet(
persistableWallet: PersistableWallet, persistableWallet: PersistableWallet,
onBackupComplete: () -> Unit onBackupComplete: () -> Unit
) { ) {
WrapBackup(this, persistableWallet, onBackupComplete) if (ConfigurationEntries.IS_SHORT_NEW_WALLET_BACKUP_UX.getValue(RemoteConfig.current)) {
WrapShortNewWallet(
persistableWallet,
onBackupComplete = onBackupComplete
)
} else {
WrapLongNewWallet(
persistableWallet,
onBackupComplete = onBackupComplete
)
}
}
@Composable
internal fun MainActivity.WrapLongNewWallet(
persistableWallet: PersistableWallet,
onBackupComplete: () -> Unit
) {
WrapLongNewWallet(this, persistableWallet, onBackupComplete)
} }
// This layer of indirection allows for activity re-creation tests // This layer of indirection allows for activity re-creation tests
@Composable @Composable
internal fun WrapBackup( internal fun WrapLongNewWallet(
activity: ComponentActivity, activity: ComponentActivity,
persistableWallet: PersistableWallet, persistableWallet: PersistableWallet,
onBackupComplete: () -> Unit onBackupComplete: () -> Unit
) { ) {
WrapBackup( WrapLongNewWallet(
persistableWallet, persistableWallet,
onCopyToClipboard = { copyToClipboard(activity.applicationContext, persistableWallet) }, onCopyToClipboard = { copyToClipboard(activity.applicationContext, persistableWallet) },
onBackupComplete = onBackupComplete onBackupComplete = onBackupComplete
@ -40,7 +61,7 @@ internal fun WrapBackup(
// This extra layer of indirection allows unit tests to validate the screen state retention. // This extra layer of indirection allows unit tests to validate the screen state retention.
@Composable @Composable
internal fun WrapBackup( internal fun WrapLongNewWallet(
persistableWallet: PersistableWallet, persistableWallet: PersistableWallet,
onCopyToClipboard: () -> Unit, onCopyToClipboard: () -> Unit,
onBackupComplete: () -> Unit onBackupComplete: () -> Unit
@ -48,7 +69,7 @@ internal fun WrapBackup(
val testChoices by rememberSaveable(stateSaver = TestChoices.Saver) { mutableStateOf(TestChoices()) } val testChoices by rememberSaveable(stateSaver = TestChoices.Saver) { mutableStateOf(TestChoices()) }
val backupState by rememberSaveable(stateSaver = BackupState.Saver) { mutableStateOf(BackupState()) } val backupState by rememberSaveable(stateSaver = BackupState.Saver) { mutableStateOf(BackupState()) }
BackupWallet( LongNewWalletBackup(
persistableWallet, persistableWallet,
backupState, backupState,
testChoices, testChoices,
@ -58,7 +79,41 @@ internal fun WrapBackup(
) )
} }
fun copyToClipboard(context: Context, persistableWallet: PersistableWallet) { @Composable
private fun MainActivity.WrapShortNewWallet(
persistableWallet: PersistableWallet,
onBackupComplete: () -> Unit
) {
WrapShortNewWallet(this, persistableWallet, onBackupComplete)
}
@Composable
private fun WrapShortNewWallet(
activity: ComponentActivity,
persistableWallet: PersistableWallet,
onBackupComplete: () -> Unit
) {
WrapShortNewWallet(
persistableWallet,
onCopyToClipboard = { copyToClipboard(activity.applicationContext, persistableWallet) },
onNewWalletComplete = onBackupComplete
)
}
@Composable
private fun WrapShortNewWallet(
persistableWallet: PersistableWallet,
onCopyToClipboard: () -> Unit,
onNewWalletComplete: () -> Unit
) {
ShortNewWalletBackup(
persistableWallet,
onCopyToClipboard = onCopyToClipboard,
onComplete = onNewWalletComplete,
)
}
internal fun copyToClipboard(context: Context, persistableWallet: PersistableWallet) {
val clipboardManager = context.getSystemService(ClipboardManager::class.java) val clipboardManager = context.getSystemService(ClipboardManager::class.java)
val data = ClipData.newPlainText( val data = ClipData.newPlainText(
context.getString(R.string.new_wallet_clipboard_tag), context.getString(R.string.new_wallet_clipboard_tag),

View File

@ -3,6 +3,7 @@
package co.electriccoin.zcash.ui.screen.backup.view package co.electriccoin.zcash.ui.screen.backup.view
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
@ -22,6 +23,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -38,6 +40,7 @@ import kotlinx.collections.immutable.ImmutableList
* @param onChoiceSelected Callback with the positional index of the item the user selected from [choices]. * @param onChoiceSelected Callback with the positional index of the item the user selected from [choices].
*/ */
@Composable @Composable
@Suppress("LongMethod")
fun ChipDropDown( fun ChipDropDown(
chipIndex: Index, chipIndex: Index,
dropdownText: String, dropdownText: String,
@ -58,7 +61,11 @@ fun ChipDropDown(
tonalElevation = 8.dp, tonalElevation = 8.dp,
shadowElevation = 8.dp shadowElevation = 8.dp
) { ) {
Row(modifier = Modifier.padding(8.dp)) { Row(
modifier = Modifier.padding(8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text( Text(
text = (chipIndex.value + 1).toString(), text = (chipIndex.value + 1).toString(),
style = ZcashTheme.typography.chipIndex, style = ZcashTheme.typography.chipIndex,

View File

@ -65,7 +65,7 @@ import kotlinx.collections.immutable.toPersistentList
fun ComposablePreview() { fun ComposablePreview() {
ZcashTheme(darkTheme = false) { ZcashTheme(darkTheme = false) {
GradientSurface { GradientSurface {
BackupWallet( LongNewWalletBackup(
PersistableWalletFixture.new(), PersistableWalletFixture.new(),
BackupState(BackupStage.EducationOverview), BackupState(BackupStage.EducationOverview),
TestChoicesFixture.new(mutableMapOf()), TestChoicesFixture.new(mutableMapOf()),
@ -83,7 +83,7 @@ fun ComposablePreview() {
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@Suppress("LongParameterList") @Suppress("LongParameterList")
fun BackupWallet( fun LongNewWalletBackup(
wallet: PersistableWallet, wallet: PersistableWallet,
backupState: BackupState, backupState: BackupState,
choices: TestChoices, choices: TestChoices,

View File

@ -0,0 +1,159 @@
@file:Suppress("TooManyFunctions")
package co.electriccoin.zcash.ui.screen.backup.view
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
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.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
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.res.stringResource
import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview
import cash.z.ecc.android.sdk.model.PersistableWallet
import cash.z.ecc.sdk.fixture.PersistableWalletFixture
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.common.SecureScreen
import co.electriccoin.zcash.ui.design.component.Body
import co.electriccoin.zcash.ui.design.component.ChipGrid
import co.electriccoin.zcash.ui.design.component.GradientSurface
import co.electriccoin.zcash.ui.design.component.PrimaryButton
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import kotlinx.collections.immutable.toPersistentList
@Preview(device = Devices.PIXEL_4)
@Composable
fun ComposablePreviewShort() {
ZcashTheme(darkTheme = false) {
GradientSurface {
ShortNewWalletBackup(
PersistableWalletFixture.new(),
onCopyToClipboard = {},
onComplete = {},
)
}
}
}
/**
* @param onComplete Callback when the user has confirmed viewing the seed phrase.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ShortNewWalletBackup(
wallet: PersistableWallet,
onCopyToClipboard: () -> Unit,
onComplete: () -> Unit,
) {
Scaffold(
topBar = {
ShortNewWalletTopAppBar(
onCopyToClipboard = onCopyToClipboard,
)
},
bottomBar = {
ShortNewWalletBottomNav(
onComplete = onComplete,
)
}
) { paddingValues ->
ShortNewWalletMainContent(
paddingValues = paddingValues,
wallet = wallet,
)
}
}
@Composable
private fun ShortNewWalletMainContent(
paddingValues: PaddingValues,
wallet: PersistableWallet,
) {
Column(
Modifier
.padding(
top = paddingValues.calculateTopPadding(),
bottom = paddingValues.calculateBottomPadding()
)
) {
SeedPhrase(wallet)
}
}
@Composable
private fun SeedPhrase(persistableWallet: PersistableWallet) {
SecureScreen()
Column(
Modifier
.verticalScroll(rememberScrollState())
.padding(vertical = ZcashTheme.paddings.padding)
) {
Body(stringResource(R.string.new_wallet_short_body))
ChipGrid(persistableWallet.seedPhrase.split.toPersistentList())
}
}
@Composable
@OptIn(ExperimentalMaterial3Api::class)
private fun ShortNewWalletTopAppBar(
onCopyToClipboard: () -> Unit,
) {
TopAppBar(
title = { Text(text = stringResource(id = R.string.new_wallet_short_header)) },
actions = {
CopySeedMenu(onCopyToClipboard)
}
)
}
@Composable
private fun CopySeedMenu(onCopyToClipboard: () -> Unit) {
Column {
var expanded by rememberSaveable { mutableStateOf(false) }
IconButton(onClick = { expanded = true }) {
Icon(
Icons.Default.MoreVert,
contentDescription = stringResource(R.string.new_wallet_toolbar_more_button_content_description)
)
}
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
DropdownMenuItem(
text = { Text(stringResource(R.string.new_wallet_short_copy)) },
onClick = {
expanded = false
onCopyToClipboard()
}
)
}
}
}
@Suppress("LongParameterList")
@Composable
private fun ShortNewWalletBottomNav(
onComplete: () -> Unit
) {
Column {
PrimaryButton(onClick = onComplete, text = stringResource(R.string.new_wallet_short_button_finished))
}
}

View File

@ -21,9 +21,8 @@ import co.electriccoin.zcash.ui.MainActivity
import co.electriccoin.zcash.ui.configuration.ConfigurationEntries import co.electriccoin.zcash.ui.configuration.ConfigurationEntries
import co.electriccoin.zcash.ui.configuration.RemoteConfig import co.electriccoin.zcash.ui.configuration.RemoteConfig
import co.electriccoin.zcash.ui.screen.home.viewmodel.WalletViewModel import co.electriccoin.zcash.ui.screen.home.viewmodel.WalletViewModel
import co.electriccoin.zcash.ui.screen.onboarding.model.OnboardingStage import co.electriccoin.zcash.ui.screen.onboarding.view.LongOnboarding
import co.electriccoin.zcash.ui.screen.onboarding.state.OnboardingState import co.electriccoin.zcash.ui.screen.onboarding.view.ShortOnboarding
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.viewmodel.OnboardingViewModel
import co.electriccoin.zcash.ui.screen.restore.view.RestoreWallet 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.CompleteWordSetState
@ -51,55 +50,58 @@ internal fun WrapOnboarding(
// TODO [#383]: https://github.com/zcash/secant-android-wallet/issues/383 // TODO [#383]: https://github.com/zcash/secant-android-wallet/issues/383
if (!onboardingViewModel.isImporting.collectAsStateWithLifecycle().value) { if (!onboardingViewModel.isImporting.collectAsStateWithLifecycle().value) {
val isFullOnboardingEnabled = ConfigurationEntries.IS_FULL_ONBOARDING_ENABLED.getValue(RemoteConfig.current) val onCreateWallet = {
val onboardingState = if (isFullOnboardingEnabled) { if (FirebaseTestLabUtil.isFirebaseTestLab(applicationContext)) {
onboardingViewModel.onboardingState
} else {
// Force to the last screen, which is the "create wallet" screen.
// This simplifies the implementation inside the Onboarding composable.
OnboardingState(OnboardingStage.values().last())
}
Onboarding(
isFullOnboardingEnabled = isFullOnboardingEnabled,
onboardingState = 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.setIsImporting(true)
},
onCreateWallet = {
if (FirebaseTestLabUtil.isFirebaseTestLab(applicationContext)) {
persistExistingWalletWithSeedPhrase(
applicationContext,
walletViewModel,
SeedPhraseFixture.new()
)
return@Onboarding
}
walletViewModel.persistNewWallet()
},
onFixtureWallet = {
persistExistingWalletWithSeedPhrase( persistExistingWalletWithSeedPhrase(
applicationContext, applicationContext,
walletViewModel, walletViewModel,
SeedPhraseFixture.new() SeedPhraseFixture.new()
) )
} else {
walletViewModel.persistNewWallet()
} }
) }
val 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()
)
} else {
onboardingViewModel.setIsImporting(true)
}
}
val onFixtureWallet = {
persistExistingWalletWithSeedPhrase(
applicationContext,
walletViewModel,
SeedPhraseFixture.new()
)
}
if (ConfigurationEntries.IS_SHORT_ONBOARDING_UX.getValue(RemoteConfig.current)) {
ShortOnboarding(
isDebugMenuEnabled = isDebugMenuEnabled,
onImportWallet = onImportWallet,
onCreateWallet = onCreateWallet,
onFixtureWallet = onFixtureWallet
)
} else {
LongOnboarding(
onboardingState = onboardingViewModel.onboardingState,
isDebugMenuEnabled = isDebugMenuEnabled,
onImportWallet = onImportWallet,
onCreateWallet = onCreateWallet,
onFixtureWallet = onFixtureWallet
)
}
activity.reportFullyDrawn() activity.reportFullyDrawn()
} else { } else {

View File

@ -54,8 +54,7 @@ import co.electriccoin.zcash.ui.screen.onboarding.state.OnboardingState
fun ComposablePreview() { fun ComposablePreview() {
ZcashTheme(darkTheme = true) { ZcashTheme(darkTheme = true) {
GradientSurface { GradientSurface {
Onboarding( LongOnboarding(
isFullOnboardingEnabled = true,
OnboardingState(OnboardingStage.Wallet), OnboardingState(OnboardingStage.Wallet),
isDebugMenuEnabled = false, isDebugMenuEnabled = false,
onImportWallet = {}, onImportWallet = {},
@ -67,16 +66,13 @@ fun ComposablePreview() {
} }
/** /**
* @param isFullOnboardingEnabled Feature toggle to control whether the full onboarding flow is enabled. If disabled, then an abbreviated flow is shown
* and the onboarding state is treated effectively as if it is [OnboardingStage.Wallet].
* @param onImportWallet Callback when the user decides to import an existing wallet. * @param onImportWallet Callback when the user decides to import an existing wallet.
* @param onCreateWallet Callback when the user decides to create a new wallet. * @param onCreateWallet Callback when the user decides to create a new wallet.
*/ */
@Composable @Composable
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Suppress("LongParameterList") @Suppress("LongParameterList")
fun Onboarding( fun LongOnboarding(
isFullOnboardingEnabled: Boolean,
onboardingState: OnboardingState, onboardingState: OnboardingState,
isDebugMenuEnabled: Boolean, isDebugMenuEnabled: Boolean,
onImportWallet: () -> Unit, onImportWallet: () -> Unit,
@ -86,7 +82,7 @@ fun Onboarding(
val currentStage = onboardingState.current.collectAsStateWithLifecycle().value val currentStage = onboardingState.current.collectAsStateWithLifecycle().value
Scaffold( Scaffold(
topBar = { topBar = {
OnboardingTopAppBar(isFullOnboardingEnabled, onboardingState, isDebugMenuEnabled, onFixtureWallet) OnboardingTopAppBar(onboardingState, isDebugMenuEnabled, onFixtureWallet)
}, },
bottomBar = { bottomBar = {
BottomNav(currentStage, onboardingState::goNext, onCreateWallet, onImportWallet) BottomNav(currentStage, onboardingState::goNext, onCreateWallet, onImportWallet)
@ -102,7 +98,6 @@ fun Onboarding(
@Composable @Composable
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
private fun OnboardingTopAppBar( private fun OnboardingTopAppBar(
isFullOnboardingEnabled: Boolean,
onboardingState: OnboardingState, onboardingState: OnboardingState,
isDebugMenuEnabled: Boolean, isDebugMenuEnabled: Boolean,
onFixtureWallet: () -> Unit onFixtureWallet: () -> Unit
@ -112,14 +107,12 @@ private fun OnboardingTopAppBar(
TopAppBar( TopAppBar(
title = { Text(text = stringResource(id = R.string.app_name)) }, title = { Text(text = stringResource(id = R.string.app_name)) },
navigationIcon = { navigationIcon = {
if (isFullOnboardingEnabled) { if (currentStage.hasPrevious()) {
if (currentStage.hasPrevious()) { IconButton(onboardingState::goPrevious) {
IconButton(onboardingState::goPrevious) { Icon(
Icon( imageVector = Icons.Filled.ArrowBack,
imageVector = Icons.Filled.ArrowBack, contentDescription = stringResource(R.string.onboarding_back)
contentDescription = stringResource(R.string.onboarding_back) )
)
}
} }
} }
}, },

View File

@ -0,0 +1,143 @@
@file:Suppress("TooManyFunctions")
package co.electriccoin.zcash.ui.screen.onboarding.view
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
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.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
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.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.design.component.GradientSurface
import co.electriccoin.zcash.ui.design.component.Header
import co.electriccoin.zcash.ui.design.component.PrimaryButton
import co.electriccoin.zcash.ui.design.component.TertiaryButton
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
@Preview
@Composable
fun ShortOnboardingComposablePreview() {
ZcashTheme(darkTheme = true) {
GradientSurface {
ShortOnboarding(
isDebugMenuEnabled = false,
onImportWallet = {},
onCreateWallet = {},
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
@OptIn(ExperimentalMaterial3Api::class)
fun ShortOnboarding(
isDebugMenuEnabled: Boolean,
onImportWallet: () -> Unit,
onCreateWallet: () -> Unit,
onFixtureWallet: () -> Unit
) {
Scaffold(
topBar = {
OnboardingTopAppBar(isDebugMenuEnabled, onFixtureWallet)
}
) { paddingValues ->
OnboardingMainContent(
paddingValues,
onImportWallet = onImportWallet,
onCreateWallet = onCreateWallet
)
}
}
@Composable
@OptIn(ExperimentalMaterial3Api::class)
private fun OnboardingTopAppBar(
isDebugMenuEnabled: Boolean,
onFixtureWallet: () -> Unit
) {
TopAppBar(
title = { Text(text = stringResource(id = R.string.app_name)) },
actions = {
if (isDebugMenuEnabled) {
DebugMenu(onFixtureWallet)
}
}
)
}
@Composable
private fun DebugMenu(onFixtureWallet: () -> Unit) {
Column {
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
)
}
}
}
@Composable
private fun OnboardingMainContent(
paddingValues: PaddingValues,
onImportWallet: () -> Unit,
onCreateWallet: () -> Unit,
) {
Column(
Modifier.padding(top = paddingValues.calculateTopPadding())
) {
Column(
Modifier
.padding(
start = ZcashTheme.paddings.padding,
end = ZcashTheme.paddings.padding,
bottom = paddingValues.calculateBottomPadding()
)
.fillMaxWidth()
) {
Header(
modifier = Modifier.padding(
top = ZcashTheme.paddings.padding,
bottom = ZcashTheme.paddings.paddingHalf
),
text = stringResource(R.string.onboarding_short_header)
)
PrimaryButton(onCreateWallet, stringResource(R.string.onboarding_short_create_new_wallet), Modifier.fillMaxWidth())
TertiaryButton(
onImportWallet,
stringResource(R.string.onboarding_short_import_existing_wallet),
Modifier.fillMaxWidth()
)
}
}
}

View File

@ -4,6 +4,11 @@
<string name="new_wallet_navigation_back_button_content_description">Back</string> <string name="new_wallet_navigation_back_button_content_description">Back</string>
<string name="new_wallet_toolbar_more_button_content_description">More</string> <string name="new_wallet_toolbar_more_button_content_description">More</string>
<string name="new_wallet_short_header">My secret phrase</string>
<string name="new_wallet_short_body">These words represent your funds and the security used to protect them.\n\nBack them up now!</string>
<string name="new_wallet_short_button_finished">I wrote it down</string>
<string name="new_wallet_short_copy">Copy to buffer</string>
<string name="new_wallet_1_header">First things first</string> <string name="new_wallet_1_header">First things first</string>
<string name="new_wallet_1_body_1">It is important to understand that you are in charge here. Great, right? YOU get to be the bank!</string> <string name="new_wallet_1_body_1">It is important to understand that you are in charge here. Great, right? YOU get to be the bank!</string>
<string name="new_wallet_1_body_2">But it also means that YOU are the customer, and you need to be self-reliant.\n\nSo how do you recover funds that youve hidden on a complete decentralized and private block-chain?</string> <string name="new_wallet_1_body_2">But it also means that YOU are the customer, and you need to be self-reliant.\n\nSo how do you recover funds that youve hidden on a complete decentralized and private block-chain?</string>

View File

@ -0,0 +1,6 @@
<resources>
<string name="new_wallet_header">My secret phrase</string>
<string name="new_wallet_body">These words represent your funds and the security used to protect them.\n\nBack them up now!.</string>
<string name="new_wallet_button_finished">I wrote it down</string>
<string name="new_wallet_button_copy">Copy to buffer</string>
</resources>

View File

@ -18,4 +18,8 @@
<string name="onboarding_4_image_content_description"></string> <string name="onboarding_4_image_content_description"></string>
<string name="onboarding_4_create_new_wallet">Create New Wallet</string> <string name="onboarding_4_create_new_wallet">Create New Wallet</string>
<string name="onboarding_4_import_existing_wallet">Import an Existing Wallet</string> <string name="onboarding_4_import_existing_wallet">Import an Existing Wallet</string>
<string name="onboarding_short_header">Its time to set up your no-frills wallet, powered by Zcash</string>
<string name="onboarding_short_create_new_wallet">Create New Wallet</string>
<string name="onboarding_short_import_existing_wallet">Import an Existing Wallet</string>
</resources> </resources>

View File

@ -2,8 +2,8 @@
<resources> <resources>
<string name="receive_title">Receive</string> <string name="receive_title">Receive</string>
<string name="receive_back_content_description">Back</string> <string name="receive_back_content_description">Back</string>
<string name="receive_qr_code_content_description">QR code for unified address</string> <string name="receive_qr_code_content_description">QR code for address</string>
<string name="receive_caption">Your UA Address</string> <string name="receive_caption">Your Address</string>
<string name="receive_see_address_details">See Address Details</string> <string name="receive_see_address_details">See Address Details</string>
</resources> </resources>

View File

@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="wallet_address_title">My wallet address</string> <string name="wallet_address_title">My wallet addresses</string>
<string name="wallet_address_back_content_description">Back</string> <string name="wallet_address_back_content_description">Back</string>
<string name="wallet_address_unified">Your Unified Address</string> <string name="wallet_address_unified">Your Unified Address</string>
<string name="wallet_address_header_includes">which includes</string> <string name="wallet_address_header_includes">which includes</string>
<string name="wallet_address_sapling">Shielded Sapling (NU1)</string> <string name="wallet_address_sapling">Shielded Sapling (NU1)</string>

View File

@ -146,7 +146,12 @@ class ScreenshotTest : UiTestPrerequisites() {
composeTestRule.waitUntil(DEFAULT_TIMEOUT_MILLISECONDS) { composeTestRule.activity.walletViewModel.secretState.value is SecretState.None } composeTestRule.waitUntil(DEFAULT_TIMEOUT_MILLISECONDS) { composeTestRule.activity.walletViewModel.secretState.value is SecretState.None }
if (ConfigurationEntries.IS_FULL_ONBOARDING_ENABLED.getValue(emptyConfiguration)) { if (ConfigurationEntries.IS_SHORT_ONBOARDING_UX.getValue(emptyConfiguration)) {
composeTestRule.onNodeWithText(resContext.getString(R.string.onboarding_short_import_existing_wallet)).also {
it.assertExists()
it.performClick()
}
} else {
composeTestRule.onNodeWithText(resContext.getString(R.string.onboarding_1_header)).also { composeTestRule.onNodeWithText(resContext.getString(R.string.onboarding_1_header)).also {
it.assertExists() it.assertExists()
} }
@ -155,11 +160,11 @@ class ScreenshotTest : UiTestPrerequisites() {
it.assertExists() it.assertExists()
it.performClick() it.performClick()
} }
}
composeTestRule.onNodeWithText(resContext.getString(R.string.onboarding_4_import_existing_wallet)).also { composeTestRule.onNodeWithText(resContext.getString(R.string.onboarding_4_import_existing_wallet)).also {
it.assertExists() it.assertExists()
it.performClick() it.performClick()
}
} }
composeTestRule.onNodeWithText(resContext.getString(R.string.restore_title)).also { composeTestRule.onNodeWithText(resContext.getString(R.string.restore_title)).also {
@ -262,7 +267,16 @@ private val emptyConfiguration = StringConfiguration(persistentMapOf(), null)
private fun onboardingScreenshots(resContext: Context, tag: String, composeTestRule: AndroidComposeTestRule<ActivityScenarioRule<MainActivity>, MainActivity>) { private fun onboardingScreenshots(resContext: Context, tag: String, composeTestRule: AndroidComposeTestRule<ActivityScenarioRule<MainActivity>, MainActivity>) {
composeTestRule.waitUntil(DEFAULT_TIMEOUT_MILLISECONDS) { composeTestRule.activity.walletViewModel.secretState.value is SecretState.None } composeTestRule.waitUntil(DEFAULT_TIMEOUT_MILLISECONDS) { composeTestRule.activity.walletViewModel.secretState.value is SecretState.None }
if (ConfigurationEntries.IS_FULL_ONBOARDING_ENABLED.getValue(emptyConfiguration)) { if (ConfigurationEntries.IS_SHORT_ONBOARDING_UX.getValue(emptyConfiguration)) {
composeTestRule.onNodeWithText(resContext.getString(R.string.onboarding_short_header)).also {
it.assertExists()
ScreenshotTest.takeScreenshot(tag, "Onboarding 1")
}
composeTestRule.onNodeWithText(resContext.getString(R.string.onboarding_short_create_new_wallet)).also {
it.performClick()
}
} else {
composeTestRule.onNodeWithText(resContext.getString(R.string.onboarding_1_header)).also { composeTestRule.onNodeWithText(resContext.getString(R.string.onboarding_1_header)).also {
it.assertExists() it.assertExists()
} }
@ -287,119 +301,131 @@ private fun onboardingScreenshots(resContext: Context, tag: String, composeTestR
composeTestRule.onNodeWithText(resContext.getString(R.string.onboarding_next)).also { composeTestRule.onNodeWithText(resContext.getString(R.string.onboarding_next)).also {
it.performClick() it.performClick()
} }
}
composeTestRule.onNodeWithText(resContext.getString(R.string.onboarding_4_header)).also { composeTestRule.onNodeWithText(resContext.getString(R.string.onboarding_4_header)).also {
it.assertExists() it.assertExists()
ScreenshotTest.takeScreenshot(tag, "Onboarding 4") ScreenshotTest.takeScreenshot(tag, "Onboarding 4")
} }
composeTestRule.onNodeWithText(resContext.getString(R.string.onboarding_4_create_new_wallet)).also { composeTestRule.onNodeWithText(resContext.getString(R.string.onboarding_4_create_new_wallet)).also {
it.performClick() it.performClick()
}
} }
} }
private fun backupScreenshots(resContext: Context, tag: String, composeTestRule: AndroidComposeTestRule<ActivityScenarioRule<MainActivity>, MainActivity>) { private fun backupScreenshots(resContext: Context, tag: String, composeTestRule: AndroidComposeTestRule<ActivityScenarioRule<MainActivity>, MainActivity>) {
composeTestRule.waitUntil(DEFAULT_TIMEOUT_MILLISECONDS) { composeTestRule.activity.walletViewModel.secretState.value is SecretState.NeedsBackup } composeTestRule.waitUntil(DEFAULT_TIMEOUT_MILLISECONDS) { composeTestRule.activity.walletViewModel.secretState.value is SecretState.NeedsBackup }
composeTestRule.onNodeWithText(resContext.getString(R.string.new_wallet_1_header)).also { if (ConfigurationEntries.IS_SHORT_ONBOARDING_UX.getValue(emptyConfiguration)) {
it.assertExists() composeTestRule.onNodeWithText(resContext.getString(R.string.new_wallet_short_header)).also {
} it.assertExists()
ScreenshotTest.takeScreenshot(tag, "Backup 1") }
ScreenshotTest.takeScreenshot(tag, "Backup 1")
composeTestRule.onNodeWithText(resContext.getString(R.string.new_wallet_1_button)).also { composeTestRule.onNodeWithText(resContext.getString(R.string.new_wallet_short_button_finished)).also {
it.performClick() it.assertExists()
} it.performClick()
}
} else {
composeTestRule.onNodeWithText(resContext.getString(R.string.new_wallet_1_header)).also {
it.assertExists()
}
ScreenshotTest.takeScreenshot(tag, "Backup 1")
composeTestRule.onNodeWithText(resContext.getString(R.string.new_wallet_2_header)).also { composeTestRule.onNodeWithText(resContext.getString(R.string.new_wallet_1_button)).also {
it.assertExists() it.performClick()
} }
ScreenshotTest.takeScreenshot(tag, "Backup 2")
composeTestRule.onNodeWithText(resContext.getString(R.string.new_wallet_2_button)).also { composeTestRule.onNodeWithText(resContext.getString(R.string.new_wallet_2_header)).also {
it.performClick() it.assertExists()
} }
ScreenshotTest.takeScreenshot(tag, "Backup 2")
composeTestRule.onNodeWithText(resContext.getString(R.string.new_wallet_3_header)).also { composeTestRule.onNodeWithText(resContext.getString(R.string.new_wallet_2_button)).also {
it.assertExists() it.performClick()
} }
ScreenshotTest.takeScreenshot(tag, "Backup 3")
composeTestRule.onNodeWithText(resContext.getString(R.string.new_wallet_3_button_finished)).also { composeTestRule.onNodeWithText(resContext.getString(R.string.new_wallet_3_header)).also {
it.performClick() it.assertExists()
} }
ScreenshotTest.takeScreenshot(tag, "Backup 3")
composeTestRule.onNodeWithText(resContext.getString(R.string.new_wallet_4_header)).also { composeTestRule.onNodeWithText(resContext.getString(R.string.new_wallet_3_button_finished)).also {
it.assertExists() it.performClick()
} }
ScreenshotTest.takeScreenshot(tag, "Backup 4")
// Fail test first composeTestRule.onNodeWithText(resContext.getString(R.string.new_wallet_4_header)).also {
composeTestRule.onAllNodesWithTag(BackupTag.DROPDOWN_CHIP).also { it.assertExists()
it[0].performScrollTo() }
it[0].performClick() ScreenshotTest.takeScreenshot(tag, "Backup 4")
composeTestRule.onNode(hasTestTag(BackupTag.DROPDOWN_MENU)).onChildren()[0].performClick()
it[1].performScrollTo() // Fail test first
it[1].performClick() composeTestRule.onAllNodesWithTag(BackupTag.DROPDOWN_CHIP).also {
composeTestRule.onNode(hasTestTag(BackupTag.DROPDOWN_MENU)).onChildren()[1].performClick() it[0].performScrollTo()
it[0].performClick()
composeTestRule.onNode(hasTestTag(BackupTag.DROPDOWN_MENU)).onChildren()[0].performClick()
it[2].performScrollTo() it[1].performScrollTo()
it[2].performClick() it[1].performClick()
composeTestRule.onNode(hasTestTag(BackupTag.DROPDOWN_MENU)).onChildren()[2].performClick() composeTestRule.onNode(hasTestTag(BackupTag.DROPDOWN_MENU)).onChildren()[1].performClick()
it[3].performScrollTo() it[2].performScrollTo()
it[3].performClick() it[2].performClick()
composeTestRule.onNode(hasTestTag(BackupTag.DROPDOWN_MENU)).onChildren()[3].performClick() composeTestRule.onNode(hasTestTag(BackupTag.DROPDOWN_MENU)).onChildren()[2].performClick()
}
composeTestRule.onNodeWithText(resContext.getString(R.string.new_wallet_4_header_ouch)).also {
it.assertExists()
ScreenshotTest.takeScreenshot(tag, "Backup Fail")
}
composeTestRule.onNode(hasText(resContext.getString(R.string.new_wallet_4_button_retry))).also { it[3].performScrollTo()
it.performClick() it[3].performClick()
} composeTestRule.onNode(hasTestTag(BackupTag.DROPDOWN_MENU)).onChildren()[3].performClick()
}
composeTestRule.onNodeWithText(resContext.getString(R.string.new_wallet_4_header_ouch)).also {
it.assertExists()
ScreenshotTest.takeScreenshot(tag, "Backup Fail")
}
composeTestRule.onNodeWithText(resContext.getString(R.string.new_wallet_3_header)).also { composeTestRule.onNode(hasText(resContext.getString(R.string.new_wallet_4_button_retry))).also {
it.assertExists() it.performClick()
} }
composeTestRule.onNodeWithText(resContext.getString(R.string.new_wallet_3_button_finished)).also {
it.performClick()
}
composeTestRule.onNodeWithText(resContext.getString(R.string.new_wallet_4_header)).also { composeTestRule.onNodeWithText(resContext.getString(R.string.new_wallet_3_header)).also {
it.assertExists() it.assertExists()
} }
composeTestRule.onNodeWithText(resContext.getString(R.string.new_wallet_3_button_finished)).also {
it.performClick()
}
composeTestRule.onAllNodesWithTag(BackupTag.DROPDOWN_CHIP).also { composeTestRule.onNodeWithText(resContext.getString(R.string.new_wallet_4_header)).also {
it.assertCountEquals(4) it.assertExists()
}
it[0].performScrollTo() composeTestRule.onAllNodesWithTag(BackupTag.DROPDOWN_CHIP).also {
it[0].performClick() it.assertCountEquals(4)
composeTestRule.onNode(hasTestTag(BackupTag.DROPDOWN_MENU)).onChildren()[1].performClick()
it[1].performScrollTo() it[0].performScrollTo()
it[1].performClick() it[0].performClick()
composeTestRule.onNode(hasTestTag(BackupTag.DROPDOWN_MENU)).onChildren()[0].performClick() composeTestRule.onNode(hasTestTag(BackupTag.DROPDOWN_MENU)).onChildren()[1].performClick()
it[2].performScrollTo() it[1].performScrollTo()
it[2].performClick() it[1].performClick()
composeTestRule.onNode(hasTestTag(BackupTag.DROPDOWN_MENU)).onChildren()[3].performClick() composeTestRule.onNode(hasTestTag(BackupTag.DROPDOWN_MENU)).onChildren()[0].performClick()
it[3].performScrollTo() it[2].performScrollTo()
it[3].performClick() it[2].performClick()
composeTestRule.onNode(hasTestTag(BackupTag.DROPDOWN_MENU)).onChildren()[2].performClick() composeTestRule.onNode(hasTestTag(BackupTag.DROPDOWN_MENU)).onChildren()[3].performClick()
}
composeTestRule.onNode(hasText(resContext.getString(R.string.new_wallet_5_body))).also { it[3].performScrollTo()
it.assertExists() it[3].performClick()
ScreenshotTest.takeScreenshot(tag, "Backup 5") composeTestRule.onNode(hasTestTag(BackupTag.DROPDOWN_MENU)).onChildren()[2].performClick()
} }
composeTestRule.onNode(hasText(resContext.getString(R.string.new_wallet_5_button_finished))).also { composeTestRule.onNode(hasText(resContext.getString(R.string.new_wallet_5_body))).also {
it.assertExists() it.assertExists()
it.performClick() ScreenshotTest.takeScreenshot(tag, "Backup 5")
}
composeTestRule.onNode(hasText(resContext.getString(R.string.new_wallet_5_button_finished))).also {
it.assertExists()
it.performClick()
}
} }
} }