2023-03-21 12:04:16 -07:00
|
|
|
@file:Suppress("TooManyFunctions")
|
|
|
|
|
2022-03-08 11:05:03 -08:00
|
|
|
package co.electriccoin.zcash.ui.screen.restore.view
|
2021-12-09 12:21:30 -08:00
|
|
|
|
|
|
|
import androidx.compose.foundation.border
|
2022-12-01 03:31:02 -08:00
|
|
|
import androidx.compose.foundation.clickable
|
2021-12-09 12:21:30 -08:00
|
|
|
import androidx.compose.foundation.layout.Column
|
2023-04-04 05:21:18 -07:00
|
|
|
import androidx.compose.foundation.layout.PaddingValues
|
2021-12-09 12:21:30 -08:00
|
|
|
import androidx.compose.foundation.layout.Row
|
|
|
|
import androidx.compose.foundation.layout.Spacer
|
|
|
|
import androidx.compose.foundation.layout.fillMaxHeight
|
|
|
|
import androidx.compose.foundation.layout.fillMaxWidth
|
2023-04-04 05:21:18 -07:00
|
|
|
import androidx.compose.foundation.layout.height
|
2022-06-09 09:31:44 -07:00
|
|
|
import androidx.compose.foundation.layout.padding
|
2021-12-09 12:21:30 -08:00
|
|
|
import androidx.compose.foundation.lazy.LazyRow
|
|
|
|
import androidx.compose.foundation.lazy.items
|
|
|
|
import androidx.compose.foundation.rememberScrollState
|
2022-12-01 03:31:02 -08:00
|
|
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
2021-12-09 12:21:30 -08:00
|
|
|
import androidx.compose.foundation.text.KeyboardActions
|
|
|
|
import androidx.compose.foundation.text.KeyboardOptions
|
|
|
|
import androidx.compose.foundation.verticalScroll
|
|
|
|
import androidx.compose.material.icons.Icons
|
|
|
|
import androidx.compose.material.icons.filled.ArrowBack
|
2022-02-21 06:33:40 -08:00
|
|
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
|
|
import androidx.compose.material3.Icon
|
|
|
|
import androidx.compose.material3.IconButton
|
2022-12-01 03:31:02 -08:00
|
|
|
import androidx.compose.material3.MaterialTheme
|
2022-02-21 06:33:40 -08:00
|
|
|
import androidx.compose.material3.Scaffold
|
2022-12-01 03:31:02 -08:00
|
|
|
import androidx.compose.material3.Surface
|
2022-02-21 06:33:40 -08:00
|
|
|
import androidx.compose.material3.Text
|
2022-04-04 05:56:34 -07:00
|
|
|
import androidx.compose.material3.TextField
|
2022-12-01 03:31:02 -08:00
|
|
|
import androidx.compose.material3.TextFieldDefaults
|
2022-09-22 06:55:34 -07:00
|
|
|
import androidx.compose.material3.TopAppBar
|
2021-12-09 12:21:30 -08:00
|
|
|
import androidx.compose.runtime.Composable
|
|
|
|
import androidx.compose.runtime.DisposableEffect
|
|
|
|
import androidx.compose.runtime.collectAsState
|
|
|
|
import androidx.compose.runtime.getValue
|
|
|
|
import androidx.compose.runtime.mutableStateOf
|
|
|
|
import androidx.compose.runtime.remember
|
2022-12-01 03:31:02 -08:00
|
|
|
import androidx.compose.runtime.rememberCoroutineScope
|
2021-12-09 12:21:30 -08:00
|
|
|
import androidx.compose.runtime.saveable.rememberSaveable
|
|
|
|
import androidx.compose.runtime.setValue
|
2023-10-04 07:01:40 -07:00
|
|
|
import androidx.compose.ui.Alignment
|
2022-12-01 03:31:02 -08:00
|
|
|
import androidx.compose.ui.Alignment.Companion.CenterVertically
|
2022-05-09 01:26:13 -07:00
|
|
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
2021-12-09 12:21:30 -08:00
|
|
|
import androidx.compose.ui.Modifier
|
2023-08-07 05:52:24 -07:00
|
|
|
import androidx.compose.ui.draw.shadow
|
2021-12-09 12:21:30 -08:00
|
|
|
import androidx.compose.ui.focus.FocusRequester
|
|
|
|
import androidx.compose.ui.focus.focusRequester
|
2022-12-01 03:31:02 -08:00
|
|
|
import androidx.compose.ui.graphics.Color
|
2023-08-04 04:01:16 -07:00
|
|
|
import androidx.compose.ui.graphics.RectangleShape
|
2022-05-09 01:26:13 -07:00
|
|
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
2021-12-09 12:21:30 -08:00
|
|
|
import androidx.compose.ui.platform.testTag
|
|
|
|
import androidx.compose.ui.res.stringResource
|
|
|
|
import androidx.compose.ui.text.input.ImeAction
|
|
|
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
|
|
|
import androidx.compose.ui.text.input.KeyboardType
|
2022-12-01 03:31:02 -08:00
|
|
|
import androidx.compose.ui.text.style.TextAlign
|
2021-12-09 12:21:30 -08:00
|
|
|
import androidx.compose.ui.tooling.preview.Preview
|
|
|
|
import androidx.compose.ui.unit.dp
|
2022-12-22 00:38:02 -08:00
|
|
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
2023-03-21 12:04:16 -07:00
|
|
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
|
|
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
2021-12-09 12:21:30 -08:00
|
|
|
import cash.z.ecc.sdk.model.SeedPhraseValidation
|
2022-02-21 06:50:09 -08:00
|
|
|
import co.electriccoin.zcash.spackle.model.Index
|
2023-10-17 07:01:53 -07:00
|
|
|
import co.electriccoin.zcash.ui.BuildConfig
|
2022-03-08 11:05:03 -08:00
|
|
|
import co.electriccoin.zcash.ui.R
|
2022-09-26 01:28:08 -07:00
|
|
|
import co.electriccoin.zcash.ui.common.SecureScreen
|
2022-03-08 11:05:03 -08:00
|
|
|
import co.electriccoin.zcash.ui.design.MINIMAL_WEIGHT
|
|
|
|
import co.electriccoin.zcash.ui.design.component.Body
|
|
|
|
import co.electriccoin.zcash.ui.design.component.CHIP_GRID_ROW_SIZE
|
|
|
|
import co.electriccoin.zcash.ui.design.component.Chip
|
2023-04-04 05:21:18 -07:00
|
|
|
import co.electriccoin.zcash.ui.design.component.FormTextField
|
2022-03-08 11:05:03 -08:00
|
|
|
import co.electriccoin.zcash.ui.design.component.GradientSurface
|
|
|
|
import co.electriccoin.zcash.ui.design.component.Header
|
|
|
|
import co.electriccoin.zcash.ui.design.component.NavigationButton
|
|
|
|
import co.electriccoin.zcash.ui.design.component.PrimaryButton
|
2023-03-21 12:04:16 -07:00
|
|
|
import co.electriccoin.zcash.ui.design.component.TertiaryButton
|
2022-03-08 11:05:03 -08:00
|
|
|
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
2023-03-21 12:04:16 -07:00
|
|
|
import co.electriccoin.zcash.ui.design.theme.ZcashTheme.dimens
|
2022-03-08 11:05:03 -08:00
|
|
|
import co.electriccoin.zcash.ui.screen.restore.RestoreTag
|
|
|
|
import co.electriccoin.zcash.ui.screen.restore.model.ParseResult
|
2023-03-21 12:04:16 -07:00
|
|
|
import co.electriccoin.zcash.ui.screen.restore.model.RestoreStage
|
|
|
|
import co.electriccoin.zcash.ui.screen.restore.state.RestoreState
|
2022-03-08 11:05:03 -08:00
|
|
|
import co.electriccoin.zcash.ui.screen.restore.state.WordList
|
|
|
|
import co.electriccoin.zcash.ui.screen.restore.state.wordValidation
|
2023-03-01 04:58:47 -08:00
|
|
|
import kotlinx.collections.immutable.ImmutableList
|
|
|
|
import kotlinx.collections.immutable.ImmutableSet
|
|
|
|
import kotlinx.collections.immutable.persistentHashSetOf
|
2022-12-01 03:31:02 -08:00
|
|
|
import kotlinx.coroutines.launch
|
2021-12-09 12:21:30 -08:00
|
|
|
|
|
|
|
@Preview("Restore Wallet")
|
|
|
|
@Composable
|
2023-06-18 23:59:00 -07:00
|
|
|
private fun PreviewRestore() {
|
2023-10-04 07:01:40 -07:00
|
|
|
ZcashTheme(darkTheme = false) {
|
2021-12-09 12:21:30 -08:00
|
|
|
GradientSurface {
|
|
|
|
RestoreWallet(
|
2023-03-21 12:04:16 -07:00
|
|
|
ZcashNetwork.Mainnet,
|
|
|
|
restoreState = RestoreState(RestoreStage.Seed),
|
2023-03-01 04:58:47 -08:00
|
|
|
completeWordList = persistentHashSetOf(
|
2021-12-09 12:21:30 -08:00
|
|
|
"abandon",
|
|
|
|
"ability",
|
|
|
|
"able",
|
|
|
|
"about",
|
|
|
|
"above",
|
|
|
|
"absent",
|
|
|
|
"absorb",
|
2022-04-11 06:19:04 -07:00
|
|
|
"abstract",
|
|
|
|
"rib",
|
|
|
|
"ribbon"
|
2021-12-09 12:21:30 -08:00
|
|
|
),
|
|
|
|
userWordList = WordList(listOf("abandon", "absorb")),
|
2023-03-21 12:04:16 -07:00
|
|
|
restoreHeight = null,
|
|
|
|
setRestoreHeight = {},
|
2021-12-09 12:21:30 -08:00
|
|
|
onBack = {},
|
|
|
|
paste = { "" },
|
|
|
|
onFinished = {}
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Preview("Restore Complete")
|
|
|
|
@Composable
|
2023-06-18 23:59:00 -07:00
|
|
|
private fun PreviewRestoreComplete() {
|
2023-10-04 07:01:40 -07:00
|
|
|
ZcashTheme(darkTheme = false) {
|
2021-12-09 12:21:30 -08:00
|
|
|
RestoreComplete(
|
|
|
|
onComplete = {}
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-21 12:04:16 -07:00
|
|
|
/**
|
|
|
|
* Note that the restore review doesn't allow the user to go back once the seed is entered correctly.
|
|
|
|
*
|
|
|
|
* @param restoreHeight A null height indicates no user input.
|
|
|
|
*/
|
|
|
|
@Suppress("LongParameterList", "LongMethod")
|
2023-06-18 23:59:00 -07:00
|
|
|
@OptIn(ExperimentalComposeUiApi::class)
|
2021-12-09 12:21:30 -08:00
|
|
|
@Composable
|
|
|
|
fun RestoreWallet(
|
2023-03-21 12:04:16 -07:00
|
|
|
zcashNetwork: ZcashNetwork,
|
|
|
|
restoreState: RestoreState,
|
2023-03-01 04:58:47 -08:00
|
|
|
completeWordList: ImmutableSet<String>,
|
2021-12-09 12:21:30 -08:00
|
|
|
userWordList: WordList,
|
2023-03-21 12:04:16 -07:00
|
|
|
restoreHeight: BlockHeight?,
|
|
|
|
setRestoreHeight: (BlockHeight?) -> Unit,
|
2021-12-09 12:21:30 -08:00
|
|
|
onBack: () -> Unit,
|
|
|
|
paste: () -> String?,
|
|
|
|
onFinished: () -> Unit
|
|
|
|
) {
|
2022-12-01 03:31:02 -08:00
|
|
|
var textState by rememberSaveable { mutableStateOf("") }
|
|
|
|
val focusRequester = remember { FocusRequester() }
|
|
|
|
val parseResult = ParseResult.new(completeWordList, textState)
|
|
|
|
|
2023-03-21 12:04:16 -07:00
|
|
|
val currentStage = restoreState.current.collectAsStateWithLifecycle().value
|
|
|
|
|
|
|
|
Scaffold(
|
|
|
|
topBar = {
|
|
|
|
RestoreTopAppBar(
|
|
|
|
onBack = {
|
|
|
|
if (currentStage.hasPrevious()) {
|
|
|
|
restoreState.goPrevious()
|
|
|
|
} else {
|
|
|
|
onBack()
|
|
|
|
}
|
|
|
|
},
|
|
|
|
isShowClear = currentStage == RestoreStage.Seed,
|
|
|
|
onClear = { userWordList.set(emptyList()) }
|
|
|
|
)
|
|
|
|
},
|
|
|
|
bottomBar = {
|
|
|
|
when (currentStage) {
|
|
|
|
RestoreStage.Seed -> {
|
|
|
|
RestoreSeedBottomBar(
|
|
|
|
userWordList = userWordList,
|
2022-12-19 09:58:43 -08:00
|
|
|
parseResult = parseResult,
|
2023-03-21 12:04:16 -07:00
|
|
|
setTextState = { textState = it },
|
2023-10-12 10:04:23 -07:00
|
|
|
focusRequester = focusRequester,
|
|
|
|
modifier = Modifier.padding(
|
|
|
|
bottom = ZcashTheme.dimens.spacingHuge
|
|
|
|
).fillMaxWidth()
|
2022-12-19 09:58:43 -08:00
|
|
|
)
|
|
|
|
}
|
2023-03-21 12:04:16 -07:00
|
|
|
RestoreStage.Birthday -> {
|
|
|
|
// No content
|
|
|
|
}
|
|
|
|
RestoreStage.Complete -> {
|
|
|
|
// No content
|
|
|
|
}
|
2021-12-09 12:21:30 -08:00
|
|
|
}
|
2023-03-21 12:04:16 -07:00
|
|
|
},
|
|
|
|
content = { paddingValues ->
|
2023-04-04 05:21:18 -07:00
|
|
|
val commonModifier = Modifier
|
|
|
|
// We intentionally set the bottom smaller to save space in case of the software keyboard is visible
|
|
|
|
.padding(
|
|
|
|
top = paddingValues.calculateTopPadding() + dimens.spacingDefault,
|
|
|
|
bottom = paddingValues.calculateBottomPadding() + dimens.spacingSmall,
|
|
|
|
start = dimens.spacingDefault,
|
|
|
|
end = dimens.spacingDefault
|
|
|
|
)
|
|
|
|
|
2023-03-21 12:04:16 -07:00
|
|
|
when (currentStage) {
|
|
|
|
RestoreStage.Seed -> {
|
2023-10-17 07:01:53 -07:00
|
|
|
if (BuildConfig.IS_SECURE_SCREEN_ENABLED) {
|
|
|
|
SecureScreen()
|
|
|
|
}
|
2022-05-09 01:26:13 -07:00
|
|
|
|
2023-03-21 12:04:16 -07:00
|
|
|
RestoreSeedMainContent(
|
|
|
|
userWordList = userWordList,
|
|
|
|
textState = textState,
|
|
|
|
setTextState = { textState = it },
|
|
|
|
focusRequester = focusRequester,
|
|
|
|
parseResult = parseResult,
|
|
|
|
paste = paste,
|
|
|
|
goNext = { restoreState.goNext() },
|
2023-04-04 05:21:18 -07:00
|
|
|
modifier = commonModifier
|
2023-03-21 12:04:16 -07:00
|
|
|
)
|
|
|
|
}
|
|
|
|
RestoreStage.Birthday -> {
|
|
|
|
RestoreBirthday(
|
|
|
|
zcashNetwork = zcashNetwork,
|
|
|
|
initialRestoreHeight = restoreHeight,
|
|
|
|
setRestoreHeight = setRestoreHeight,
|
|
|
|
onNext = { restoreState.goNext() },
|
2023-04-04 05:21:18 -07:00
|
|
|
modifier = commonModifier
|
2023-03-21 12:04:16 -07:00
|
|
|
)
|
|
|
|
}
|
|
|
|
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,
|
2023-04-04 05:21:18 -07:00
|
|
|
modifier = commonModifier
|
2023-03-21 12:04:16 -07:00
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2021-12-09 12:21:30 -08:00
|
|
|
}
|
2023-03-21 12:04:16 -07:00
|
|
|
)
|
2021-12-09 12:21:30 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
@Composable
|
2022-09-22 06:55:34 -07:00
|
|
|
@OptIn(ExperimentalMaterial3Api::class)
|
2023-03-21 12:04:16 -07:00
|
|
|
private fun RestoreTopAppBar(onBack: () -> Unit, isShowClear: Boolean, onClear: () -> Unit) {
|
2022-09-22 06:55:34 -07:00
|
|
|
TopAppBar(
|
2022-12-01 03:31:02 -08:00
|
|
|
title = { Text(text = stringResource(id = R.string.restore_title)) },
|
2021-12-31 05:28:16 -08:00
|
|
|
navigationIcon = {
|
2021-12-09 12:21:30 -08:00
|
|
|
IconButton(
|
|
|
|
onClick = onBack
|
|
|
|
) {
|
|
|
|
Icon(
|
|
|
|
imageVector = Icons.Filled.ArrowBack,
|
|
|
|
contentDescription = stringResource(R.string.restore_back_content_description)
|
|
|
|
)
|
|
|
|
}
|
2021-12-31 05:28:16 -08:00
|
|
|
},
|
|
|
|
actions = {
|
2023-03-21 12:04:16 -07:00
|
|
|
if (isShowClear) {
|
|
|
|
NavigationButton(onClick = onClear, stringResource(R.string.restore_button_clear))
|
|
|
|
}
|
2021-12-09 12:21:30 -08:00
|
|
|
}
|
2021-12-31 05:28:16 -08:00
|
|
|
)
|
2021-12-09 12:21:30 -08:00
|
|
|
}
|
|
|
|
|
2023-06-18 23:59:00 -07:00
|
|
|
// TODO [#672]: Implement custom seed phrase pasting for wallet import
|
|
|
|
// TODO [#672]: https://github.com/zcash/secant-android-wallet/issues/672
|
2023-01-17 11:13:46 -08:00
|
|
|
|
2023-03-21 12:04:16 -07:00
|
|
|
@OptIn(ExperimentalComposeUiApi::class)
|
2022-12-01 03:31:02 -08:00
|
|
|
@Suppress("UNUSED_PARAMETER", "LongParameterList")
|
2021-12-09 12:21:30 -08:00
|
|
|
@Composable
|
2023-03-21 12:04:16 -07:00
|
|
|
private fun RestoreSeedMainContent(
|
2021-12-09 12:21:30 -08:00
|
|
|
userWordList: WordList,
|
2023-03-21 12:04:16 -07:00
|
|
|
textState: String,
|
|
|
|
setTextState: (String) -> Unit,
|
2022-12-01 03:31:02 -08:00
|
|
|
focusRequester: FocusRequester,
|
|
|
|
parseResult: ParseResult,
|
2023-03-21 12:04:16 -07:00
|
|
|
paste: () -> String?,
|
|
|
|
goNext: () -> Unit,
|
|
|
|
modifier: Modifier = Modifier
|
2021-12-09 12:21:30 -08:00
|
|
|
) {
|
2022-12-22 00:38:02 -08:00
|
|
|
val currentUserWordList = userWordList.current.collectAsStateWithLifecycle().value
|
2022-12-01 03:31:02 -08:00
|
|
|
val scrollState = rememberScrollState()
|
|
|
|
val scope = rememberCoroutineScope()
|
2021-12-09 12:21:30 -08:00
|
|
|
|
|
|
|
if (parseResult is ParseResult.Add) {
|
2023-03-21 12:04:16 -07:00
|
|
|
setTextState("")
|
2021-12-09 12:21:30 -08:00
|
|
|
userWordList.append(parseResult.words)
|
|
|
|
}
|
|
|
|
|
2023-03-21 12:04:16 -07:00
|
|
|
val isSeedValid = userWordList.wordValidation().collectAsState(null).value is SeedPhraseValidation.Valid
|
|
|
|
|
2022-06-09 09:31:44 -07:00
|
|
|
Column(
|
2023-04-04 05:21:18 -07:00
|
|
|
Modifier
|
|
|
|
.fillMaxHeight()
|
|
|
|
.verticalScroll(scrollState)
|
2023-10-04 07:01:40 -07:00
|
|
|
.then(modifier),
|
|
|
|
horizontalAlignment = Alignment.CenterHorizontally
|
2022-06-09 09:31:44 -07:00
|
|
|
) {
|
2023-04-04 05:21:18 -07:00
|
|
|
Body(text = stringResource(id = R.string.restore_seed_instructions))
|
|
|
|
|
|
|
|
Spacer(Modifier.height(dimens.spacingSmall))
|
2021-12-09 12:21:30 -08:00
|
|
|
|
2022-12-01 03:31:02 -08:00
|
|
|
ChipGridWithText(currentUserWordList)
|
2023-03-21 12:04:16 -07:00
|
|
|
|
|
|
|
if (!isSeedValid) {
|
|
|
|
NextWordTextField(
|
|
|
|
parseResult = parseResult,
|
|
|
|
text = textState,
|
|
|
|
setText = { setTextState(it) },
|
|
|
|
modifier = Modifier.focusRequester(focusRequester)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2023-04-04 05:21:18 -07:00
|
|
|
Spacer(
|
|
|
|
modifier = Modifier
|
|
|
|
.fillMaxHeight()
|
|
|
|
.weight(MINIMAL_WEIGHT)
|
|
|
|
)
|
2023-03-21 12:04:16 -07:00
|
|
|
|
|
|
|
PrimaryButton(
|
|
|
|
onClick = goNext,
|
|
|
|
text = stringResource(id = R.string.restore_seed_button_restore),
|
2023-04-04 05:21:18 -07:00
|
|
|
enabled = isSeedValid,
|
|
|
|
outerPaddingValues = PaddingValues(top = dimens.spacingSmall)
|
2023-03-21 12:04:16 -07:00
|
|
|
)
|
2023-10-04 07:01:40 -07:00
|
|
|
|
|
|
|
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
2021-12-09 12:21:30 -08:00
|
|
|
}
|
|
|
|
|
2023-03-21 12:04:16 -07:00
|
|
|
if (isSeedValid) {
|
|
|
|
// Hides the keyboard, making it easier for users to see the next button
|
|
|
|
LocalSoftwareKeyboardController.current?.hide()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cause text field to refocus
|
2021-12-09 12:21:30 -08:00
|
|
|
DisposableEffect(parseResult) {
|
2023-03-21 12:04:16 -07:00
|
|
|
if (!isSeedValid) {
|
|
|
|
focusRequester.requestFocus()
|
|
|
|
}
|
2022-12-01 03:31:02 -08:00
|
|
|
scope.launch {
|
|
|
|
scrollState.scrollTo(scrollState.maxValue)
|
|
|
|
}
|
2021-12-09 12:21:30 -08:00
|
|
|
onDispose { }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-21 12:04:16 -07:00
|
|
|
@Composable
|
|
|
|
private fun RestoreSeedBottomBar(
|
|
|
|
userWordList: WordList,
|
|
|
|
parseResult: ParseResult,
|
|
|
|
setTextState: (String) -> Unit,
|
|
|
|
focusRequester: FocusRequester,
|
|
|
|
modifier: Modifier = Modifier
|
|
|
|
) {
|
|
|
|
val isSeedValid = userWordList.wordValidation().collectAsState(null).value is SeedPhraseValidation.Valid
|
|
|
|
// Hide the field once the user has completed the seed phrase; if they need the field back then
|
|
|
|
// the user can hit the clear button
|
|
|
|
if (!isSeedValid) {
|
2023-10-12 10:04:23 -07:00
|
|
|
Column(
|
|
|
|
modifier = modifier
|
|
|
|
) {
|
2023-04-04 05:21:18 -07:00
|
|
|
Warn(
|
|
|
|
parseResult = parseResult,
|
|
|
|
modifier = Modifier
|
|
|
|
.fillMaxWidth()
|
|
|
|
// Note we don't set the top, as it's set by the confirm button above
|
|
|
|
.padding(
|
|
|
|
bottom = dimens.spacingDefault,
|
|
|
|
start = dimens.spacingDefault,
|
|
|
|
end = dimens.spacingDefault
|
|
|
|
)
|
|
|
|
)
|
2023-03-21 12:04:16 -07:00
|
|
|
Autocomplete(parseResult = parseResult, {
|
|
|
|
setTextState("")
|
|
|
|
userWordList.append(listOf(it))
|
|
|
|
focusRequester.requestFocus()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-09 12:21:30 -08:00
|
|
|
@Composable
|
|
|
|
private fun ChipGridWithText(
|
2023-03-01 04:58:47 -08:00
|
|
|
userWordList: ImmutableList<String>
|
2021-12-09 12:21:30 -08:00
|
|
|
) {
|
2023-04-04 05:21:18 -07:00
|
|
|
Column(Modifier.testTag(RestoreTag.CHIP_LAYOUT)) {
|
2021-12-09 12:21:30 -08:00
|
|
|
userWordList.chunked(CHIP_GRID_ROW_SIZE).forEachIndexed { chunkIndex, chunk ->
|
2022-12-01 03:31:02 -08:00
|
|
|
Row(Modifier.fillMaxWidth(), verticalAlignment = CenterVertically) {
|
2021-12-09 12:21:30 -08:00
|
|
|
val remainder = (chunk.size % CHIP_GRID_ROW_SIZE)
|
|
|
|
|
|
|
|
val singleItemWeight = 1f / CHIP_GRID_ROW_SIZE
|
|
|
|
chunk.forEachIndexed { subIndex, word ->
|
|
|
|
Chip(
|
|
|
|
index = Index(chunkIndex * CHIP_GRID_ROW_SIZE + subIndex),
|
|
|
|
text = word,
|
|
|
|
modifier = Modifier.weight(singleItemWeight)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (0 != remainder) {
|
2022-12-01 03:31:02 -08:00
|
|
|
Spacer(modifier = Modifier.weight((CHIP_GRID_ROW_SIZE - chunk.size) * singleItemWeight))
|
2021-12-09 12:21:30 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Composable
|
2022-12-01 03:31:02 -08:00
|
|
|
private fun NextWordTextField(
|
|
|
|
parseResult: ParseResult,
|
|
|
|
text: String,
|
2023-03-01 04:58:47 -08:00
|
|
|
setText: (String) -> Unit,
|
|
|
|
modifier: Modifier = Modifier
|
2022-12-01 03:31:02 -08:00
|
|
|
) {
|
|
|
|
Surface(
|
2021-12-09 12:21:30 -08:00
|
|
|
modifier = modifier
|
|
|
|
.fillMaxWidth()
|
2023-08-07 05:52:24 -07:00
|
|
|
.padding(dimens.spacingTiny)
|
|
|
|
.shadow(
|
|
|
|
elevation = 12.dp,
|
|
|
|
ambientColor = MaterialTheme.colorScheme.primary,
|
|
|
|
spotColor = MaterialTheme.colorScheme.primary
|
|
|
|
),
|
2023-08-04 04:01:16 -07:00
|
|
|
shape = RectangleShape,
|
2023-08-07 05:52:24 -07:00
|
|
|
color = MaterialTheme.colorScheme.surface,
|
|
|
|
|
2022-12-01 03:31:02 -08:00
|
|
|
) {
|
2023-03-21 12:04:16 -07:00
|
|
|
/*
|
|
|
|
* Treat the user input as a password for more secure input, but disable the transformation
|
|
|
|
* to obscure typing.
|
|
|
|
*/
|
2022-12-01 03:31:02 -08:00
|
|
|
TextField(
|
2023-03-01 04:58:47 -08:00
|
|
|
modifier = Modifier
|
2022-12-01 03:31:02 -08:00
|
|
|
.fillMaxWidth()
|
2023-03-21 12:04:16 -07:00
|
|
|
.padding(dimens.spacingTiny)
|
2022-12-01 03:31:02 -08:00
|
|
|
.testTag(RestoreTag.SEED_WORD_TEXT_FIELD),
|
|
|
|
value = text,
|
|
|
|
onValueChange = setText,
|
|
|
|
keyboardOptions = KeyboardOptions(
|
|
|
|
KeyboardCapitalization.None,
|
|
|
|
autoCorrect = false,
|
|
|
|
imeAction = ImeAction.Done,
|
|
|
|
keyboardType = KeyboardType.Password
|
|
|
|
),
|
|
|
|
keyboardActions = KeyboardActions(onAny = {}),
|
|
|
|
shape = RoundedCornerShape(8.dp),
|
|
|
|
isError = parseResult is ParseResult.Warn,
|
2023-03-22 12:05:19 -07:00
|
|
|
colors = TextFieldDefaults.colors(
|
|
|
|
focusedContainerColor = Color.Transparent,
|
|
|
|
unfocusedContainerColor = Color.Transparent,
|
|
|
|
disabledContainerColor = Color.Transparent,
|
|
|
|
errorContainerColor = Color.Transparent,
|
2022-12-01 03:31:02 -08:00
|
|
|
focusedIndicatorColor = Color.Transparent,
|
|
|
|
unfocusedIndicatorColor = Color.Transparent,
|
|
|
|
disabledIndicatorColor = Color.Transparent
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
2021-12-09 12:21:30 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
private fun Autocomplete(
|
|
|
|
parseResult: ParseResult,
|
2023-03-01 04:58:47 -08:00
|
|
|
onSuggestionSelected: (String) -> Unit,
|
|
|
|
modifier: Modifier = Modifier
|
2021-12-09 12:21:30 -08:00
|
|
|
) {
|
|
|
|
val (isHighlight, suggestions) = when (parseResult) {
|
|
|
|
is ParseResult.Autocomplete -> {
|
|
|
|
Pair(false, parseResult.suggestions)
|
|
|
|
}
|
2022-12-01 03:31:02 -08:00
|
|
|
|
2021-12-09 12:21:30 -08:00
|
|
|
is ParseResult.Warn -> {
|
2022-12-01 03:31:02 -08:00
|
|
|
return
|
2021-12-09 12:21:30 -08:00
|
|
|
}
|
2022-12-01 03:31:02 -08:00
|
|
|
|
2021-12-09 12:21:30 -08:00
|
|
|
else -> {
|
|
|
|
Pair(false, null)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
suggestions?.let {
|
|
|
|
val highlightModifier = if (isHighlight) {
|
|
|
|
modifier.border(2.dp, ZcashTheme.colors.highlight)
|
|
|
|
} else {
|
|
|
|
modifier
|
|
|
|
}
|
|
|
|
|
2023-03-01 04:58:47 -08:00
|
|
|
@Suppress("ModifierReused")
|
2023-04-04 05:21:18 -07:00
|
|
|
LazyRow(
|
|
|
|
modifier = highlightModifier.testTag(RestoreTag.AUTOCOMPLETE_LAYOUT),
|
|
|
|
// Note we don't set the top, as it's set by the confirm button above
|
|
|
|
// And we also set the bottom smaller, as the keyboard will be always visible
|
|
|
|
contentPadding = PaddingValues(
|
|
|
|
bottom = dimens.spacingDefault,
|
|
|
|
start = dimens.spacingDefault,
|
|
|
|
end = dimens.spacingSmall
|
|
|
|
)
|
|
|
|
) {
|
2021-12-09 12:21:30 -08:00
|
|
|
items(it) {
|
2022-12-01 03:31:02 -08:00
|
|
|
Chip(
|
|
|
|
text = it,
|
2023-04-04 05:21:18 -07:00
|
|
|
modifier = Modifier
|
2022-12-01 03:31:02 -08:00
|
|
|
.testTag(RestoreTag.AUTOCOMPLETE_ITEM)
|
|
|
|
.clickable { onSuggestionSelected(it) }
|
|
|
|
)
|
2021-12-09 12:21:30 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Composable
|
2023-04-04 05:21:18 -07:00
|
|
|
private fun Warn(
|
|
|
|
parseResult: ParseResult,
|
|
|
|
modifier: Modifier = Modifier
|
|
|
|
) {
|
2021-12-09 12:21:30 -08:00
|
|
|
if (parseResult is ParseResult.Warn) {
|
2022-12-01 03:31:02 -08:00
|
|
|
Surface(
|
2023-04-04 05:21:18 -07:00
|
|
|
modifier = modifier,
|
2023-08-04 04:01:16 -07:00
|
|
|
shape = RectangleShape,
|
2022-12-01 03:31:02 -08:00
|
|
|
color = MaterialTheme.colorScheme.secondary,
|
|
|
|
shadowElevation = 4.dp
|
2021-12-09 12:21:30 -08:00
|
|
|
) {
|
2022-12-01 03:31:02 -08:00
|
|
|
Text(
|
|
|
|
modifier = Modifier
|
|
|
|
.fillMaxWidth()
|
2023-03-21 12:04:16 -07:00
|
|
|
.padding(dimens.spacingTiny),
|
2022-12-01 03:31:02 -08:00
|
|
|
textAlign = TextAlign.Center,
|
|
|
|
text = if (parseResult.suggestions.isEmpty()) {
|
2023-03-21 12:04:16 -07:00
|
|
|
stringResource(id = R.string.restore_seed_warning_no_suggestions)
|
2022-12-01 03:31:02 -08:00
|
|
|
} else {
|
2023-03-21 12:04:16 -07:00
|
|
|
stringResource(id = R.string.restore_seed_warning_suggestions)
|
2022-12-01 03:31:02 -08:00
|
|
|
}
|
2021-12-09 12:21:30 -08:00
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-21 12:04:16 -07:00
|
|
|
@Composable
|
2023-04-04 05:21:18 -07:00
|
|
|
@Suppress("LongMethod")
|
2023-03-21 12:04:16 -07:00
|
|
|
private fun RestoreBirthday(
|
|
|
|
zcashNetwork: ZcashNetwork,
|
|
|
|
initialRestoreHeight: BlockHeight?,
|
|
|
|
setRestoreHeight: (BlockHeight?) -> Unit,
|
|
|
|
onNext: () -> Unit,
|
|
|
|
modifier: Modifier = Modifier
|
|
|
|
) {
|
|
|
|
val (height, setHeight) = rememberSaveable {
|
|
|
|
mutableStateOf(initialRestoreHeight?.value?.toString() ?: "")
|
|
|
|
}
|
|
|
|
|
2023-04-04 05:21:18 -07:00
|
|
|
Column(
|
|
|
|
Modifier
|
|
|
|
.fillMaxHeight()
|
|
|
|
.verticalScroll(rememberScrollState())
|
2023-10-04 07:01:40 -07:00
|
|
|
.then(modifier),
|
|
|
|
horizontalAlignment = Alignment.CenterHorizontally
|
2023-04-04 05:21:18 -07:00
|
|
|
) {
|
2023-03-21 12:04:16 -07:00
|
|
|
Header(stringResource(R.string.restore_birthday_header))
|
2023-04-04 05:21:18 -07:00
|
|
|
|
|
|
|
Spacer(modifier = Modifier.height(dimens.spacingDefault))
|
|
|
|
|
2023-03-21 12:04:16 -07:00
|
|
|
Body(stringResource(R.string.restore_birthday_body))
|
2023-04-04 05:21:18 -07:00
|
|
|
|
|
|
|
Spacer(modifier = Modifier.height(dimens.spacingDefault))
|
|
|
|
|
|
|
|
FormTextField(
|
2023-03-21 12:04:16 -07:00
|
|
|
value = height,
|
|
|
|
onValueChange = { heightString ->
|
|
|
|
val filteredHeightString = heightString.filter { it.isDigit() }
|
|
|
|
setHeight(filteredHeightString)
|
|
|
|
},
|
|
|
|
Modifier
|
|
|
|
.fillMaxWidth()
|
|
|
|
.padding(dimens.spacingTiny)
|
|
|
|
.testTag(RestoreTag.BIRTHDAY_TEXT_FIELD),
|
|
|
|
label = { Text(stringResource(id = R.string.restore_birthday_hint)) },
|
|
|
|
keyboardOptions = KeyboardOptions(
|
|
|
|
KeyboardCapitalization.None,
|
|
|
|
autoCorrect = false,
|
|
|
|
imeAction = ImeAction.Done,
|
|
|
|
keyboardType = KeyboardType.Number
|
|
|
|
),
|
|
|
|
keyboardActions = KeyboardActions(onAny = {}),
|
2023-08-04 04:01:16 -07:00
|
|
|
shape = RectangleShape,
|
2023-03-21 12:04:16 -07:00
|
|
|
)
|
2023-04-04 05:21:18 -07:00
|
|
|
|
2023-03-21 12:04:16 -07:00
|
|
|
Spacer(
|
|
|
|
modifier = Modifier
|
2023-04-04 05:21:18 -07:00
|
|
|
.fillMaxHeight()
|
2023-03-21 12:04:16 -07:00
|
|
|
.weight(MINIMAL_WEIGHT)
|
|
|
|
)
|
|
|
|
|
|
|
|
val isBirthdayValid = height.toLongOrNull()?.let {
|
|
|
|
it >= zcashNetwork.saplingActivationHeight.value
|
|
|
|
} ?: false
|
|
|
|
|
|
|
|
PrimaryButton(
|
|
|
|
onClick = {
|
|
|
|
setRestoreHeight(BlockHeight.new(zcashNetwork, height.toLong()))
|
|
|
|
onNext()
|
|
|
|
},
|
|
|
|
text = stringResource(R.string.restore_birthday_button_restore),
|
2023-04-04 05:21:18 -07:00
|
|
|
enabled = isBirthdayValid,
|
|
|
|
outerPaddingValues = PaddingValues(top = dimens.spacingSmall)
|
2023-03-21 12:04:16 -07:00
|
|
|
)
|
2023-04-04 05:21:18 -07:00
|
|
|
|
2023-03-21 12:04:16 -07:00
|
|
|
TertiaryButton(
|
|
|
|
onClick = {
|
|
|
|
setRestoreHeight(null)
|
|
|
|
onNext()
|
|
|
|
},
|
2023-04-04 05:21:18 -07:00
|
|
|
text = stringResource(R.string.restore_birthday_button_skip),
|
|
|
|
outerPaddingValues = PaddingValues(top = dimens.spacingSmall)
|
2023-03-21 12:04:16 -07:00
|
|
|
)
|
2023-10-04 07:01:40 -07:00
|
|
|
|
|
|
|
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
2023-03-21 12:04:16 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-09 12:21:30 -08:00
|
|
|
@Composable
|
2023-04-04 05:21:18 -07:00
|
|
|
private fun RestoreComplete(
|
|
|
|
onComplete: () -> Unit,
|
|
|
|
modifier: Modifier = Modifier
|
|
|
|
) {
|
|
|
|
Column(
|
|
|
|
Modifier
|
|
|
|
.fillMaxHeight()
|
|
|
|
.verticalScroll(rememberScrollState())
|
2023-10-04 07:01:40 -07:00
|
|
|
.then(modifier),
|
|
|
|
horizontalAlignment = Alignment.CenterHorizontally
|
2023-04-04 05:21:18 -07:00
|
|
|
) {
|
2021-12-09 12:21:30 -08:00
|
|
|
Header(stringResource(R.string.restore_complete_header))
|
2023-04-04 05:21:18 -07:00
|
|
|
|
|
|
|
Spacer(modifier = Modifier.height(dimens.spacingDefault))
|
|
|
|
|
2021-12-09 12:21:30 -08:00
|
|
|
Body(stringResource(R.string.restore_complete_info))
|
2023-04-04 05:21:18 -07:00
|
|
|
|
|
|
|
Spacer(modifier = Modifier.height(dimens.spacingDefault))
|
|
|
|
|
2021-12-09 12:21:30 -08:00
|
|
|
Spacer(
|
|
|
|
modifier = Modifier
|
|
|
|
.fillMaxHeight()
|
|
|
|
.weight(MINIMAL_WEIGHT)
|
|
|
|
)
|
2023-04-04 05:21:18 -07:00
|
|
|
|
|
|
|
PrimaryButton(
|
|
|
|
onClick = onComplete,
|
|
|
|
text = stringResource(R.string.restore_button_see_wallet),
|
|
|
|
outerPaddingValues = PaddingValues(top = dimens.spacingSmall)
|
|
|
|
)
|
2023-10-04 07:01:40 -07:00
|
|
|
|
|
|
|
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
2021-12-09 12:21:30 -08:00
|
|
|
}
|
|
|
|
}
|