Updated on 2026-08-14

This commit is contained in:
Tangem 2024-03-28 19:56:01 +00:00
parent 4df89bb521
commit cf5a7d68ad
16 changed files with 406 additions and 66 deletions

View file

@ -407,7 +407,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
private fun navigateToInitialScreenIfNeeded(intentWhichStartedActivity: Intent?) {
val backStackIsEmpty = supportFragmentManager.backStackEntryCount == 0
val isNotScannedBefore = store.state.globalState.scanResponse == null
val isOnboardingServiceNotActive = store.state.globalState.onboardingState.onboardingStarted
val isOnboardingServiceNotActive = !store.state.globalState.onboardingState.onboardingStarted
when {
!backStackIsEmpty && isNotScannedBefore && isOnboardingServiceNotActive -> {

View file

@ -106,6 +106,7 @@ class TangemSdkManager(
suspend fun importWallet(
scanResponse: ScanResponse,
mnemonic: String,
passphrase: String?,
shouldReset: Boolean,
): CompletionResult<CreateProductWalletTaskResponse> {
val defaultMnemonic = try {
@ -118,6 +119,7 @@ class TangemSdkManager(
cardTypesResolver = scanResponse.cardTypesResolver,
derivationStyleProvider = scanResponse.derivationStyleProvider,
mnemonic = defaultMnemonic,
passphrase = passphrase,
shouldReset = shouldReset,
),
scanResponse.card.cardId,

View file

@ -62,6 +62,7 @@ class CreateProductWalletTask(
private val cardTypesResolver: CardTypesResolver,
private val derivationStyleProvider: DerivationStyleProvider,
private val mnemonic: Mnemonic? = null,
private val passphrase: String? = null,
private val shouldReset: Boolean,
) : CardSessionRunnable<CreateProductWalletTaskResponse> {
@ -82,7 +83,7 @@ class CreateProductWalletTask(
cardTypesResolver.isTangemTwins() ->
throw UnsupportedOperationException("Use the TwinCardsManager to create a wallet")
else -> CreateWalletTangemWallet(mnemonic, shouldReset, derivationStyleProvider, cardDto)
else -> CreateWalletTangemWallet(mnemonic, passphrase, shouldReset, derivationStyleProvider, cardDto)
}
commandProcessor.proceed(cardDto, session) {
when (it) {
@ -142,6 +143,7 @@ private class CreateWalletTangemNote(private val cardTypesResolver: CardTypesRes
*/
private class CreateWalletTangemWallet(
private val mnemonic: Mnemonic?,
private val passphrase: String?,
private val shouldReset: Boolean,
private val derivationStyleProvider: DerivationStyleProvider,
cardDTO: CardDTO,
@ -170,7 +172,7 @@ private class CreateWalletTangemWallet(
session: CardSession,
callback: (result: CompletionResult<CreateProductWalletTaskResponse>) -> Unit,
) {
CreateWalletsTask(cardConfig.mandatoryCurves, mnemonic).run(session) { result ->
CreateWalletsTask(cardConfig.mandatoryCurves, mnemonic, passphrase).run(session) { result ->
when (result) {
is CompletionResult.Success -> {
checkIfAllWalletsCreated(card, session, result.data, callback)

View file

@ -21,6 +21,7 @@ class CreateWalletsResponse(
class CreateWalletsTask(
private val curves: List<EllipticCurve>,
private val mnemonic: Mnemonic? = null,
private val passphrase: String?,
) : CardSessionRunnable<CreateWalletsResponse> {
private val createdWalletsResponses = mutableListOf<CreateWalletResponse>()
@ -41,7 +42,7 @@ class CreateWalletsTask(
callback: (result: CompletionResult<CreateWalletsResponse>) -> Unit,
) {
val extendedPrivateKey = mnemonic?.let {
AnyMasterKeyFactory(mnemonic = it, passphrase = "").makeMasterKey(curve)
AnyMasterKeyFactory(mnemonic = it, passphrase = passphrase ?: "").makeMasterKey(curve)
}
CreateWalletTask(curve, extendedPrivateKey).run(session) { result ->
when (result) {

View file

@ -38,6 +38,7 @@ sealed class OnboardingWallet2Action : OnboardingWalletAction() {
data class ImportWallet(
val mnemonicComponents: List<String>,
val passphrase: String?,
val seedPhraseSource: SeedPhraseSource,
val callback: (CompletionResult<CreateWalletResponse>) -> Unit,
) : OnboardingWallet2Action()

View file

@ -271,6 +271,7 @@ private fun handleWallet2Action(action: OnboardingWallet2Action) {
val mediateResult = when (
val result = tangemSdkManager.importWallet(
scanResponse = scanResponse,
passphrase = action.passphrase,
mnemonic = action.mnemonicComponents.joinToString(" "),
shouldReset = globalState.onboardingState.shouldResetOnCreate,
)

View file

@ -523,10 +523,18 @@ class OnboardingWalletFragment :
override fun importWallet(
mnemonicComponents: List<String>,
passphrase: String?,
seedPhraseSource: SeedPhraseSource,
callback: (CompletionResult<CreateWalletResponse>) -> Unit,
) {
store.dispatch(OnboardingWallet2Action.ImportWallet(mnemonicComponents, seedPhraseSource, callback))
store.dispatch(
OnboardingWallet2Action.ImportWallet(
mnemonicComponents,
passphrase,
seedPhraseSource,
callback,
),
)
}
override fun allowScreenshots(allow: Boolean) {

View file

@ -1,5 +1,6 @@
package com.tangem.core.ui.components
import androidx.annotation.DrawableRes
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.tween
@ -59,6 +60,40 @@ fun OutlineTextField(
)
}
@Composable
fun OutlineTextFieldWithIcon(
value: TextFieldValue,
onValueChange: (TextFieldValue) -> Unit,
@DrawableRes iconResId: Int,
iconColor: Color,
onIconClick: () -> Unit,
modifier: Modifier = Modifier,
label: String? = null,
placeholder: String? = null,
enabled: Boolean = true,
isError: Boolean = false,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions(),
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
) {
TangemTextFieldWithIcon(
modifier = modifier,
value = value,
onValueChange = onValueChange,
iconResId = iconResId,
iconColor = iconColor,
singleLine = true,
label = label,
placeholder = placeholder,
enabled = enabled,
isError = isError,
keyboardOptions = keyboardOptions,
keyboardActions = keyboardActions,
interactionSource = interactionSource,
onIconClick = onIconClick,
)
}
// region Defaults
@Suppress("LongMethod")
@Composable
@ -168,6 +203,84 @@ private fun TangemTextField(
}
}
@Suppress("LongMethod")
@Composable
private fun TangemTextFieldWithIcon(
value: TextFieldValue,
singleLine: Boolean,
onValueChange: (TextFieldValue) -> Unit,
@DrawableRes iconResId: Int,
iconColor: Color,
modifier: Modifier = Modifier,
label: String? = null,
placeholder: String? = null,
enabled: Boolean = true,
isError: Boolean = false,
visualTransformation: VisualTransformation = VisualTransformation.None,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions(),
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
colors: TangemTextFieldColors = TangemTextFieldsDefault.defaultTextFieldColors,
size: TangemTextFieldSize = TangemTextFieldSize.Default,
onIconClick: () -> Unit = {},
) {
Column(modifier = modifier) {
OutlinedTextField(
modifier = Modifier
.fillMaxWidth()
.height(IntrinsicSize.Min)
.heightIn(size.toHeightDp()),
value = value,
textStyle = TangemTheme.typography.body1,
onValueChange = onValueChange,
singleLine = singleLine,
enabled = enabled,
isError = isError,
visualTransformation = visualTransformation,
keyboardActions = keyboardActions,
keyboardOptions = keyboardOptions,
interactionSource = interactionSource,
shape = size.toShape(),
colors = colors,
label = {
if (!label.isNullOrEmpty()) {
Text(
text = label,
style = TangemTheme.typography.caption2,
color = colors.labelColor(
enabled = enabled,
error = isError,
interactionSource = interactionSource,
).value,
)
}
},
placeholder = {
if (!placeholder.isNullOrEmpty()) {
Text(
text = placeholder,
style = TangemTheme.typography.body1,
color = colors.placeholderColor(enabled = enabled).value,
)
}
},
trailingIcon = {
IconButton(
modifier = Modifier.size(32.dp),
onClick = onIconClick,
) {
Icon(
modifier = Modifier.size(24.dp),
painter = painterResource(id = iconResId),
tint = iconColor,
contentDescription = "Clear input",
)
}
},
)
}
}
private enum class TangemTextFieldSize {
Default,
}
@ -378,6 +491,16 @@ private fun OutlineTextFieldSample(modifier: Modifier = Modifier) {
caption = "Supporting text",
)
Divider(modifier = Modifier.padding(vertical = 8.dp))
OutlineTextFieldWithIcon(
value = TextFieldValue(),
onValueChange = { /* no-op */ },
iconResId = R.drawable.ic_alert_24,
iconColor = TangemTheme.colors.icon.informative,
label = "Default without value",
placeholder = "Placeholder",
onIconClick = {},
)
Divider(modifier = Modifier.padding(vertical = 8.dp))
}
}

View file

@ -1,6 +1,8 @@
package com.tangem.feature.onboarding.presentation.wallet2.model
import androidx.compose.ui.text.input.TextFieldValue
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.feature.onboarding.domain.SeedPhraseError
import com.tangem.feature.onboarding.domain.models.MnemonicType
import kotlinx.collections.immutable.ImmutableList
@ -64,11 +66,6 @@ enum class SegmentSeedType(val count: Int) {
}
}
data class MnemonicState(
val seedType: SegmentSeedType,
val mnemonicItems: PersistentList<MnemonicGridItem>,
)
data class MnemonicGridItem(
val index: Int,
val mnemonic: String,
@ -82,14 +79,19 @@ data class CheckSeedPhraseState(
)
data class ImportSeedPhraseState(
val tvSeedPhrase: TextFieldState,
val fieldSeedPhrase: TextFieldState,
val fieldPassphrase: TextFieldState,
val onSuggestedPhraseClick: (Int) -> Unit,
val onPassphraseInfoClick: () -> Unit,
val buttonCreateWallet: ButtonState,
val invalidWords: Set<String> = emptySet(),
val suggestionsList: ImmutableList<String> = persistentListOf(),
val error: SeedPhraseError? = null,
val bottomSheetConfig: TangemBottomSheetConfig? = null,
)
data class ShowPassphraseInfoBottomSheetContent(val onOkClick: () -> Unit) : TangemBottomSheetConfigContent
data class TextFieldState(
val onTextFieldValueChanged: (TextFieldValue) -> Unit,
val textFieldValue: TextFieldValue = TextFieldValue(),

View file

@ -40,7 +40,9 @@ data class CheckSeedPhraseUiAction(
data class ImportSeedPhraseUiAction(
val phraseTextFieldAction: TextFieldUiAction,
val passTextFieldAction: TextFieldUiAction,
val suggestedPhraseClick: (Int) -> Unit,
val onPassphraseInfoClick: () -> Unit,
val buttonCreateWalletClick: () -> Unit,
)

View file

@ -1,13 +1,16 @@
package com.tangem.feature.onboarding.presentation.wallet2.ui
import androidx.compose.animation.*
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
@ -17,9 +20,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import com.tangem.core.ui.components.Notifier
import com.tangem.core.ui.components.PrimaryButtonIconEnd
import com.tangem.core.ui.components.TangemTextFieldsDefault
import com.tangem.core.ui.components.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.onboarding.R
import com.tangem.feature.onboarding.presentation.wallet2.model.ButtonState
@ -38,33 +39,31 @@ import kotlinx.collections.immutable.persistentListOf
*/
@Composable
fun ImportSeedPhraseScreen(state: ImportSeedPhraseState, modifier: Modifier = Modifier) {
Column(
val keyboard by keyboardAsState()
Box(
modifier = modifier.fillMaxSize(),
) {
Box(
modifier = Modifier.weight(1f),
) {
Column {
OnboardingDescriptionBlock(
modifier = Modifier.padding(top = TangemTheme.dimens.size16),
) {
DescriptionSubTitleText(text = stringResource(id = R.string.onboarding_seed_import_message))
}
PhraseBlock(
modifier = Modifier
.padding(top = TangemTheme.dimens.size62)
.padding(horizontal = TangemTheme.dimens.size16),
state = state,
)
SuggestionsBlock(
modifier = Modifier.padding(top = TangemTheme.dimens.size4),
suggestionsList = state.suggestionsList,
onClick = { index -> state.onSuggestedPhraseClick(index) },
)
Column {
OnboardingDescriptionBlock(
modifier = Modifier.padding(top = TangemTheme.dimens.size16),
) {
DescriptionSubTitleText(text = stringResource(id = R.string.onboarding_seed_import_message))
}
}
Box {
PhraseBlock(
modifier = Modifier
.padding(top = TangemTheme.dimens.size62)
.padding(horizontal = TangemTheme.dimens.size16),
state = state,
)
PassphraseBlock(
modifier = Modifier.padding(horizontal = TangemTheme.dimens.size16),
passphraseField = state.fieldPassphrase,
onPassphraseInfoClick = state.onPassphraseInfoClick,
)
OnboardingActionBlock(
modifier = Modifier.padding(top = TangemTheme.dimens.size16),
firstActionContent = {
PrimaryButtonIconEnd(
modifier = Modifier
@ -78,9 +77,41 @@ fun ImportSeedPhraseScreen(state: ImportSeedPhraseState, modifier: Modifier = Mo
},
)
}
if (keyboard is Keyboard.Opened) {
SuggestionsBlock(
modifier = Modifier
.align(Alignment.BottomCenter)
.imePadding(),
suggestionsList = state.suggestionsList,
onClick = { index -> state.onSuggestedPhraseClick(index) },
)
}
if (state.bottomSheetConfig != null) {
PassphraseInfoBottomSheet(config = state.bottomSheetConfig)
}
}
}
@Composable
private fun PassphraseBlock(
passphraseField: TextFieldState,
onPassphraseInfoClick: () -> Unit,
modifier: Modifier = Modifier,
) {
OutlineTextFieldWithIcon(
modifier = modifier.fillMaxWidth(),
value = passphraseField.textFieldValue,
onValueChange = passphraseField.onTextFieldValueChanged,
iconResId = R.drawable.ic_information_24,
iconColor = TangemTheme.colors.icon.informative,
label = stringResource(id = R.string.common_passphrase),
placeholder = stringResource(id = R.string.send_optional_field),
onIconClick = onPassphraseInfoClick,
)
}
@Composable
private fun PhraseBlock(state: ImportSeedPhraseState, modifier: Modifier = Modifier) {
// TODO: replace by TextReference
@ -93,8 +124,8 @@ private fun PhraseBlock(state: ImportSeedPhraseState, modifier: Modifier = Modif
modifier = Modifier
.fillMaxWidth()
.height(TangemTheme.dimens.size142),
value = state.tvSeedPhrase.textFieldValue,
onValueChange = state.tvSeedPhrase.onTextFieldValueChanged,
value = state.fieldSeedPhrase.textFieldValue,
onValueChange = state.fieldSeedPhrase.onTextFieldValueChanged,
textStyle = TangemTheme.typography.body1,
singleLine = false,
visualTransformation = InvalidWordsColorTransformation(
@ -131,12 +162,12 @@ private fun SuggestionsBlock(
modifier: Modifier = Modifier,
) {
AnimatedVisibility(
modifier = modifier,
enter = fadeIn() + slideIn(initialOffset = { IntOffset(x = 200, y = 0) }),
exit = slideOut(targetOffset = { IntOffset(x = -200, y = 0) }) + fadeOut(),
visible = suggestionsList.isNotEmpty(),
) {
LazyRow(
modifier = modifier,
contentPadding = PaddingValues(horizontal = TangemTheme.dimens.size16),
) {
items(suggestionsList.size) { index ->
@ -160,7 +191,7 @@ private fun SuggestionButton(text: String, onClick: () -> Unit, modifier: Modifi
Box(modifier = modifier.clickable(onClick = onClick)) {
Notifier(
text = text,
backgroundColor = TangemTheme.colors.background.action,
backgroundColor = TangemTheme.colors.icon.primary1,
textColor = TangemTheme.colors.text.primary2,
)
}
@ -204,19 +235,23 @@ private fun ImportSeedPhraseScreenPreview_Light(
@PreviewParameter(SuggestionsPreviewParamsProvider::class) suggestions: ImmutableList<String>,
) {
TangemTheme(isDark = false) {
ImportSeedPhraseScreen(
ImportSeedPhraseState(
tvSeedPhrase = TextFieldState(onTextFieldValueChanged = {}),
onSuggestedPhraseClick = {},
buttonCreateWallet = ButtonState(
enabled = true,
isClickable = true,
showProgress = false,
onClick = {},
Box(modifier = Modifier.background(TangemTheme.colors.background.primary)) {
ImportSeedPhraseScreen(
ImportSeedPhraseState(
fieldSeedPhrase = TextFieldState(onTextFieldValueChanged = {}),
fieldPassphrase = TextFieldState(onTextFieldValueChanged = {}),
onSuggestedPhraseClick = {},
onPassphraseInfoClick = {},
buttonCreateWallet = ButtonState(
enabled = true,
isClickable = true,
showProgress = false,
onClick = {},
),
suggestionsList = suggestions,
),
suggestionsList = suggestions,
),
)
)
}
}
}

View file

@ -0,0 +1,89 @@
package com.tangem.feature.onboarding.presentation.wallet2.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.onboarding.R
import com.tangem.feature.onboarding.presentation.wallet2.model.ShowPassphraseInfoBottomSheetContent
@Composable
fun PassphraseInfoBottomSheet(config: TangemBottomSheetConfig) {
TangemBottomSheet(
config = config,
containerColor = TangemTheme.colors.background.primary,
) { content: ShowPassphraseInfoBottomSheetContent ->
PassphraseInfoBottomSheetContent(content)
}
}
@Composable
fun PassphraseInfoBottomSheetContent(content: ShowPassphraseInfoBottomSheetContent) {
Column(
modifier = Modifier
.background(color = TangemTheme.colors.background.primary)
.fillMaxWidth(),
) {
Icon(
modifier = Modifier
.align(Alignment.CenterHorizontally)
.padding(top = TangemTheme.dimens.size40)
.size(TangemTheme.dimens.size48),
painter = painterResource(id = R.drawable.ic_information_24),
tint = TangemTheme.colors.icon.accent,
contentDescription = null,
)
Text(
text = stringResource(id = R.string.common_passphrase),
modifier = Modifier
.padding(top = TangemTheme.dimens.size40)
.align(Alignment.CenterHorizontally),
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.h2,
)
Text(
text = stringResource(id = R.string.onboarding_bottom_sheet_passphrase_description),
modifier = Modifier
.padding(top = TangemTheme.dimens.size16)
.padding(horizontal = TangemTheme.dimens.size24)
.align(Alignment.CenterHorizontally),
color = TangemTheme.colors.text.secondary,
style = TangemTheme.typography.body2,
textAlign = TextAlign.Center,
)
PrimaryButton(
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.size16)
.padding(top = TangemTheme.dimens.size40)
.padding(bottom = TangemTheme.dimens.size32)
.fillMaxWidth(),
text = stringResource(id = R.string.common_ok),
onClick = content.onOkClick,
)
}
}
@Preview
@Composable
private fun PassphraseInfoBottomSheetContentPreview() {
TangemTheme {
PassphraseInfoBottomSheetContent(ShowPassphraseInfoBottomSheetContent { })
}
}

View file

@ -2,9 +2,11 @@ package com.tangem.feature.onboarding.presentation.wallet2.ui.stateBuiders
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.TextFieldValue
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.feature.onboarding.domain.InsertSuggestionResult
import com.tangem.feature.onboarding.domain.SeedPhraseError
import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingSeedPhraseState
import com.tangem.feature.onboarding.presentation.wallet2.model.ShowPassphraseInfoBottomSheetContent
import kotlinx.collections.immutable.ImmutableList
/**
@ -12,12 +14,23 @@ import kotlinx.collections.immutable.ImmutableList
*/
class ImportSeedPhraseStateBuilder {
fun updateTextField(
fun updateSeedPhraseTextField(
uiState: OnboardingSeedPhraseState,
textFieldValue: TextFieldValue,
): OnboardingSeedPhraseState = uiState.copy(
importSeedPhraseState = uiState.importSeedPhraseState.copy(
tvSeedPhrase = uiState.importSeedPhraseState.tvSeedPhrase.copy(
fieldSeedPhrase = uiState.importSeedPhraseState.fieldSeedPhrase.copy(
textFieldValue = textFieldValue,
),
),
)
fun updatePassPhraseTextField(
uiState: OnboardingSeedPhraseState,
textFieldValue: TextFieldValue,
): OnboardingSeedPhraseState = uiState.copy(
importSeedPhraseState = uiState.importSeedPhraseState.copy(
fieldPassphrase = uiState.importSeedPhraseState.fieldPassphrase.copy(
textFieldValue = textFieldValue,
),
),
@ -49,15 +62,40 @@ class ImportSeedPhraseStateBuilder {
),
)
fun showPassphraseInfoBottomSheet(
uiState: OnboardingSeedPhraseState,
onDismiss: () -> Unit,
): OnboardingSeedPhraseState {
return uiState.copy(
importSeedPhraseState = uiState.importSeedPhraseState.copy(
bottomSheetConfig = TangemBottomSheetConfig(
isShow = true,
onDismissRequest = onDismiss,
content = ShowPassphraseInfoBottomSheetContent(onDismiss),
),
),
)
}
fun dismissPassphraseBottomSheet(uiState: OnboardingSeedPhraseState): OnboardingSeedPhraseState {
return uiState.copy(
importSeedPhraseState = uiState.importSeedPhraseState.copy(
bottomSheetConfig = uiState.importSeedPhraseState.bottomSheetConfig?.copy(
isShow = false,
),
),
)
}
fun insertSuggestionWord(
uiState: OnboardingSeedPhraseState,
insertResult: InsertSuggestionResult,
): OnboardingSeedPhraseState {
val newTextFieldValue = uiState.importSeedPhraseState.tvSeedPhrase.textFieldValue.copy(
val newTextFieldValue = uiState.importSeedPhraseState.fieldSeedPhrase.textFieldValue.copy(
text = insertResult.text,
selection = TextRange(insertResult.cursorPosition, insertResult.cursorPosition),
)
return updateTextField(uiState, newTextFieldValue)
return updateSeedPhraseTextField(uiState, newTextFieldValue)
}
fun updateError(uiState: OnboardingSeedPhraseState, error: SeedPhraseError?): OnboardingSeedPhraseState {

View file

@ -28,11 +28,16 @@ class StateBuilder(
private fun getInitialImportSeedPhraseState(): ImportSeedPhraseState {
return ImportSeedPhraseState(
tvSeedPhrase = TextFieldState(
fieldSeedPhrase = TextFieldState(
onTextFieldValueChanged = uiActions.importSeedPhraseActions.phraseTextFieldAction.onTextFieldChanged,
onFocusChanged = uiActions.importSeedPhraseActions.phraseTextFieldAction.onFocusChanged,
),
fieldPassphrase = TextFieldState(
onTextFieldValueChanged = uiActions.importSeedPhraseActions.passTextFieldAction.onTextFieldChanged,
onFocusChanged = uiActions.importSeedPhraseActions.passTextFieldAction.onFocusChanged,
),
onSuggestedPhraseClick = uiActions.importSeedPhraseActions.suggestedPhraseClick,
onPassphraseInfoClick = uiActions.importSeedPhraseActions.onPassphraseInfoClick,
buttonCreateWallet = ButtonState(
enabled = false,
onClick = uiActions.importSeedPhraseActions.buttonCreateWalletClick,

View file

@ -11,6 +11,7 @@ interface SeedPhraseMediator {
fun createWallet(callback: (CompletionResult<CreateWalletResponse>) -> Unit)
fun importWallet(
mnemonicComponents: List<String>,
passphrase: String?,
seedPhraseSource: SeedPhraseSource,
callback: (CompletionResult<CreateWalletResponse>) -> Unit,
)

View file

@ -74,6 +74,7 @@ class SeedPhraseViewModel @Inject constructor(
private var generatedMnemonicComponents: Map<MnemonicType, List<String>> = emptyMap()
private var importedMnemonicComponents: List<String>? = null
private var importedPassphrase: String? = null
override fun onCleared() {
textFieldsDebouncers.forEach { entry -> entry.value.release() }
@ -143,7 +144,7 @@ class SeedPhraseViewModel @Inject constructor(
buttonCreateWalletClick = {
val mnemonicComponents =
generatedMnemonicComponents[selectedMnemonicType] ?: return@CheckSeedPhraseUiAction
buttonImportWalletClick(mnemonicComponents, SeedPhraseSource.GENERATED)
buttonImportWalletClick(mnemonicComponents, null, SeedPhraseSource.GENERATED)
},
secondTextFieldAction = TextFieldUiAction(
onTextFieldChanged = { value -> onTextFieldChanged(SeedPhraseField.Second, value) },
@ -159,10 +160,14 @@ class SeedPhraseViewModel @Inject constructor(
phraseTextFieldAction = TextFieldUiAction(
onTextFieldChanged = { value -> onSeedPhraseTextFieldChanged(value) },
),
passTextFieldAction = TextFieldUiAction(
onTextFieldChanged = { value -> onPassPhraseTextFieldChanged(value) },
),
suggestedPhraseClick = ::buttonSuggestedPhraseClick,
onPassphraseInfoClick = ::buttonPassphraseInfoClick,
buttonCreateWalletClick = {
analyticsEventHandler.send(SeedPhraseEvents.ButtonImport)
buttonImportWalletClick(importedMnemonicComponents, SeedPhraseSource.IMPORTED)
buttonImportWalletClick(importedMnemonicComponents, importedPassphrase, SeedPhraseSource.IMPORTED)
},
),
menuChatClick = ::menuChatClick,
@ -208,13 +213,13 @@ class SeedPhraseViewModel @Inject constructor(
// region ImportSeedPhrase
private fun onSeedPhraseTextFieldChanged(textFieldValue: TextFieldValue) {
val oldTextFieldValue = uiState.importSeedPhraseState.tvSeedPhrase.textFieldValue
val oldTextFieldValue = uiState.importSeedPhraseState.fieldSeedPhrase.textFieldValue
val isSameText = textFieldValue.text == oldTextFieldValue.text
val isCursorMoved = textFieldValue.selection != oldTextFieldValue.selection
uiState = uiBuilder.importSeedPhrase.updateTextField(uiState, textFieldValue)
uiState = uiBuilder.importSeedPhrase.updateSeedPhraseTextField(uiState, textFieldValue)
val fieldState = uiState.importSeedPhraseState.tvSeedPhrase
val fieldState = uiState.importSeedPhraseState.fieldSeedPhrase
val inputMnemonic = fieldState.textFieldValue.text
val debouncer = createOrGetDebouncer(MNEMONIC_DEBOUNCER)
@ -236,6 +241,11 @@ class SeedPhraseViewModel @Inject constructor(
}
}
private fun onPassPhraseTextFieldChanged(textFieldValue: TextFieldValue) {
importedPassphrase = textFieldValue.text
uiState = uiBuilder.importSeedPhrase.updatePassPhraseTextField(uiState, textFieldValue)
}
private suspend fun validateMnemonic(inputMnemonic: String) {
if (inputMnemonic.isEmpty() && uiState.importSeedPhraseState.error != null) {
val mediateState = uiBuilder.importSeedPhrase.updateCreateWalletButton(uiState, enabled = false)
@ -284,11 +294,15 @@ class SeedPhraseViewModel @Inject constructor(
}
}
private fun buttonImportWalletClick(mnemonicComponents: List<String>?, seedPhraseSource: SeedPhraseSource) {
private fun buttonImportWalletClick(
mnemonicComponents: List<String>?,
passphrase: String?,
seedPhraseSource: SeedPhraseSource,
) {
mnemonicComponents ?: return
viewModelScope.launch(dispatchers.io) {
mediator.importWallet(mnemonicComponents, seedPhraseSource, ::handleWalletCreationResult)
mediator.importWallet(mnemonicComponents, passphrase, seedPhraseSource, ::handleWalletCreationResult)
}
}
@ -391,7 +405,7 @@ class SeedPhraseViewModel @Inject constructor(
private fun buttonSuggestedPhraseClick(suggestionIndex: Int) {
launchSingle {
val textFieldValue = uiState.importSeedPhraseState.tvSeedPhrase.textFieldValue
val textFieldValue = uiState.importSeedPhraseState.fieldSeedPhrase.textFieldValue
val word = uiState.importSeedPhraseState.suggestionsList[suggestionIndex]
val cursorPosition = textFieldValue.selection.end
@ -406,6 +420,22 @@ class SeedPhraseViewModel @Inject constructor(
}
}
}
private fun buttonPassphraseInfoClick() {
launchSingle {
updateUi {
uiBuilder.importSeedPhrase.showPassphraseInfoBottomSheet(uiState) {
dismissPassphraseBottomSheet()
}
}
}
}
private fun dismissPassphraseBottomSheet() {
launchSingle {
updateUi { uiBuilder.importSeedPhrase.dismissPassphraseBottomSheet(uiState) }
}
}
// endregion ButtonClickHandlers
// region Utils