diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index c1d5853c80..d5d7000089 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -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 -> { diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index 548f91c4ad..4097061289 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -106,6 +106,7 @@ class TangemSdkManager( suspend fun importWallet( scanResponse: ScanResponse, mnemonic: String, + passphrase: String?, shouldReset: Boolean, ): CompletionResult { val defaultMnemonic = try { @@ -118,6 +119,7 @@ class TangemSdkManager( cardTypesResolver = scanResponse.cardTypesResolver, derivationStyleProvider = scanResponse.derivationStyleProvider, mnemonic = defaultMnemonic, + passphrase = passphrase, shouldReset = shouldReset, ), scanResponse.card.cardId, diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt index f5c1a21f38..699e7a527e 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt @@ -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 { @@ -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) -> 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) diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateWalletsTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateWalletsTask.kt index 2c75d8da36..c0ddcdeea3 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateWalletsTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateWalletsTask.kt @@ -21,6 +21,7 @@ class CreateWalletsResponse( class CreateWalletsTask( private val curves: List, private val mnemonic: Mnemonic? = null, + private val passphrase: String?, ) : CardSessionRunnable { private val createdWalletsResponses = mutableListOf() @@ -41,7 +42,7 @@ class CreateWalletsTask( callback: (result: CompletionResult) -> 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) { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt index 5bd2df0285..c624e36825 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt @@ -38,6 +38,7 @@ sealed class OnboardingWallet2Action : OnboardingWalletAction() { data class ImportWallet( val mnemonicComponents: List, + val passphrase: String?, val seedPhraseSource: SeedPhraseSource, val callback: (CompletionResult) -> Unit, ) : OnboardingWallet2Action() diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index 16045b38c8..5c29c96fb9 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -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, ) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt index 0655d01aa9..deb5bb4873 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt @@ -523,10 +523,18 @@ class OnboardingWalletFragment : override fun importWallet( mnemonicComponents: List, + passphrase: String?, seedPhraseSource: SeedPhraseSource, callback: (CompletionResult) -> Unit, ) { - store.dispatch(OnboardingWallet2Action.ImportWallet(mnemonicComponents, seedPhraseSource, callback)) + store.dispatch( + OnboardingWallet2Action.ImportWallet( + mnemonicComponents, + passphrase, + seedPhraseSource, + callback, + ), + ) } override fun allowScreenshots(allow: Boolean) { diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt b/core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt index e954cbd32d..f8312aa659 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt @@ -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)) } } diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingSeedPhraseState.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingSeedPhraseState.kt index 5591aa1013..a8300c9a76 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingSeedPhraseState.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingSeedPhraseState.kt @@ -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, -) - 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 = emptySet(), val suggestionsList: ImmutableList = 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(), diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/UiActions.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/UiActions.kt index afb379bdca..91db0f6407 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/UiActions.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/UiActions.kt @@ -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, ) diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt index e0a5b674ad..abc2bfff70 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt @@ -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, ) { 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, - ), - ) + ) + } } } diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/PassphraseInfoBottomSheet.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/PassphraseInfoBottomSheet.kt new file mode 100644 index 0000000000..ee04bfe228 --- /dev/null +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/PassphraseInfoBottomSheet.kt @@ -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 { }) + } +} \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/ImportSeedPhraseStateBuilder.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/ImportSeedPhraseStateBuilder.kt index 794ec1d599..56169d7fa1 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/ImportSeedPhraseStateBuilder.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/ImportSeedPhraseStateBuilder.kt @@ -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 { diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/StateBuilder.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/StateBuilder.kt index 749d4be6a0..3a27500c46 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/StateBuilder.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/StateBuilder.kt @@ -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, diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseMediator.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseMediator.kt index daf7815eb8..5848187991 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseMediator.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseMediator.kt @@ -11,6 +11,7 @@ interface SeedPhraseMediator { fun createWallet(callback: (CompletionResult) -> Unit) fun importWallet( mnemonicComponents: List, + passphrase: String?, seedPhraseSource: SeedPhraseSource, callback: (CompletionResult) -> Unit, ) diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt index ed20db4319..74dba8d22c 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt @@ -74,6 +74,7 @@ class SeedPhraseViewModel @Inject constructor( private var generatedMnemonicComponents: Map> = emptyMap() private var importedMnemonicComponents: List? = 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?, seedPhraseSource: SeedPhraseSource) { + private fun buttonImportWalletClick( + mnemonicComponents: List?, + 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