Updated on 2026-08-14
This commit is contained in:
parent
4df89bb521
commit
cf5a7d68ad
16 changed files with 406 additions and 66 deletions
|
|
@ -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 -> {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 { })
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue