[#1048] Seed Recovery Screen
[#1048] Seed Recovery Screen Open Seed Recovery from the Settings screen [#1014] Unify screen name with Figma [#1048] UI + logic + tests Improve click action effect on TopAppBar [#1049] Add Changelog Closes #1049 Update PR template
This commit is contained in:
parent
cf45a0ef34
commit
771dc114da
|
@ -10,7 +10,7 @@
|
|||
- [ ] Add **automated tests** as appropriate
|
||||
- [ ] Update the [**manual tests**](../blob/main/docs/testing/manual_testing)[^2] as appropriate
|
||||
- [ ] Check the **code coverage**[^3] report for the automated tests
|
||||
- [ ] Update **documentation** as appropriate (e.g [README.md](../blob/main/README.md), and [**Architecture.md**](../blob/main/docs/Architecture.md), etc.)
|
||||
- [ ] Update **documentation** as appropriate (e.g [**README.md**](../blob/main/README.md), [**Architecture.md**](../blob/main/docs/Architecture.md), [**CHANGELOG.md**](../blob/main/CHANGELOG.md), etc.)
|
||||
- [ ] **Run the app** and try the changes
|
||||
- [ ] Pull in the latest changes from the **main** branch and **squash** your commits before assigning a reviewer[^4]
|
||||
|
||||
|
@ -23,7 +23,7 @@
|
|||
- [ ] Perform an **ad hoc review**[^5]
|
||||
- [ ] Review the **automated tests**
|
||||
- [ ] Review the **manual tests**
|
||||
- [ ] Review the **documentation**, [**README.md**](../blob/main/README.md), and [**Architecture.md**](../blob/main/docs/Architecture.md) as appropriate
|
||||
- [ ] Review the **documentation**, [**README.md**](../blob/main/README.md), [**Architecture.md**](../blob/main/docs/Architecture.md), etc. as appropriate
|
||||
- [ ] **Run the app** and try the changes[^6]
|
||||
|
||||
[^1]: _Code often looks different when reviewing the diff in a browser, making it easier to spot potential bugs._
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
# Changelog
|
||||
All notable changes to this application will be documented in this file.
|
||||
|
||||
Please be aware that this changelog primarily focuses on user-related modifications, emphasizing changes that can
|
||||
directly impact users rather than highlighting other crucial architectural updates.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this application adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
- The user interface for both the Recovery Seed screen within the New Wallet Screens flow and the one accessible from
|
||||
Settings has been updated.
|
|
@ -1,13 +1,16 @@
|
|||
package co.electriccoin.zcash.ui.design.component
|
||||
|
||||
import androidx.compose.foundation.text.ClickableText
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
|
@ -143,21 +146,25 @@ fun Reference(
|
|||
textAlign: TextAlign = TextAlign.Start,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
ClickableText(
|
||||
text = AnnotatedString(text),
|
||||
style = MaterialTheme.typography.bodyLarge
|
||||
.merge(
|
||||
TextStyle(
|
||||
color = ZcashTheme.colors.reference,
|
||||
textAlign = textAlign,
|
||||
textDecoration = TextDecoration.Underline
|
||||
)
|
||||
),
|
||||
modifier = modifier,
|
||||
onClick = {
|
||||
onClick()
|
||||
}
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.clip(RoundedCornerShape(ZcashTheme.dimens.topAppBarActionRippleCorner))
|
||||
.clickable { onClick() }
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
style = MaterialTheme.typography.bodyLarge
|
||||
.merge(
|
||||
TextStyle(
|
||||
color = ZcashTheme.colors.reference,
|
||||
textAlign = textAlign,
|
||||
textDecoration = TextDecoration.Underline
|
||||
)
|
||||
),
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
package co.electriccoin.zcash.ui.design.component
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
|
@ -22,6 +29,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
@ -209,22 +217,23 @@ fun SmallTopAppBar(
|
|||
},
|
||||
navigationIcon = {
|
||||
backText?.let {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.clip(RoundedCornerShape(ZcashTheme.dimens.topAppBarActionRippleCorner))
|
||||
.clickable { onBack?.run { onBack() } }
|
||||
) {
|
||||
IconButton(
|
||||
onClick = if (onBack != null) {
|
||||
onBack
|
||||
} else {
|
||||
{}
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier.padding(all = ZcashTheme.dimens.spacingDefault),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
Image(
|
||||
imageVector = Icons.Filled.ArrowBack,
|
||||
contentDescription = backContentDescriptionText
|
||||
)
|
||||
Spacer(modifier = Modifier.size(size = ZcashTheme.dimens.spacingSmall))
|
||||
Text(text = backText.uppercase())
|
||||
}
|
||||
Text(text = backText.uppercase())
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -30,6 +30,7 @@ data class Dimens(
|
|||
|
||||
// TopAppBar:
|
||||
val topAppBarZcashLogoHeight: Dp,
|
||||
val topAppBarActionRippleCorner: Dp,
|
||||
|
||||
// In screen custom spacings:
|
||||
val inScreenZcashLogoHeight: Dp,
|
||||
|
@ -52,6 +53,7 @@ private val defaultDimens = Dimens(
|
|||
defaultButtonWidth = 230.dp,
|
||||
defaultButtonHeight = 50.dp,
|
||||
topAppBarZcashLogoHeight = 24.dp,
|
||||
topAppBarActionRippleCorner = 28.dp,
|
||||
inScreenZcashLogoHeight = 100.dp,
|
||||
inScreenZcashLogoWidth = 60.dp,
|
||||
inScreenZcashTextLogoHeight = 30.dp,
|
||||
|
|
|
@ -40,7 +40,7 @@ android {
|
|||
"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_recovery",
|
||||
"src/main/res/ui/send",
|
||||
"src/main/res/ui/settings",
|
||||
"src/main/res/ui/support",
|
||||
|
|
|
@ -10,7 +10,6 @@ import androidx.test.filters.SmallTest
|
|||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.fixture.ConfigInfoFixture
|
||||
import co.electriccoin.zcash.ui.fixture.VersionInfoFixture
|
||||
import co.electriccoin.zcash.ui.screen.about.AboutTag
|
||||
import co.electriccoin.zcash.ui.test.getStringResource
|
||||
import org.junit.Rule
|
||||
import kotlin.test.Test
|
||||
|
|
|
@ -9,6 +9,7 @@ import androidx.compose.ui.test.performScrollTo
|
|||
import androidx.test.filters.MediumTest
|
||||
import co.electriccoin.zcash.test.UiTestPrerequisites
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.test.CommonTag.WALLET_BIRTHDAY
|
||||
import co.electriccoin.zcash.ui.design.component.CommonTag
|
||||
import co.electriccoin.zcash.ui.test.getStringResource
|
||||
import org.junit.Rule
|
||||
|
@ -34,7 +35,7 @@ class NewWalletRecoveryViewTest : UiTestPrerequisites() {
|
|||
assertEquals(0, testSetup.getOnBirthdayCopyCount())
|
||||
assertEquals(0, testSetup.getOnCompleteCount())
|
||||
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.new_wallet_copy)).also {
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.new_wallet_recovery_copy)).also {
|
||||
it.assertExists()
|
||||
}
|
||||
|
||||
|
@ -57,7 +58,7 @@ class NewWalletRecoveryViewTest : UiTestPrerequisites() {
|
|||
it.assertExists()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithTag(NewWalletRecoveryTag.WALLET_BIRTHDAY).also {
|
||||
composeTestRule.onNodeWithTag(WALLET_BIRTHDAY).also {
|
||||
it.performScrollTo()
|
||||
it.assertExists()
|
||||
}
|
||||
|
@ -80,7 +81,7 @@ class NewWalletRecoveryViewTest : UiTestPrerequisites() {
|
|||
|
||||
assertEquals(0, testSetup.getOnSeedCopyCount())
|
||||
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.new_wallet_copy)).also { menuButton ->
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.new_wallet_recovery_copy)).also { menuButton ->
|
||||
menuButton.performClick()
|
||||
}
|
||||
|
||||
|
@ -109,7 +110,7 @@ class NewWalletRecoveryViewTest : UiTestPrerequisites() {
|
|||
|
||||
assertEquals(0, testSetup.getOnBirthdayCopyCount())
|
||||
|
||||
composeTestRule.onNodeWithTag(NewWalletRecoveryTag.WALLET_BIRTHDAY).also {
|
||||
composeTestRule.onNodeWithTag(WALLET_BIRTHDAY).also {
|
||||
it.performScrollTo()
|
||||
it.performClick()
|
||||
}
|
||||
|
|
|
@ -158,7 +158,7 @@ class RestoreViewAndroidTest : UiTestPrerequisites() {
|
|||
private fun copyToClipboard(context: Context, text: String) {
|
||||
val clipboardManager = context.getSystemService(ClipboardManager::class.java)
|
||||
val data = ClipData.newPlainText(
|
||||
context.getString(R.string.new_wallet_seed_clipboard_tag),
|
||||
context.getString(R.string.new_wallet_recovery_seed_clipboard_tag),
|
||||
text
|
||||
)
|
||||
clipboardManager.setPrimaryClip(data)
|
||||
|
|
|
@ -1,87 +0,0 @@
|
|||
package co.electriccoin.zcash.ui.screen.seed.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.compose.ui.test.performScrollTo
|
||||
import androidx.test.filters.MediumTest
|
||||
import cash.z.ecc.sdk.fixture.PersistableWalletFixture
|
||||
import co.electriccoin.zcash.test.UiTestPrerequisites
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.test.getStringResource
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class SeedViewTest : UiTestPrerequisites() {
|
||||
@get:Rule
|
||||
val composeTestRule = createComposeRule()
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun back() = runTest {
|
||||
val testSetup = TestSetup(composeTestRule)
|
||||
|
||||
assertEquals(0, testSetup.getOnBackCount())
|
||||
|
||||
composeTestRule.onNodeWithContentDescription(getStringResource(R.string.seed_back_content_description)).also {
|
||||
it.performClick()
|
||||
}
|
||||
|
||||
assertEquals(1, testSetup.getOnBackCount())
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun copyToClipboard() = runTest {
|
||||
val testSetup = TestSetup(composeTestRule)
|
||||
|
||||
assertEquals(0, testSetup.getCopyToClipboardCount())
|
||||
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.seed_copy)).also {
|
||||
it.performScrollTo()
|
||||
it.performClick()
|
||||
}
|
||||
|
||||
assertEquals(1, testSetup.getCopyToClipboardCount())
|
||||
}
|
||||
|
||||
private class TestSetup(private val composeTestRule: ComposeContentTestRule) {
|
||||
|
||||
private val onBackCount = AtomicInteger(0)
|
||||
private val onCopyToClipboardCount = AtomicInteger(0)
|
||||
|
||||
fun getOnBackCount(): Int {
|
||||
composeTestRule.waitForIdle()
|
||||
return onBackCount.get()
|
||||
}
|
||||
|
||||
fun getCopyToClipboardCount(): Int {
|
||||
composeTestRule.waitForIdle()
|
||||
return onCopyToClipboardCount.get()
|
||||
}
|
||||
|
||||
init {
|
||||
composeTestRule.setContent {
|
||||
ZcashTheme {
|
||||
Seed(
|
||||
PersistableWalletFixture.new(),
|
||||
onBack = {
|
||||
onBackCount.incrementAndGet()
|
||||
},
|
||||
onCopyToClipboard = {
|
||||
onCopyToClipboardCount.incrementAndGet()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package co.electriccoin.zcash.ui.screen.seedrecovery.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 SeedRecoveryBackupTestSetup(
|
||||
private val composeTestRule: ComposeContentTestRule,
|
||||
) {
|
||||
|
||||
private val onSeedCopyCount = AtomicInteger(0)
|
||||
|
||||
private val onBirthdayCopyCount = AtomicInteger(0)
|
||||
|
||||
private val onCompleteCallbackCount = AtomicInteger(0)
|
||||
|
||||
private val onBackCount = AtomicInteger(0)
|
||||
|
||||
fun getOnSeedCopyCount(): Int {
|
||||
composeTestRule.waitForIdle()
|
||||
return onSeedCopyCount.get()
|
||||
}
|
||||
fun getOnBirthdayCopyCount(): Int {
|
||||
composeTestRule.waitForIdle()
|
||||
return onBirthdayCopyCount.get()
|
||||
}
|
||||
|
||||
fun getOnCompleteCount(): Int {
|
||||
composeTestRule.waitForIdle()
|
||||
return onCompleteCallbackCount.get()
|
||||
}
|
||||
|
||||
fun getOnBackCount(): Int {
|
||||
composeTestRule.waitForIdle()
|
||||
return onBackCount.get()
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Suppress("TestFunctionName")
|
||||
fun DefaultContent() {
|
||||
ZcashTheme {
|
||||
SeedRecovery(
|
||||
PersistableWalletFixture.new(),
|
||||
onBack = { onBackCount.incrementAndGet() },
|
||||
onSeedCopy = { onSeedCopyCount.incrementAndGet() },
|
||||
onBirthdayCopy = { onBirthdayCopyCount.incrementAndGet() },
|
||||
onDone = { onCompleteCallbackCount.incrementAndGet() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun setDefaultContent() {
|
||||
composeTestRule.setContent {
|
||||
DefaultContent()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
package co.electriccoin.zcash.ui.screen.seedrecovery.view
|
||||
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.onNodeWithTag
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.performScrollTo
|
||||
import androidx.test.filters.MediumTest
|
||||
import co.electriccoin.zcash.test.UiTestPrerequisites
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.test.CommonTag.WALLET_BIRTHDAY
|
||||
import co.electriccoin.zcash.ui.design.component.CommonTag
|
||||
import co.electriccoin.zcash.ui.test.getStringResource
|
||||
import org.junit.Rule
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class SeedRecoveryViewTest : UiTestPrerequisites() {
|
||||
@get:Rule
|
||||
val composeTestRule = createComposeRule()
|
||||
|
||||
private fun newTestSetup(): SeedRecoveryBackupTestSetup {
|
||||
return SeedRecoveryBackupTestSetup(composeTestRule).apply {
|
||||
setDefaultContent()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun default_ui_state_test() {
|
||||
val testSetup = newTestSetup()
|
||||
|
||||
assertEquals(0, testSetup.getOnSeedCopyCount())
|
||||
assertEquals(0, testSetup.getOnBirthdayCopyCount())
|
||||
assertEquals(0, testSetup.getOnCompleteCount())
|
||||
assertEquals(0, testSetup.getOnBackCount())
|
||||
|
||||
composeTestRule.onNodeWithContentDescription(getStringResource(R.string.seed_recovery_back_content_description))
|
||||
.also {
|
||||
it.assertExists()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.seed_recovery_copy)).also {
|
||||
it.assertExists()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithContentDescription(
|
||||
label = getStringResource(R.string.zcash_logo_content_description)
|
||||
).also {
|
||||
it.assertExists()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.seed_recovery_header)).also {
|
||||
it.assertExists()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.seed_recovery_description)).also {
|
||||
it.assertExists()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithTag(CommonTag.CHIP_LAYOUT).also {
|
||||
it.performScrollTo()
|
||||
it.assertExists()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithTag(WALLET_BIRTHDAY).also {
|
||||
it.performScrollTo()
|
||||
it.assertExists()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.seed_recovery_button_finished).uppercase())
|
||||
.also {
|
||||
it.performScrollTo()
|
||||
it.assertExists()
|
||||
}
|
||||
|
||||
assertEquals(0, testSetup.getOnSeedCopyCount())
|
||||
assertEquals(0, testSetup.getOnBirthdayCopyCount())
|
||||
assertEquals(0, testSetup.getOnCompleteCount())
|
||||
assertEquals(0, testSetup.getOnBackCount())
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun back_test() {
|
||||
val testSetup = newTestSetup()
|
||||
|
||||
assertEquals(0, testSetup.getOnBackCount())
|
||||
|
||||
composeTestRule.onNodeWithContentDescription(getStringResource(R.string.seed_recovery_back_content_description))
|
||||
.also {
|
||||
it.performClick()
|
||||
}
|
||||
|
||||
assertEquals(1, testSetup.getOnBackCount())
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun copy_seed_to_clipboard_from_app_bar_test() {
|
||||
val testSetup = newTestSetup()
|
||||
|
||||
assertEquals(0, testSetup.getOnSeedCopyCount())
|
||||
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.seed_recovery_copy)).also { menuButton ->
|
||||
menuButton.performClick()
|
||||
}
|
||||
|
||||
assertEquals(1, testSetup.getOnSeedCopyCount())
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun copy_seed_to_clipboard_content_test() {
|
||||
val testSetup = newTestSetup()
|
||||
|
||||
assertEquals(0, testSetup.getOnSeedCopyCount())
|
||||
|
||||
composeTestRule.onNodeWithTag(CommonTag.CHIP_LAYOUT).also {
|
||||
it.performScrollTo()
|
||||
it.performClick()
|
||||
}
|
||||
|
||||
assertEquals(1, testSetup.getOnSeedCopyCount())
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun copy_birthday_to_clipboard_content_test() {
|
||||
val testSetup = newTestSetup()
|
||||
|
||||
assertEquals(0, testSetup.getOnBirthdayCopyCount())
|
||||
|
||||
composeTestRule.onNodeWithTag(WALLET_BIRTHDAY).also {
|
||||
it.performScrollTo()
|
||||
it.performClick()
|
||||
}
|
||||
|
||||
assertEquals(1, testSetup.getOnBirthdayCopyCount())
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun click_finish_test() {
|
||||
val testSetup = newTestSetup()
|
||||
|
||||
assertEquals(0, testSetup.getOnSeedCopyCount())
|
||||
assertEquals(0, testSetup.getOnBirthdayCopyCount())
|
||||
assertEquals(0, testSetup.getOnCompleteCount())
|
||||
|
||||
composeTestRule.onNodeWithText(
|
||||
text = getStringResource(R.string.seed_recovery_button_finished),
|
||||
ignoreCase = true
|
||||
).also {
|
||||
it.performScrollTo()
|
||||
it.performClick()
|
||||
}
|
||||
|
||||
assertEquals(0, testSetup.getOnSeedCopyCount())
|
||||
assertEquals(0, testSetup.getOnBirthdayCopyCount())
|
||||
assertEquals(1, testSetup.getOnCompleteCount())
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package co.electriccoin.zcash.ui.screen.seed.view
|
||||
package co.electriccoin.zcash.ui.screen.seedrecovery.view
|
||||
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
||||
|
@ -14,31 +14,38 @@ import org.junit.Rule
|
|||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class SeedViewSecuredScreenTest : UiTestPrerequisites() {
|
||||
class SeedRecoveryViewsSecuredScreenTest : UiTestPrerequisites() {
|
||||
@get:Rule
|
||||
val composeTestRule = createComposeRule()
|
||||
|
||||
private fun newTestSetup() =
|
||||
TestSetup(composeTestRule).apply {
|
||||
setContentView()
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun acquireScreenSecurity() = runTest {
|
||||
val testSetup = TestSetup(composeTestRule)
|
||||
val testSetup = newTestSetup()
|
||||
|
||||
assertEquals(1, testSetup.getSecureScreenCount())
|
||||
}
|
||||
|
||||
private class TestSetup(composeTestRule: ComposeContentTestRule) {
|
||||
private class TestSetup(private val composeTestRule: ComposeContentTestRule) {
|
||||
private val screenSecurity = ScreenSecurity()
|
||||
|
||||
fun getSecureScreenCount() = screenSecurity.referenceCount.value
|
||||
|
||||
init {
|
||||
fun setContentView() {
|
||||
composeTestRule.setContent {
|
||||
CompositionLocalProvider(LocalScreenSecurity provides screenSecurity) {
|
||||
ZcashTheme {
|
||||
Seed(
|
||||
persistableWallet = PersistableWalletFixture.new(),
|
||||
SeedRecovery(
|
||||
PersistableWalletFixture.new(),
|
||||
onBack = {},
|
||||
onCopyToClipboard = {}
|
||||
onSeedCopy = {},
|
||||
onBirthdayCopy = {},
|
||||
onDone = {}
|
||||
)
|
||||
}
|
||||
}
|
|
@ -85,7 +85,7 @@ class SettingsViewTestSetup(
|
|||
onBack = {
|
||||
onBackCount.incrementAndGet()
|
||||
},
|
||||
onBackup = {
|
||||
onSeedRecovery = {
|
||||
onBackupCount.incrementAndGet()
|
||||
},
|
||||
onDocumentation = {
|
||||
|
|
|
@ -18,7 +18,7 @@ import co.electriccoin.zcash.ui.NavigationTargets.HOME
|
|||
import co.electriccoin.zcash.ui.NavigationTargets.RECEIVE
|
||||
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.SEED_RECOVERY
|
||||
import co.electriccoin.zcash.ui.NavigationTargets.SEND
|
||||
import co.electriccoin.zcash.ui.NavigationTargets.SETTINGS
|
||||
import co.electriccoin.zcash.ui.NavigationTargets.SUPPORT
|
||||
|
@ -33,7 +33,7 @@ import co.electriccoin.zcash.ui.screen.home.WrapHome
|
|||
import co.electriccoin.zcash.ui.screen.receive.WrapReceive
|
||||
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.seedrecovery.WrapSeedRecovery
|
||||
import co.electriccoin.zcash.ui.screen.send.WrapSend
|
||||
import co.electriccoin.zcash.ui.screen.send.model.SendArgumentsWrapper
|
||||
import co.electriccoin.zcash.ui.screen.settings.WrapSettings
|
||||
|
@ -55,7 +55,7 @@ internal fun MainActivity.Navigation() {
|
|||
goAbout = { navController.navigateJustOnce(ABOUT) },
|
||||
goHistory = { navController.navigateJustOnce(HISTORY) },
|
||||
goReceive = { navController.navigateJustOnce(RECEIVE) },
|
||||
goSeedPhrase = { navController.navigateJustOnce(SEED) },
|
||||
goSeedPhrase = { navController.navigateJustOnce(SEED_RECOVERY) },
|
||||
goSend = { navController.navigateJustOnce(SEND) },
|
||||
goSettings = { navController.navigateJustOnce(SETTINGS) },
|
||||
goSupport = { navController.navigateJustOnce(SUPPORT) },
|
||||
|
@ -82,13 +82,19 @@ internal fun MainActivity.Navigation() {
|
|||
},
|
||||
goExportPrivateData = {
|
||||
navController.navigateJustOnce(EXPORT_PRIVATE_DATA)
|
||||
},
|
||||
goSeedRecovery = {
|
||||
navController.navigateJustOnce(SEED_RECOVERY)
|
||||
}
|
||||
)
|
||||
}
|
||||
composable(SEED) {
|
||||
WrapSeed(
|
||||
composable(SEED_RECOVERY) {
|
||||
WrapSeedRecovery(
|
||||
goBack = {
|
||||
navController.popBackStackJustOnce(SEED)
|
||||
navController.popBackStackJustOnce(SEED_RECOVERY)
|
||||
},
|
||||
onDone = {
|
||||
navController.popBackStackJustOnce(SEED_RECOVERY)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -193,7 +199,7 @@ object NavigationTargets {
|
|||
const val RECEIVE = "receive"
|
||||
const val REQUEST = "request"
|
||||
const val SCAN = "scan"
|
||||
const val SEED = "seed"
|
||||
const val SEED_RECOVERY = "seed_recovery"
|
||||
const val SEND = "send"
|
||||
const val SETTINGS = "settings"
|
||||
const val SUPPORT = "support"
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
package co.electriccoin.zcash.ui.common.test
|
||||
|
||||
/**
|
||||
* These are only used for automated testing.
|
||||
*/
|
||||
object CommonTag {
|
||||
const val WALLET_BIRTHDAY = "wallet_birthday"
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package co.electriccoin.zcash.ui.screen.about
|
||||
package co.electriccoin.zcash.ui.screen.about.view
|
||||
|
||||
/**
|
||||
* These are only used for automated testing.
|
|
@ -38,7 +38,6 @@ import co.electriccoin.zcash.ui.design.component.SmallTopAppBar
|
|||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.fixture.ConfigInfoFixture
|
||||
import co.electriccoin.zcash.ui.fixture.VersionInfoFixture
|
||||
import co.electriccoin.zcash.ui.screen.about.AboutTag
|
||||
import co.electriccoin.zcash.ui.screen.support.model.ConfigInfo
|
||||
|
||||
@Preview("About")
|
||||
|
|
|
@ -16,45 +16,28 @@ fun MainActivity.WrapNewWalletRecovery(
|
|||
WrapNewWalletRecovery(this, persistableWallet, onBackupComplete)
|
||||
}
|
||||
|
||||
// This layer of indirection allows for activity re-creation tests
|
||||
@Composable
|
||||
private fun WrapNewWalletRecovery(
|
||||
activity: ComponentActivity,
|
||||
persistableWallet: PersistableWallet,
|
||||
onBackupComplete: () -> Unit
|
||||
) {
|
||||
WrapNewWalletRecovery(
|
||||
NewWalletRecovery(
|
||||
persistableWallet,
|
||||
onSeedCopyToClipboard = {
|
||||
onSeedCopy = {
|
||||
ClipboardManagerUtil.copyToClipboard(
|
||||
activity.applicationContext,
|
||||
activity.getString(R.string.new_wallet_seed_clipboard_tag),
|
||||
activity.getString(R.string.new_wallet_recovery_seed_clipboard_tag),
|
||||
persistableWallet.seedPhrase.joinToString()
|
||||
)
|
||||
},
|
||||
onBirthdayCopyToClipboard = {
|
||||
onBirthdayCopy = {
|
||||
ClipboardManagerUtil.copyToClipboard(
|
||||
activity.applicationContext,
|
||||
activity.getString(R.string.new_wallet_birthday_clipboard_tag),
|
||||
activity.getString(R.string.new_wallet_recovery_birthday_clipboard_tag),
|
||||
persistableWallet.birthday?.value.toString()
|
||||
)
|
||||
},
|
||||
onNewWalletComplete = onBackupComplete
|
||||
)
|
||||
}
|
||||
|
||||
// This extra layer of indirection allows unit tests to validate the screen state retention
|
||||
@Composable
|
||||
private fun WrapNewWalletRecovery(
|
||||
persistableWallet: PersistableWallet,
|
||||
onSeedCopyToClipboard: () -> Unit,
|
||||
onBirthdayCopyToClipboard: () -> Unit,
|
||||
onNewWalletComplete: () -> Unit
|
||||
) {
|
||||
NewWalletRecovery(
|
||||
persistableWallet,
|
||||
onSeedCopy = onSeedCopyToClipboard,
|
||||
onBirthdayCopy = onBirthdayCopyToClipboard,
|
||||
onComplete = onNewWalletComplete,
|
||||
onComplete = onBackupComplete
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
@file:Suppress("TooManyFunctions")
|
||||
|
||||
package co.electriccoin.zcash.ui.screen.newwalletrecovery.view
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
|
@ -32,6 +30,7 @@ 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.common.shouldSecureScreen
|
||||
import co.electriccoin.zcash.ui.common.test.CommonTag.WALLET_BIRTHDAY
|
||||
import co.electriccoin.zcash.ui.design.MINIMAL_WEIGHT
|
||||
import co.electriccoin.zcash.ui.design.component.BodySmall
|
||||
import co.electriccoin.zcash.ui.design.component.ChipGrid
|
||||
|
@ -41,7 +40,6 @@ import co.electriccoin.zcash.ui.design.component.Reference
|
|||
import co.electriccoin.zcash.ui.design.component.SmallTopAppBar
|
||||
import co.electriccoin.zcash.ui.design.component.TopScreenLogoTitle
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.screen.newwalletrecovery.view.NewWalletRecoveryTag.WALLET_BIRTHDAY
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
@Preview(name = "NewWalletRecovery", device = Devices.PIXEL_4)
|
||||
|
@ -115,7 +113,7 @@ private fun NewWalletRecoveryCopyToBufferMenuItem(
|
|||
onCopyToClipboard: () -> Unit,
|
||||
) {
|
||||
Reference(
|
||||
text = stringResource(id = R.string.new_wallet_copy),
|
||||
text = stringResource(id = R.string.new_wallet_recovery_copy),
|
||||
onClick = onCopyToClipboard,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = modifier.then(
|
||||
|
@ -224,7 +222,6 @@ private fun NewWalletRecoverySeedPhrase(
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
private fun NewWalletRecoveryBottomNav(
|
||||
onComplete: () -> Unit,
|
||||
|
|
|
@ -1,128 +0,0 @@
|
|||
package co.electriccoin.zcash.ui.screen.seed.view
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.height
|
||||
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.ArrowBack
|
||||
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.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
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.common.shouldSecureScreen
|
||||
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.TertiaryButton
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
@Preview("Seed")
|
||||
@Composable
|
||||
private fun PreviewSeed() {
|
||||
ZcashTheme(forceDarkMode = false) {
|
||||
GradientSurface {
|
||||
Seed(
|
||||
persistableWallet = PersistableWalletFixture.new(),
|
||||
onBack = {},
|
||||
onCopyToClipboard = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Note we have some things to determine regarding locking of the secrets for persistableWallet
|
||||
* (e.g. seed phrase and spending keys) which should require additional authorization to view.
|
||||
*/
|
||||
@Composable
|
||||
fun Seed(
|
||||
persistableWallet: PersistableWallet,
|
||||
onBack: () -> Unit,
|
||||
onCopyToClipboard: () -> Unit
|
||||
) {
|
||||
if (shouldSecureScreen) {
|
||||
SecureScreen()
|
||||
}
|
||||
Scaffold(topBar = {
|
||||
SeedTopAppBar(onBack = onBack)
|
||||
}) { paddingValues ->
|
||||
SeedMainContent(
|
||||
persistableWallet = persistableWallet,
|
||||
onCopyToClipboard = onCopyToClipboard,
|
||||
modifier = Modifier.padding(
|
||||
top = paddingValues.calculateTopPadding() + ZcashTheme.dimens.spacingDefault,
|
||||
bottom = paddingValues.calculateBottomPadding() + ZcashTheme.dimens.spacingDefault,
|
||||
start = ZcashTheme.dimens.spacingDefault,
|
||||
end = ZcashTheme.dimens.spacingDefault
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
private fun SeedTopAppBar(onBack: () -> Unit) {
|
||||
TopAppBar(
|
||||
title = { Text(text = stringResource(id = R.string.seed_title)) },
|
||||
navigationIcon = {
|
||||
IconButton(
|
||||
onClick = onBack
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.ArrowBack,
|
||||
contentDescription = stringResource(R.string.seed_back_content_description)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SeedMainContent(
|
||||
persistableWallet: PersistableWallet,
|
||||
onCopyToClipboard: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxHeight()
|
||||
.verticalScroll(
|
||||
rememberScrollState()
|
||||
)
|
||||
.then(modifier)
|
||||
) {
|
||||
Body(stringResource(R.string.seed_body))
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
||||
|
||||
ChipGrid(
|
||||
wordList = persistableWallet.seedPhrase.split.toPersistentList(),
|
||||
onGridClick = {}
|
||||
)
|
||||
|
||||
TertiaryButton(
|
||||
onClick = onCopyToClipboard,
|
||||
text = stringResource(R.string.seed_copy),
|
||||
outerPaddingValues = PaddingValues(
|
||||
horizontal = ZcashTheme.dimens.spacingNone,
|
||||
vertical = ZcashTheme.dimens.spacingSmall
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,6 +1,4 @@
|
|||
@file:Suppress("ktlint:filename")
|
||||
|
||||
package co.electriccoin.zcash.ui.screen.seed
|
||||
package co.electriccoin.zcash.ui.screen.seedrecovery
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.viewModels
|
||||
|
@ -11,19 +9,21 @@ import co.electriccoin.zcash.ui.MainActivity
|
|||
import co.electriccoin.zcash.ui.R
|
||||
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
|
||||
import co.electriccoin.zcash.ui.screen.seedrecovery.view.SeedRecovery
|
||||
|
||||
@Composable
|
||||
internal fun MainActivity.WrapSeed(
|
||||
goBack: () -> Unit
|
||||
internal fun MainActivity.WrapSeedRecovery(
|
||||
goBack: () -> Unit,
|
||||
onDone: () -> Unit
|
||||
) {
|
||||
WrapSeed(this, goBack)
|
||||
WrapSeedRecovery(this, goBack, onDone)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WrapSeed(
|
||||
private fun WrapSeedRecovery(
|
||||
activity: ComponentActivity,
|
||||
goBack: () -> Unit
|
||||
goBack: () -> Unit,
|
||||
onDone: () -> Unit
|
||||
) {
|
||||
val walletViewModel by activity.viewModels<WalletViewModel>()
|
||||
|
||||
|
@ -40,16 +40,24 @@ private fun WrapSeed(
|
|||
if (null == synchronizer || null == persistableWallet) {
|
||||
// Display loading indicator
|
||||
} else {
|
||||
Seed(
|
||||
persistableWallet = persistableWallet,
|
||||
SeedRecovery(
|
||||
persistableWallet,
|
||||
onBack = goBack,
|
||||
onCopyToClipboard = {
|
||||
onSeedCopy = {
|
||||
ClipboardManagerUtil.copyToClipboard(
|
||||
activity.applicationContext,
|
||||
activity.getString(R.string.new_wallet_seed_clipboard_tag),
|
||||
activity.getString(R.string.seed_recovery_seed_clipboard_tag),
|
||||
persistableWallet.seedPhrase.joinToString()
|
||||
)
|
||||
},
|
||||
onBirthdayCopy = {
|
||||
ClipboardManagerUtil.copyToClipboard(
|
||||
activity.applicationContext,
|
||||
activity.getString(R.string.seed_recovery_birthday_clipboard_tag),
|
||||
persistableWallet.birthday?.value.toString()
|
||||
)
|
||||
},
|
||||
onDone = onDone
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,244 @@
|
|||
package co.electriccoin.zcash.ui.screen.seedrecovery.view
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.basicMarquee
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
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.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
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.common.shouldSecureScreen
|
||||
import co.electriccoin.zcash.ui.common.test.CommonTag.WALLET_BIRTHDAY
|
||||
import co.electriccoin.zcash.ui.design.MINIMAL_WEIGHT
|
||||
import co.electriccoin.zcash.ui.design.component.BodySmall
|
||||
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.component.Reference
|
||||
import co.electriccoin.zcash.ui.design.component.SmallTopAppBar
|
||||
import co.electriccoin.zcash.ui.design.component.TopScreenLogoTitle
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
@Preview(name = "SeedRecovery", device = Devices.PIXEL_4)
|
||||
@Composable
|
||||
private fun ComposablePreview() {
|
||||
ZcashTheme(forceDarkMode = false) {
|
||||
GradientSurface {
|
||||
SeedRecovery(
|
||||
PersistableWalletFixture.new(),
|
||||
onBack = {},
|
||||
onBirthdayCopy = {},
|
||||
onDone = {},
|
||||
onSeedCopy = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO [#998]: Check and enhance screen dark mode
|
||||
// TODO [#998]: https://github.com/Electric-Coin-Company/zashi-android/issues/998
|
||||
|
||||
/**
|
||||
* @param onDone Callback when the user has confirmed viewing the seed phrase.
|
||||
*/
|
||||
@Composable
|
||||
fun SeedRecovery(
|
||||
wallet: PersistableWallet,
|
||||
onBack: () -> Unit,
|
||||
onBirthdayCopy: () -> Unit,
|
||||
onDone: () -> Unit,
|
||||
onSeedCopy: () -> Unit,
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
SeedRecoveryTopAppBar(
|
||||
onBack = onBack,
|
||||
onSeedCopy = onSeedCopy,
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
SeedRecoveryMainContent(
|
||||
wallet = wallet,
|
||||
onDone = onDone,
|
||||
onSeedCopy = onSeedCopy,
|
||||
onBirthdayCopy = onBirthdayCopy,
|
||||
// Horizontal paddings will be part of each UI element to minimize a possible truncation on very
|
||||
// small screens
|
||||
modifier = Modifier.padding(
|
||||
top = paddingValues.calculateTopPadding(),
|
||||
bottom = paddingValues.calculateBottomPadding()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SeedRecoveryTopAppBar(
|
||||
onBack: () -> Unit,
|
||||
onSeedCopy: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
SmallTopAppBar(
|
||||
modifier = modifier,
|
||||
backText = stringResource(id = R.string.seed_recovery_back).uppercase(),
|
||||
backContentDescriptionText = stringResource(R.string.seed_recovery_back_content_description),
|
||||
onBack = onBack,
|
||||
regularActions = {
|
||||
SeedRecoveryCopyToBufferMenuItem(
|
||||
onCopyToClipboard = onSeedCopy
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SeedRecoveryCopyToBufferMenuItem(
|
||||
modifier: Modifier = Modifier,
|
||||
onCopyToClipboard: () -> Unit,
|
||||
) {
|
||||
Reference(
|
||||
text = stringResource(id = R.string.seed_recovery_copy),
|
||||
onClick = onCopyToClipboard,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = modifier.then(
|
||||
Modifier.padding(all = ZcashTheme.dimens.spacingDefault)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SeedRecoveryMainContent(
|
||||
wallet: PersistableWallet,
|
||||
onSeedCopy: () -> Unit,
|
||||
onBirthdayCopy: () -> Unit,
|
||||
onDone: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.then(modifier),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
TopScreenLogoTitle(
|
||||
title = stringResource(R.string.seed_recovery_header),
|
||||
logoContentDescription = stringResource(R.string.zcash_logo_content_description),
|
||||
modifier = Modifier.padding(horizontal = ZcashTheme.dimens.spacingHuge)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingLarge))
|
||||
|
||||
BodySmall(
|
||||
text = stringResource(R.string.seed_recovery_description),
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(horizontal = ZcashTheme.dimens.spacingHuge)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
||||
|
||||
SeedRecoverySeedPhrase(
|
||||
persistableWallet = wallet,
|
||||
onSeedCopy = onSeedCopy,
|
||||
onBirthdayCopy = onBirthdayCopy,
|
||||
)
|
||||
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.weight(MINIMAL_WEIGHT)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingLarge))
|
||||
|
||||
SeedRecoveryBottomNav(
|
||||
onDone = onDone,
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
bottom = ZcashTheme.dimens.spacingHuge
|
||||
)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun SeedRecoverySeedPhrase(
|
||||
persistableWallet: PersistableWallet,
|
||||
onSeedCopy: () -> Unit,
|
||||
onBirthdayCopy: () -> Unit,
|
||||
) {
|
||||
if (shouldSecureScreen) {
|
||||
SecureScreen()
|
||||
}
|
||||
|
||||
Column {
|
||||
ChipGrid(
|
||||
wordList = persistableWallet.seedPhrase.split.toPersistentList(),
|
||||
onGridClick = onSeedCopy
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
||||
|
||||
persistableWallet.birthday?.let {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
BodySmall(
|
||||
text = stringResource(R.string.seed_recovery_birthday_height, it.value),
|
||||
modifier = Modifier
|
||||
.testTag(WALLET_BIRTHDAY)
|
||||
.padding(horizontal = ZcashTheme.dimens.spacingDefault)
|
||||
.basicMarquee()
|
||||
// Apply click callback to the text only as the wrapping layout can be much wider
|
||||
.clickable(
|
||||
interactionSource = interactionSource,
|
||||
indication = null, // Disable ripple
|
||||
onClick = onBirthdayCopy
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
private fun SeedRecoveryBottomNav(
|
||||
onDone: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
PrimaryButton(onClick = onDone, text = stringResource(R.string.seed_recovery_button_finished))
|
||||
}
|
||||
}
|
|
@ -20,12 +20,14 @@ internal fun MainActivity.WrapSettings(
|
|||
goAbout: () -> Unit,
|
||||
goBack: () -> Unit,
|
||||
goExportPrivateData: () -> Unit,
|
||||
goSeedRecovery: () -> Unit,
|
||||
) {
|
||||
WrapSettings(
|
||||
activity = this,
|
||||
goAbout = goAbout,
|
||||
goBack = goBack,
|
||||
goExportPrivateData = goExportPrivateData
|
||||
goExportPrivateData = goExportPrivateData,
|
||||
goSeedRecovery = goSeedRecovery
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -35,6 +37,7 @@ private fun WrapSettings(
|
|||
goBack: () -> Unit,
|
||||
goAbout: () -> Unit,
|
||||
goExportPrivateData: () -> Unit,
|
||||
goSeedRecovery: () -> Unit,
|
||||
) {
|
||||
val walletViewModel by activity.viewModels<WalletViewModel>()
|
||||
val settingsViewModel by activity.viewModels<SettingsViewModel>()
|
||||
|
@ -63,7 +66,7 @@ private fun WrapSettings(
|
|||
isRescanEnabled = ConfigurationEntries.IS_RESCAN_ENABLED.getValue(RemoteConfig.current),
|
||||
),
|
||||
onBack = goBack,
|
||||
onBackup = {},
|
||||
onSeedRecovery = goSeedRecovery,
|
||||
onDocumentation = {},
|
||||
onPrivacyPolicy = {},
|
||||
onFeedback = {},
|
||||
|
|
|
@ -53,7 +53,7 @@ private fun PreviewSettings() {
|
|||
isRescanEnabled = false
|
||||
),
|
||||
onBack = {},
|
||||
onBackup = {},
|
||||
onSeedRecovery = {},
|
||||
onDocumentation = {},
|
||||
onPrivacyPolicy = {},
|
||||
onFeedback = {},
|
||||
|
@ -73,7 +73,7 @@ private fun PreviewSettings() {
|
|||
fun Settings(
|
||||
troubleshootingParameters: TroubleshootingParameters,
|
||||
onBack: () -> Unit,
|
||||
onBackup: () -> Unit,
|
||||
onSeedRecovery: () -> Unit,
|
||||
onDocumentation: () -> Unit,
|
||||
onPrivacyPolicy: () -> Unit,
|
||||
onFeedback: () -> Unit,
|
||||
|
@ -105,7 +105,7 @@ fun Settings(
|
|||
start = dimens.spacingHuge,
|
||||
end = dimens.spacingHuge
|
||||
),
|
||||
onBackup = onBackup,
|
||||
onSeedRecovery = onSeedRecovery,
|
||||
onDocumentation = onDocumentation,
|
||||
onPrivacyPolicy = onPrivacyPolicy,
|
||||
onFeedback = onFeedback,
|
||||
|
@ -229,7 +229,7 @@ private fun TroubleshootingMenu(
|
|||
@Composable
|
||||
@Suppress("LongParameterList", "LongMethod")
|
||||
private fun SettingsMainContent(
|
||||
onBackup: () -> Unit,
|
||||
onSeedRecovery: () -> Unit,
|
||||
onDocumentation: () -> Unit,
|
||||
onPrivacyPolicy: () -> Unit,
|
||||
onFeedback: () -> Unit,
|
||||
|
@ -245,7 +245,7 @@ private fun SettingsMainContent(
|
|||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
PrimaryButton(
|
||||
onClick = onBackup,
|
||||
onClick = onSeedRecovery,
|
||||
text = stringResource(R.string.settings_backup_wallet),
|
||||
outerPaddingValues = PaddingValues(
|
||||
horizontal = dimens.spacingNone,
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<string name="new_wallet_recovery_birthday_height" formatted="true">Wallet birthday height: <xliff:g example="419200"
|
||||
id="birthday_height">%1$d</xliff:g></string>
|
||||
<string name="new_wallet_recovery_button_finished">I got it!</string>
|
||||
<string name="new_wallet_copy">Tap to Copy</string>
|
||||
<string name="new_wallet_seed_clipboard_tag">Zcash Seed Phrase</string>
|
||||
<string name="new_wallet_birthday_clipboard_tag">Zcash Wallet Birthday</string>
|
||||
<string name="new_wallet_recovery_copy">Tap to Copy</string>
|
||||
<string name="new_wallet_recovery_seed_clipboard_tag">Zcash Seed Phrase</string>
|
||||
<string name="new_wallet_recovery_birthday_clipboard_tag">Zcash Wallet Birthday</string>
|
||||
</resources>
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
<resources>
|
||||
<string name="seed_title">My secret phrase</string>
|
||||
<string name="seed_back_content_description">Back</string>
|
||||
|
||||
<string name="seed_header">Your Secret Recovery Phrase</string>
|
||||
<string name="seed_body">These words represent your funds and the security used to protect them.</string>
|
||||
<string name="seed_copy">Copy to buffer</string>
|
||||
|
||||
</resources>
|
|
@ -0,0 +1,14 @@
|
|||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="seed_recovery_back">Back</string>
|
||||
<string name="seed_recovery_back_content_description">Back</string>
|
||||
<string name="seed_recovery_header">Your secret recovery phrase</string>
|
||||
<string name="seed_recovery_description">The following 24 words are the keys to your funds and are the only way to
|
||||
recover your funds if you get locked out or get a new device. Protect your ZEC by storing this phrase in a
|
||||
place you trust and never share it with anyone!</string>
|
||||
<string name="seed_recovery_birthday_height" formatted="true">Wallet birthday height: <xliff:g example="419200"
|
||||
id="birthday_height">%1$d</xliff:g></string>
|
||||
<string name="seed_recovery_button_finished">I got it!</string>
|
||||
<string name="seed_recovery_copy">Tap to Copy</string>
|
||||
<string name="seed_recovery_seed_clipboard_tag">Zcash Seed Phrase</string>
|
||||
<string name="seed_recovery_birthday_clipboard_tag">Zcash Wallet Birthday</string>
|
||||
</resources>
|
|
@ -296,7 +296,7 @@ class ScreenshotTest : UiTestPrerequisites() {
|
|||
|
||||
// These are the hamburger menu items
|
||||
// We could manually click on each one, which is a better integration test but a worse screenshot test
|
||||
navigateTo(NavigationTargets.SEED)
|
||||
navigateTo(NavigationTargets.SEED_RECOVERY)
|
||||
seedScreenshots(resContext, tag, composeTestRule)
|
||||
|
||||
navigateTo(NavigationTargets.SETTINGS)
|
||||
|
@ -588,7 +588,7 @@ private fun aboutScreenshots(resContext: Context, tag: String, composeTestRule:
|
|||
}
|
||||
|
||||
private fun seedScreenshots(resContext: Context, tag: String, composeTestRule: ComposeTestRule) {
|
||||
composeTestRule.onNode(hasText(resContext.getString(R.string.seed_title))).also {
|
||||
composeTestRule.onNode(hasText(resContext.getString(R.string.seed_recovery_header))).also {
|
||||
it.assertExists()
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue