[#1064] Restore Seed-Birthday Screen
* [#1064] Restore Seed-Birthday Screen - Reworked Restore Seed-Birthday height screen - Contains UI, screen logic and tests - Closes #1064 * Update changelog * [#859] Fix failing Restore screenshot test Closes #859
This commit is contained in:
parent
0054588ec0
commit
3d33bb8ca5
|
@ -11,6 +11,7 @@ and this application adheres to [Semantic Versioning](https://semver.org/spec/v2
|
|||
|
||||
### Changed
|
||||
- Updated user interface of these screens:
|
||||
- New Wallet Recovery Seed screen accessible from onboarding
|
||||
- Seed Recovery screen accessible from Settings
|
||||
- Restore existing wallet accessible from onboarding
|
||||
- New Wallet Recovery Seed screen (accessible from onboarding)
|
||||
- Seed Recovery screen (accessible from Settings)
|
||||
- Restore Seed screen for an existing wallet (accessible from onboarding)
|
||||
- Restore Seed Birthday Height screen for an existing wallet (accessible from onboarding)
|
|
@ -11,8 +11,10 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
|
@ -20,6 +22,7 @@ fun FormTextField(
|
|||
value: String,
|
||||
onValueChange: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
textStyle: TextStyle = ZcashTheme.extendedTypography.textFieldValue,
|
||||
label: @Composable (() -> Unit)? = null,
|
||||
leadingIcon: @Composable (() -> Unit)? = null,
|
||||
trailingIcon: @Composable (() -> Unit)? = null,
|
||||
|
@ -38,13 +41,16 @@ fun FormTextField(
|
|||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
label = label,
|
||||
textStyle = textStyle,
|
||||
keyboardOptions = keyboardOptions,
|
||||
colors = colors,
|
||||
modifier = if (withBorder) {
|
||||
modifier.border(width = 1.dp, color = MaterialTheme.colorScheme.primary)
|
||||
} else {
|
||||
modifier
|
||||
},
|
||||
modifier = modifier.then(
|
||||
if (withBorder) {
|
||||
modifier.border(width = 1.dp, color = MaterialTheme.colorScheme.primary)
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
),
|
||||
leadingIcon = leadingIcon,
|
||||
trailingIcon = trailingIcon,
|
||||
keyboardActions = keyboardActions,
|
||||
|
|
|
@ -124,6 +124,7 @@ data class ExtendedTypography(
|
|||
val securityWarningText: TextStyle,
|
||||
val textFieldHint: TextStyle,
|
||||
val textFieldValue: TextStyle,
|
||||
val textFieldBirthday: TextStyle,
|
||||
)
|
||||
|
||||
@Suppress("CompositionLocalAllowlist")
|
||||
|
@ -171,5 +172,6 @@ val LocalExtendedTypography = staticCompositionLocalOf {
|
|||
textFieldValue = PrimaryTypography.bodyLarge.copy(
|
||||
fontSize = 17.sp,
|
||||
),
|
||||
textFieldBirthday = SecondaryTypography.headlineMedium.copy(),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -185,12 +185,16 @@ class RestoreViewTest : UiTestPrerequisites() {
|
|||
initialWordsList = SeedPhraseFixture.new().split
|
||||
)
|
||||
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.restore_birthday_button_skip)).also {
|
||||
composeTestRule.onNodeWithText(
|
||||
text = getStringResource(R.string.restore_birthday_button_restore),
|
||||
ignoreCase = true
|
||||
).also {
|
||||
it.assertIsEnabled()
|
||||
it.performClick()
|
||||
}
|
||||
|
||||
assertEquals(testSetup.getRestoreHeight(), null)
|
||||
assertEquals(testSetup.getStage(), RestoreStage.Complete)
|
||||
assertEquals(1, testSetup.getOnFinishedCount())
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -201,14 +205,7 @@ class RestoreViewTest : UiTestPrerequisites() {
|
|||
initialWordsList = SeedPhraseFixture.new().split
|
||||
)
|
||||
|
||||
composeTestRule.onNodeWithText(
|
||||
text = getStringResource(R.string.restore_birthday_button_restore),
|
||||
ignoreCase = true
|
||||
).also {
|
||||
it.assertIsNotEnabled()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.restore_birthday_hint)).also {
|
||||
composeTestRule.onNodeWithTag(RestoreTag.BIRTHDAY_TEXT_FIELD).also {
|
||||
it.performTextInput(ZcashNetwork.Mainnet.saplingActivationHeight.value.toString())
|
||||
}
|
||||
|
||||
|
@ -221,34 +218,7 @@ class RestoreViewTest : UiTestPrerequisites() {
|
|||
}
|
||||
|
||||
assertEquals(testSetup.getRestoreHeight(), ZcashNetwork.Mainnet.saplingActivationHeight)
|
||||
assertEquals(testSetup.getStage(), RestoreStage.Complete)
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun height_set_valid_but_skip() {
|
||||
val testSetup = newTestSetup(
|
||||
initialStage = RestoreStage.Birthday,
|
||||
initialWordsList = SeedPhraseFixture.new().split
|
||||
)
|
||||
|
||||
composeTestRule.onNodeWithText(
|
||||
text = getStringResource(R.string.restore_birthday_button_restore),
|
||||
ignoreCase = true
|
||||
).also {
|
||||
it.assertIsNotEnabled()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.restore_birthday_hint)).also {
|
||||
it.performTextInput(ZcashNetwork.Mainnet.saplingActivationHeight.value.toString())
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.restore_birthday_button_skip)).also {
|
||||
it.performClick()
|
||||
}
|
||||
|
||||
assertNull(testSetup.getRestoreHeight())
|
||||
assertEquals(testSetup.getStage(), RestoreStage.Complete)
|
||||
assertEquals(1, testSetup.getOnFinishedCount())
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -263,10 +233,10 @@ class RestoreViewTest : UiTestPrerequisites() {
|
|||
text = getStringResource(R.string.restore_birthday_button_restore),
|
||||
ignoreCase = true
|
||||
).also {
|
||||
it.assertIsNotEnabled()
|
||||
it.assertIsEnabled()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.restore_birthday_hint)).also {
|
||||
composeTestRule.onNodeWithTag(RestoreTag.BIRTHDAY_TEXT_FIELD).also {
|
||||
it.performTextInput((ZcashNetwork.Mainnet.saplingActivationHeight.value - 1L).toString())
|
||||
}
|
||||
|
||||
|
@ -275,14 +245,11 @@ class RestoreViewTest : UiTestPrerequisites() {
|
|||
ignoreCase = true
|
||||
).also {
|
||||
it.assertIsNotEnabled()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.restore_birthday_button_skip)).also {
|
||||
it.performClick()
|
||||
}
|
||||
|
||||
assertNull(testSetup.getRestoreHeight())
|
||||
assertEquals(testSetup.getStage(), RestoreStage.Complete)
|
||||
assertEquals(0, testSetup.getOnFinishedCount())
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -293,14 +260,7 @@ class RestoreViewTest : UiTestPrerequisites() {
|
|||
initialWordsList = SeedPhraseFixture.new().split
|
||||
)
|
||||
|
||||
composeTestRule.onNodeWithText(
|
||||
text = getStringResource(R.string.restore_birthday_button_restore),
|
||||
ignoreCase = true
|
||||
).also {
|
||||
it.assertIsNotEnabled()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.restore_birthday_hint)).also {
|
||||
composeTestRule.onNodeWithTag(RestoreTag.BIRTHDAY_TEXT_FIELD).also {
|
||||
it.performTextInput("1.2")
|
||||
}
|
||||
|
||||
|
@ -309,27 +269,27 @@ class RestoreViewTest : UiTestPrerequisites() {
|
|||
ignoreCase = true
|
||||
).also {
|
||||
it.assertIsNotEnabled()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.restore_birthday_button_skip)).also {
|
||||
it.performClick()
|
||||
}
|
||||
|
||||
assertNull(testSetup.getRestoreHeight())
|
||||
assertEquals(testSetup.getStage(), RestoreStage.Complete)
|
||||
assertEquals(0, testSetup.getOnFinishedCount())
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun complete_click_take_to_wallet() {
|
||||
val testSetup = newTestSetup(
|
||||
initialStage = RestoreStage.Complete,
|
||||
initialStage = RestoreStage.Birthday,
|
||||
initialWordsList = SeedPhraseFixture.new().split
|
||||
)
|
||||
|
||||
assertEquals(0, testSetup.getOnFinishedCount())
|
||||
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.restore_button_see_wallet), ignoreCase = true).also {
|
||||
composeTestRule.onNodeWithText(
|
||||
text = getStringResource(R.string.restore_birthday_button_restore),
|
||||
ignoreCase = true
|
||||
).also {
|
||||
it.performClick()
|
||||
}
|
||||
|
||||
|
@ -375,26 +335,6 @@ class RestoreViewTest : UiTestPrerequisites() {
|
|||
assertEquals(0, testSetup.getOnBackCount())
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun back_from_complete() {
|
||||
val testSetup = newTestSetup(
|
||||
initialStage = RestoreStage.Complete,
|
||||
initialWordsList = SeedPhraseFixture.new().split
|
||||
)
|
||||
|
||||
assertEquals(0, testSetup.getOnBackCount())
|
||||
|
||||
composeTestRule.onNodeWithContentDescription(
|
||||
getStringResource(R.string.restore_back_content_description)
|
||||
).also {
|
||||
it.performClick()
|
||||
}
|
||||
|
||||
assertEquals(testSetup.getStage(), RestoreStage.Birthday)
|
||||
assertEquals(0, testSetup.getOnBackCount())
|
||||
}
|
||||
|
||||
private fun newTestSetup(
|
||||
initialStage: RestoreStage = RestoreStage.Seed,
|
||||
initialWordsList: List<String> = emptyList()
|
||||
|
|
|
@ -4,8 +4,7 @@ enum class RestoreStage {
|
|||
// Note: the ordinal order is used to manage progression through each stage
|
||||
// so be careful if reordering these
|
||||
Seed,
|
||||
Birthday,
|
||||
Complete;
|
||||
Birthday;
|
||||
|
||||
/**
|
||||
* @see getPrevious
|
||||
|
@ -15,15 +14,15 @@ enum class RestoreStage {
|
|||
/**
|
||||
* @see getNext
|
||||
*/
|
||||
fun hasNext() = ordinal < values().size - 1
|
||||
fun hasNext() = ordinal < entries.size - 1
|
||||
|
||||
/**
|
||||
* @return Previous item in ordinal order. Returns the first item when it cannot go further back.
|
||||
*/
|
||||
fun getPrevious() = values()[maxOf(0, ordinal - 1)]
|
||||
fun getPrevious() = entries[maxOf(0, ordinal - 1)]
|
||||
|
||||
/**
|
||||
* @return Last item in ordinal order. Returns the last item when it cannot go further forward.
|
||||
*/
|
||||
fun getNext() = values()[minOf(values().size - 1, ordinal + 1)]
|
||||
fun getNext() = entries[minOf(entries.size - 1, ordinal + 1)]
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import androidx.compose.material3.TextField
|
|||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
|
@ -71,11 +72,9 @@ import co.electriccoin.zcash.ui.design.component.Body
|
|||
import co.electriccoin.zcash.ui.design.component.ChipOnSurface
|
||||
import co.electriccoin.zcash.ui.design.component.FormTextField
|
||||
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.Reference
|
||||
import co.electriccoin.zcash.ui.design.component.SmallTopAppBar
|
||||
import co.electriccoin.zcash.ui.design.component.TertiaryButton
|
||||
import co.electriccoin.zcash.ui.design.component.TopScreenLogoTitle
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.screen.restore.RestoreTag
|
||||
|
@ -88,9 +87,9 @@ import kotlinx.collections.immutable.ImmutableSet
|
|||
import kotlinx.collections.immutable.persistentHashSetOf
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Preview("Restore Wallet")
|
||||
@Preview("Restore Seed")
|
||||
@Composable
|
||||
private fun PreviewRestore() {
|
||||
private fun PreviewRestoreSeed() {
|
||||
ZcashTheme(forceDarkMode = false) {
|
||||
GradientSurface {
|
||||
RestoreWallet(
|
||||
|
@ -119,12 +118,31 @@ private fun PreviewRestore() {
|
|||
}
|
||||
}
|
||||
|
||||
@Preview("Restore Complete")
|
||||
@Preview("Restore Seed Birthday")
|
||||
@Composable
|
||||
private fun PreviewRestoreComplete() {
|
||||
private fun PreviewRestoreBirthday() {
|
||||
ZcashTheme(forceDarkMode = false) {
|
||||
RestoreComplete(
|
||||
onComplete = {}
|
||||
RestoreWallet(
|
||||
ZcashNetwork.Mainnet,
|
||||
restoreState = RestoreState(RestoreStage.Birthday),
|
||||
completeWordList = persistentHashSetOf(
|
||||
"abandon",
|
||||
"ability",
|
||||
"able",
|
||||
"about",
|
||||
"above",
|
||||
"absent",
|
||||
"absorb",
|
||||
"abstract",
|
||||
"rib",
|
||||
"ribbon"
|
||||
),
|
||||
userWordList = WordList(listOf("abandon", "absorb")),
|
||||
restoreHeight = null,
|
||||
setRestoreHeight = {},
|
||||
onBack = {},
|
||||
paste = { "" },
|
||||
onFinished = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -135,7 +153,6 @@ private fun PreviewRestoreComplete() {
|
|||
* @param restoreHeight A null height indicates no user input.
|
||||
*/
|
||||
@Suppress("LongParameterList", "LongMethod")
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
fun RestoreWallet(
|
||||
zcashNetwork: ZcashNetwork,
|
||||
|
@ -167,20 +184,28 @@ fun RestoreWallet(
|
|||
Scaffold(
|
||||
modifier = Modifier.navigationBarsPadding(),
|
||||
topBar = {
|
||||
RestoreTopAppBar(
|
||||
onBack = {
|
||||
if (currentStage.hasPrevious()) {
|
||||
restoreState.goPrevious()
|
||||
} else {
|
||||
onBack()
|
||||
}
|
||||
},
|
||||
isShowClear = currentStage == RestoreStage.Seed,
|
||||
onClear = {
|
||||
userWordList.set(emptyList())
|
||||
text = ""
|
||||
when (currentStage) {
|
||||
RestoreStage.Seed -> {
|
||||
RestoreSeedTopAppBar(
|
||||
onBack = onBack,
|
||||
onClear = {
|
||||
userWordList.set(emptyList())
|
||||
text = ""
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
RestoreStage.Birthday -> {
|
||||
RestoreSeedBirthdayTopAppBar(
|
||||
onBack = {
|
||||
if (currentStage.hasPrevious()) {
|
||||
restoreState.goPrevious()
|
||||
} else {
|
||||
onBack()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
bottomBar = {
|
||||
when (currentStage) {
|
||||
|
@ -198,10 +223,7 @@ fun RestoreWallet(
|
|||
)
|
||||
}
|
||||
RestoreStage.Birthday -> {
|
||||
// No content
|
||||
}
|
||||
RestoreStage.Complete -> {
|
||||
// No content
|
||||
// No content. The action button is part of scrollable content.
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -231,25 +253,14 @@ fun RestoreWallet(
|
|||
)
|
||||
}
|
||||
RestoreStage.Birthday -> {
|
||||
RestoreBirthday(
|
||||
RestoreBirthdayMainContent(
|
||||
zcashNetwork = zcashNetwork,
|
||||
initialRestoreHeight = restoreHeight,
|
||||
setRestoreHeight = setRestoreHeight,
|
||||
onNext = { restoreState.goNext() },
|
||||
onDone = onFinished,
|
||||
modifier = commonModifier
|
||||
.imePadding()
|
||||
.navigationBarsPadding()
|
||||
.animateContentSize()
|
||||
)
|
||||
}
|
||||
RestoreStage.Complete -> {
|
||||
// In some cases we need to hide the software keyboard manually, as it stays shown after
|
||||
// input on prior screens
|
||||
LocalSoftwareKeyboardController.current?.hide()
|
||||
|
||||
RestoreComplete(
|
||||
onComplete = onFinished,
|
||||
modifier = commonModifier
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -257,29 +268,6 @@ fun RestoreWallet(
|
|||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RestoreTopAppBar(
|
||||
onBack: () -> Unit,
|
||||
onClear: () -> Unit,
|
||||
isShowClear: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
SmallTopAppBar(
|
||||
backText = stringResource(id = R.string.restore_back).uppercase(),
|
||||
backContentDescriptionText = stringResource(R.string.restore_back_content_description),
|
||||
onBack = onBack,
|
||||
regularActions = if (isShowClear) { {
|
||||
ClearSeedMenuItem(
|
||||
onSeedClear = onClear
|
||||
)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
},
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ClearSeedMenuItem(
|
||||
modifier: Modifier = Modifier,
|
||||
|
@ -295,6 +283,38 @@ private fun ClearSeedMenuItem(
|
|||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RestoreSeedTopAppBar(
|
||||
onBack: () -> Unit,
|
||||
onClear: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
SmallTopAppBar(
|
||||
backText = stringResource(id = R.string.restore_back).uppercase(),
|
||||
backContentDescriptionText = stringResource(R.string.restore_back_content_description),
|
||||
onBack = onBack,
|
||||
regularActions = {
|
||||
ClearSeedMenuItem(
|
||||
onSeedClear = onClear
|
||||
)
|
||||
},
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RestoreSeedBirthdayTopAppBar(
|
||||
onBack: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
SmallTopAppBar(
|
||||
backText = stringResource(id = R.string.restore_back).uppercase(),
|
||||
backContentDescriptionText = stringResource(R.string.restore_back_content_description),
|
||||
onBack = onBack,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
||||
// TODO [#672]: Implement custom seed phrase pasting for wallet import
|
||||
// TODO [#672]: https://github.com/Electric-Coin-Company/zashi-android/issues/672
|
||||
// TODO [#1060]: https://github.com/Electric-Coin-Company/zashi-android/issues/1060
|
||||
|
@ -317,8 +337,6 @@ private fun RestoreSeedMainContent(
|
|||
val focusRequester = remember { FocusRequester() }
|
||||
val textFieldScrollToHeight = rememberSaveable { mutableIntStateOf(0) }
|
||||
|
||||
Twig.error { "TEST: $parseResult, $text" }
|
||||
|
||||
if (parseResult is ParseResult.Add) {
|
||||
setText("")
|
||||
userWordList.append(parseResult.words)
|
||||
|
@ -388,7 +406,6 @@ private fun RestoreSeedMainContent(
|
|||
DisposableEffect(parseResult) {
|
||||
// Causes the TextFiled to refocus
|
||||
if (!isSeedValid) {
|
||||
Twig.error { "NUT" }
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
// Causes scroll to the TextField after the first type action
|
||||
|
@ -638,13 +655,16 @@ private fun Warn(
|
|||
|
||||
@Composable
|
||||
@Suppress("LongMethod")
|
||||
private fun RestoreBirthday(
|
||||
private fun RestoreBirthdayMainContent(
|
||||
zcashNetwork: ZcashNetwork,
|
||||
initialRestoreHeight: BlockHeight?,
|
||||
setRestoreHeight: (BlockHeight?) -> Unit,
|
||||
onNext: () -> Unit,
|
||||
onDone: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val scrollState = rememberScrollState()
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
|
||||
val (height, setHeight) = rememberSaveable {
|
||||
mutableStateOf(initialRestoreHeight?.value?.toString() ?: "")
|
||||
}
|
||||
|
@ -652,15 +672,16 @@ private fun RestoreBirthday(
|
|||
Column(
|
||||
Modifier
|
||||
.fillMaxHeight()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.verticalScroll(scrollState)
|
||||
.then(modifier),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Header(stringResource(R.string.restore_birthday_header))
|
||||
TopScreenLogoTitle(
|
||||
title = stringResource(R.string.restore_birthday_header),
|
||||
logoContentDescription = stringResource(R.string.zcash_logo_content_description),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
||||
|
||||
Body(stringResource(R.string.restore_birthday_body))
|
||||
Body(stringResource(R.string.restore_birthday_sub_header))
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
||||
|
||||
|
@ -670,11 +691,12 @@ private fun RestoreBirthday(
|
|||
val filteredHeightString = heightString.filter { it.isDigit() }
|
||||
setHeight(filteredHeightString)
|
||||
},
|
||||
Modifier
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(ZcashTheme.dimens.spacingTiny)
|
||||
.focusRequester(focusRequester)
|
||||
.testTag(RestoreTag.BIRTHDAY_TEXT_FIELD),
|
||||
label = { Text(stringResource(id = R.string.restore_birthday_hint)) },
|
||||
textStyle = ZcashTheme.extendedTypography.textFieldBirthday,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
KeyboardCapitalization.None,
|
||||
autoCorrect = false,
|
||||
|
@ -682,7 +704,7 @@ private fun RestoreBirthday(
|
|||
keyboardType = KeyboardType.Number
|
||||
),
|
||||
keyboardActions = KeyboardActions(onAny = {}),
|
||||
shape = RectangleShape,
|
||||
withBorder = false,
|
||||
)
|
||||
|
||||
Spacer(
|
||||
|
@ -693,65 +715,33 @@ private fun RestoreBirthday(
|
|||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
||||
|
||||
val isBirthdayValid = height.toLongOrNull()?.let {
|
||||
// Empty birthday value is a valid birthday height too, thus run validation only in case of non-empty heights.
|
||||
val isBirthdayValid = height.isEmpty() || height.toLongOrNull()?.let {
|
||||
it >= zcashNetwork.saplingActivationHeight.value
|
||||
} ?: false
|
||||
|
||||
val isEmptyBirthday = height.isEmpty()
|
||||
|
||||
PrimaryButton(
|
||||
onClick = {
|
||||
setRestoreHeight(BlockHeight.new(zcashNetwork, height.toLong()))
|
||||
onNext()
|
||||
if (isEmptyBirthday) {
|
||||
setRestoreHeight(null)
|
||||
} else if (isBirthdayValid) {
|
||||
setRestoreHeight(BlockHeight.new(zcashNetwork, height.toLong()))
|
||||
} else {
|
||||
error("The restore button should not expect click events")
|
||||
}
|
||||
onDone()
|
||||
},
|
||||
text = stringResource(R.string.restore_birthday_button_restore),
|
||||
enabled = isBirthdayValid,
|
||||
outerPaddingValues = PaddingValues(top = ZcashTheme.dimens.spacingSmall)
|
||||
enabled = isBirthdayValid
|
||||
)
|
||||
|
||||
TertiaryButton(
|
||||
onClick = {
|
||||
setRestoreHeight(null)
|
||||
onNext()
|
||||
},
|
||||
text = stringResource(R.string.restore_birthday_button_skip),
|
||||
outerPaddingValues = PaddingValues(top = ZcashTheme.dimens.spacingDefault)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingHuge))
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RestoreComplete(
|
||||
onComplete: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxHeight()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.then(modifier),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Header(stringResource(R.string.restore_complete_header))
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
||||
|
||||
Body(stringResource(R.string.restore_complete_info))
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
||||
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.weight(MINIMAL_WEIGHT)
|
||||
)
|
||||
|
||||
PrimaryButton(
|
||||
onClick = onComplete,
|
||||
text = stringResource(R.string.restore_button_see_wallet),
|
||||
outerPaddingValues = PaddingValues(top = ZcashTheme.dimens.spacingSmall)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
||||
LaunchedEffect(Unit) {
|
||||
// Causes the TextFiled to focus on the first screen visit
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,14 +11,8 @@
|
|||
<string name="restore_seed_warning_suggestions">This word is not in the seed phrase dictionary. Please select the correct one from the suggestions.</string>
|
||||
<string name="restore_seed_warning_no_suggestions">This word is not in the seed phrase dictionary.</string>
|
||||
|
||||
<string name="restore_birthday_header">Do you know the wallet’s birthday?</string>
|
||||
<string name="restore_birthday_body">This will allow a faster sync. If you don’t know the wallet’s birthday, don’t worry.</string>
|
||||
<string name="restore_birthday_hint">Birthday height</string>
|
||||
<string name="restore_birthday_button_restore">Restore with this birthday</string>
|
||||
<string name="restore_birthday_button_skip">I don’t know the birthday</string>
|
||||
|
||||
<string name="restore_complete_header">Seed phrase imported!</string>
|
||||
<string name="restore_complete_info">We will now scan the blockchain to find your transactions and balance.</string>
|
||||
<string name="restore_button_see_wallet">Take me to my wallet</string>
|
||||
<string name="restore_birthday_header">Wallet birthday height</string>
|
||||
<string name="restore_birthday_sub_header">(optional)</string>
|
||||
<string name="restore_birthday_button_restore">Restore</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -141,11 +141,6 @@ class ScreenshotTest : UiTestPrerequisites() {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO [#859]: Screenshot tests fail on Firebase Test Lab
|
||||
// TODO [#859]: https://github.com/Electric-Coin-Company/zashi-android/issues/859
|
||||
// Some of the restore screenshots broke with the Compose 1.4 update and we don't yet know why.
|
||||
private val isRestoreScreenshotsEnabled = false
|
||||
|
||||
@Suppress("LongMethod", "FunctionNaming", "CyclomaticComplexMethod")
|
||||
private fun take_screenshots_for_restore_wallet(resContext: Context, tag: String) {
|
||||
// TODO [#286]: Screenshot tests fail on Firebase Test Lab
|
||||
|
@ -207,39 +202,31 @@ class ScreenshotTest : UiTestPrerequisites() {
|
|||
SeedPhrase.SEED_PHRASE_SIZE
|
||||
}
|
||||
|
||||
if (isRestoreScreenshotsEnabled.not()) {
|
||||
return
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText(resContext.getString(R.string.restore_seed_button_next)).also {
|
||||
it.performScrollTo()
|
||||
|
||||
composeTestRule.onNodeWithText(
|
||||
text = resContext.getString(R.string.restore_seed_button_next),
|
||||
ignoreCase = true
|
||||
).also {
|
||||
// Even with waiting for the word list in the view model, there's some latency before the button is enabled
|
||||
composeTestRule.waitUntil(5.seconds.inWholeMilliseconds) {
|
||||
runCatching { it.assertIsEnabled() }.isSuccess
|
||||
}
|
||||
|
||||
it.performClick()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText(resContext.getString(R.string.restore_birthday_button_restore)).also {
|
||||
composeTestRule.waitUntil(5.seconds.inWholeMilliseconds) {
|
||||
kotlin.runCatching { it.assertExists() }.isSuccess
|
||||
}
|
||||
}
|
||||
|
||||
takeScreenshot(tag, "Import 3")
|
||||
|
||||
composeTestRule.onNodeWithText(resContext.getString(R.string.restore_birthday_button_skip)).also {
|
||||
it.performScrollTo()
|
||||
it.performClick()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText(resContext.getString(R.string.restore_complete_header)).also {
|
||||
composeTestRule.onNodeWithText(resContext.getString(R.string.restore_birthday_header)).also {
|
||||
it.assertExists()
|
||||
}
|
||||
|
||||
takeScreenshot(tag, "Import 4")
|
||||
takeScreenshot(tag, "Import 3")
|
||||
|
||||
composeTestRule.onNodeWithText(
|
||||
text = resContext.getString(R.string.restore_birthday_button_restore),
|
||||
ignoreCase = true
|
||||
).also {
|
||||
it.performScrollTo()
|
||||
it.performClick()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
Loading…
Reference in New Issue