Updated on 2026-08-14

This commit is contained in:
Tangem 2024-11-21 17:49:53 +03:00
parent 5a6dfd50e4
commit c6497a0abc
19 changed files with 448 additions and 54 deletions

View file

@ -4,8 +4,36 @@
<inspection_tool class="CanBeParameter" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="CanBePrimaryConstructorProperty" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="ComplexRedundantLet" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="ComposePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="ConstantConditionIf" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="ExplicitThis" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="GlancePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="KotlinDoubleNegation" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="KotlinRedundantOverride" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="KotlinUnusedImport" enabled="true" level="ERROR" enabled_by_default="true" />
@ -19,6 +47,10 @@
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewDeviceShouldUseNewSpec" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />

View file

@ -25,6 +25,9 @@ dependencies {
implementation(projects.core.decompose)
implementation(projects.core.navigation)
/** Common */
implementation(projects.common.ui)
/** Domain */
implementation(projects.domain.models)
implementation(projects.domain.feedback)
@ -44,7 +47,7 @@ dependencies {
implementation(deps.lifecycle.runtime.ktx)
/** Compose libraries */
implementation(deps.compose.material) // to use buttons
implementation(deps.compose.material) // to use buttons and text field in MultiWalletSeedPhraseImport.kt
implementation(deps.compose.material3)
implementation(deps.compose.animation)
implementation(deps.compose.foundation)

View file

@ -15,7 +15,7 @@ import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.ui.
import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletState
import kotlinx.coroutines.launch
class MultiWalletCreateWalletComponent(
internal class MultiWalletCreateWalletComponent(
context: AppComponentContext,
params: MultiWalletChildParams,
onNextStep: (OnboardingMultiWalletState.Step) -> Unit,

View file

@ -13,6 +13,7 @@ import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.features.onboarding.v2.impl.R
import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams
import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.ui.state.MultiWalletCreateWalletUM
import com.tangem.features.onboarding.v2.multiwallet.impl.common.ui.resetCardDialog
import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletState
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -24,7 +25,7 @@ import kotlinx.coroutines.launch
import javax.inject.Inject
@ComponentScoped
class MultiWalletCreateWalletModel @Inject constructor(
internal class MultiWalletCreateWalletModel @Inject constructor(
paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
private val tangemSdkManager: TangemSdkManager,

View file

@ -21,14 +21,14 @@ import com.tangem.features.onboarding.v2.impl.R
import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.ui.state.MultiWalletCreateWalletUM
@Composable
fun MultiWalletCreateWallet(state: MultiWalletCreateWalletUM, modifier: Modifier = Modifier) {
internal fun MultiWalletCreateWallet(state: MultiWalletCreateWalletUM, modifier: Modifier = Modifier) {
if (state.dialog != null) {
BasicDialog(
title = state.dialog.title.resolveReference(),
message = state.dialog.description.resolveReference(),
message = state.dialog.message.resolveReference(),
confirmButton = DialogButtonUM(
title = state.dialog.confirmButtonText.resolveReference(),
onClick = state.dialog.onConfirm,
onClick = state.dialog.onConfirmClick,
),
dismissButton = DialogButtonUM(
title = state.dialog.dismissButtonText.resolveReference(),

View file

@ -1,23 +1,13 @@
package com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.ui.state
import com.tangem.core.ui.extensions.TextReference
import com.tangem.features.onboarding.v2.multiwallet.impl.common.ui.OnboardingDialogUM
data class MultiWalletCreateWalletUM(
internal data class MultiWalletCreateWalletUM(
val title: TextReference,
val bodyText: TextReference,
val showOtherOptionsButton: Boolean,
val onCreateWalletClick: () -> Unit,
val onOtherOptionsClick: () -> Unit,
val dialog: Dialog?,
) {
data class Dialog(
val title: TextReference,
val description: TextReference,
val dismissButtonText: TextReference,
val confirmButtonText: TextReference,
val dismissWarningColor: Boolean = false,
val onConfirm: () -> Unit,
val onDismissButtonClick: () -> Unit,
val onDismiss: () -> Unit,
val dialog: OnboardingDialogUM?,
)
}

View file

@ -1,9 +1,15 @@
package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase
import android.app.Activity
import android.content.Context
import android.content.ContextWrapper
import android.view.WindowManager
import androidx.activity.compose.BackHandler
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
@ -16,20 +22,24 @@ import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.launch
@Suppress("UnusedPrivateMember")
class MultiWalletSeedPhraseComponent(
internal class MultiWalletSeedPhraseComponent(
context: AppComponentContext,
params: MultiWalletChildParams,
onNextStep: (OnboardingMultiWalletState.Step) -> Unit,
backButtonClickFlow: SharedFlow<Unit>,
) : AppComponentContext by context, MultiWalletChildComponent {
private val model: MultiWalletSeedPhraseModel = getOrCreateModel()
private val model: MultiWalletSeedPhraseModel = getOrCreateModel(params)
init {
componentScope.launch {
backButtonClickFlow.collect {
model.onBack()
backButtonClickFlow.collect { model.onBack() }
}
componentScope.launch {
model.onDone.collect { onNextStep(OnboardingMultiWalletState.Step.AddBackupDevice) }
}
componentScope.launch {
model.navigateBack.collect { onNextStep(OnboardingMultiWalletState.Step.CreateWallet) }
}
}
@ -41,9 +51,30 @@ class MultiWalletSeedPhraseComponent(
model.onBack()
}
// disable screenshots
val activity = LocalContext.current.findActivity()
DisposableEffect(activity) {
activity.window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE,
)
onDispose {
activity.window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
}
}
MultiWalletSeedPhrase(
modifier = modifier,
state = state,
)
}
private fun Context.findActivity(): Activity {
var context = this
while (context is ContextWrapper) {
if (context is Activity) return context
context = context.baseContext
}
error("")
}
}

View file

@ -1,46 +1,78 @@
package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemSdkError
import com.tangem.core.decompose.di.ComponentScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.domain.card.repository.CardRepository
import com.tangem.domain.feedback.GetCardInfoUseCase
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model.builder.GenerateSeedPhraseUiStateBuilder
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model.builder.ImportSeedPhraseUiStateBuilder
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model.builder.SeedPhraseCheckUiStateBuilder
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.state.MultiWalletSeedPhraseUM
import com.tangem.features.onboarding.v2.multiwallet.impl.common.ui.OnboardingDialogUM
import com.tangem.features.onboarding.v2.multiwallet.impl.common.ui.resetCardDialog
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import javax.inject.Inject
@Suppress("LongParameterList")
@ComponentScoped
class MultiWalletSeedPhraseModel @Inject constructor(
internal class MultiWalletSeedPhraseModel @Inject constructor(
paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
private val mnemonicRepository: MnemonicRepository,
private val urlOpener: UrlOpener,
private val tangemSdkManager: TangemSdkManager,
private val cardRepository: CardRepository,
private val getCardInfoUseCase: GetCardInfoUseCase,
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
) : Model() {
private val params = paramsContainer.require<MultiWalletChildParams>()
private val multiWalletState get() = params.multiWalletState
private val state = MutableStateFlow(SeedPhraseState())
private val _uiState = MutableStateFlow(getStartState())
private val generateSeedPhraseUiStateBuilder = GenerateSeedPhraseUiStateBuilder(
state = state,
updateUiState = { block -> updateUiStateSpecific(block) },
onContinue = { openWordsCheck() },
onContinue = ::openWordsCheck,
)
private val seedPhraseCheckUiStateBuilder = SeedPhraseCheckUiStateBuilder(
state = state,
currentUiState = { _uiState.value },
updateUiState = { block -> updateUiStateSpecific(block) },
importWallet = { },
importWallet = ::importWallet,
)
private val importSeedPhraseUiStateBuilder = ImportSeedPhraseUiStateBuilder(
modelScope = modelScope,
state = state,
mnemonicRepository = mnemonicRepository,
updateUiState = { block -> updateUiStateSpecific(block) },
importWallet = ::importWallet,
)
val uiState = _uiState.asStateFlow()
val onDone = MutableSharedFlow<Unit>()
val navigateBack = MutableSharedFlow<Unit>()
fun onBack() {
when (_uiState.value) {
is MultiWalletSeedPhraseUM.Start -> {
// TODO navigate back
modelScope.launch { navigateBack.emit(Unit) }
}
is MultiWalletSeedPhraseUM.Import,
is MultiWalletSeedPhraseUM.GenerateSeedPhrase,
@ -56,7 +88,9 @@ class MultiWalletSeedPhraseModel @Inject constructor(
private fun getStartState(): MultiWalletSeedPhraseUM {
return MultiWalletSeedPhraseUM.Start(
onImportSeedPhraseClicked = {},
onImportSeedPhraseClicked = {
openImportSeedPhrase()
},
onGenerateSeedPhraseClicked = {
generateSeedPhrase()
openGeneratedSeedPhrase()
@ -73,8 +107,6 @@ class MultiWalletSeedPhraseModel @Inject constructor(
val words12 = state.value.generatedWords12 ?: return
val words24 = state.value.generatedWords24 ?: return
// TODO disallow screenshots
_uiState.value = generateSeedPhraseUiStateBuilder.getState(
generatedWords12 = words12,
generatedWords24 = words24,
@ -85,6 +117,10 @@ class MultiWalletSeedPhraseModel @Inject constructor(
_uiState.value = seedPhraseCheckUiStateBuilder.getState()
}
private fun openImportSeedPhrase() {
_uiState.value = importSeedPhraseUiStateBuilder.getState()
}
private fun generateSeedPhrase() {
val words12 = mnemonicRepository.generateMnemonic(MnemonicRepository.MnemonicType.Words12)
val words24 = mnemonicRepository.generateMnemonic(MnemonicRepository.MnemonicType.Words24)
@ -97,6 +133,79 @@ class MultiWalletSeedPhraseModel @Inject constructor(
}
}
private fun importWallet(shouldReset: Boolean = false) {
// TODO make stateless [REDACTED_TASK_KEY]
val currentState = state.value
val mnemonic = currentState.importedMnemonic
?: (if (currentState.words24Option) currentState.generatedWords24 else currentState.generatedWords12)
?: return
val scanResponse = params.parentParams.scanResponse
modelScope.launch {
val result = tangemSdkManager.importWallet(
scanResponse = scanResponse,
mnemonic = mnemonic.mnemonicComponents.joinToString(" "),
passphrase = currentState.passphrase,
shouldReset = shouldReset,
)
when (result) {
is CompletionResult.Success -> {
// TODO on success send analytics event
multiWalletState.update {
it.copy(
currentScanResponse = it.currentScanResponse.copy(
card = result.data.card,
derivedKeys = result.data.derivedKeys,
primaryCard = result.data.primaryCard,
),
)
}
cardRepository.startCardActivation(cardId = result.data.card.cardId)
onDone.emit(Unit)
}
is CompletionResult.Failure -> {
if (result.error is TangemSdkError.WalletAlreadyCreated) {
// show should reset dialog
handleActivationError()
}
}
}
}
}
private fun handleActivationError() {
updateDialog(
resetCardDialog(
onConfirm = ::navigateToSupportScreen,
dismiss = { updateDialog(null) },
onDismissButtonClick = ::resetCard,
),
)
}
private fun resetCard() = importWallet(true)
fun navigateToSupportScreen() {
modelScope.launch {
val cardInfo = getCardInfoUseCase(multiWalletState.value.currentScanResponse).getOrNull() ?: return@launch
sendFeedbackEmailUseCase(FeedbackEmailType.DirectUserRequest(cardInfo))
}
}
private fun updateDialog(dialog: OnboardingDialogUM?) {
_uiState.update { st ->
when (st) {
is MultiWalletSeedPhraseUM.GeneratedWordsCheck -> st.copy(dialog = dialog)
is MultiWalletSeedPhraseUM.Import -> st.copy(dialog = dialog)
else -> st
}
}
}
private inline fun <reified T : MultiWalletSeedPhraseUM> updateUiStateSpecific(block: (T) -> T) {
_uiState.update {
if (it !is T) return@update it

View file

@ -6,5 +6,7 @@ data class SeedPhraseState(
val generatedWords12: Mnemonic? = null,
val generatedWords24: Mnemonic? = null,
val words24Option: Boolean = false,
val importedMnemonic: Mnemonic? = null,
val passphrase: String? = null,
val readyToImport: Boolean = false,
)

View file

@ -8,7 +8,7 @@ import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
class GenerateSeedPhraseUiStateBuilder(
internal class GenerateSeedPhraseUiStateBuilder(
private val state: MutableStateFlow<SeedPhraseState>,
private val updateUiState: (
(MultiWalletSeedPhraseUM.GenerateSeedPhrase) -> MultiWalletSeedPhraseUM.GenerateSeedPhrase,

View file

@ -0,0 +1,163 @@
package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model.builder
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.TextFieldValue
import com.tangem.common.core.TangemSdkError
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.crypto.bip39.MnemonicErrorResult
import com.tangem.features.onboarding.v2.impl.R
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model.MnemonicRepository
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model.SeedPhraseState
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.state.MultiWalletSeedPhraseUM
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
internal class ImportSeedPhraseUiStateBuilder(
private val modelScope: CoroutineScope,
private val state: MutableStateFlow<SeedPhraseState>,
private val mnemonicRepository: MnemonicRepository,
private val updateUiState: ((MultiWalletSeedPhraseUM.Import) -> MultiWalletSeedPhraseUM.Import) -> Unit,
private val importWallet: () -> Unit,
) {
private val wordsCheckJobHolder = JobHolder()
fun getState(): MultiWalletSeedPhraseUM {
return MultiWalletSeedPhraseUM.Import(
wordsChange = {
launchInterceptWords(wordsField = it)
suggestNextWord(it)
updateUiState { state ->
state.copy(words = it)
}
},
passPhraseChange = {
state.update { st -> st.copy(passphrase = it.text) }
updateUiState { state -> state.copy(passPhrase = it) }
},
onPassphraseInfoClick = ::showInfoBS,
createWalletClick = importWallet,
onSuggestionClick = { word -> addSuggestedWord(word) },
)
}
private fun addSuggestedWord(word: String) {
updateUiState { st ->
val text = st.words.text
val wordsFromText = text.split(" ").filter { it.isNotBlank() }.map { it.trim() }
val newWords = wordsFromText.dropLast(1) + word
val newWordsText = newWords.joinToString(" ")
st.copy(
words = TextFieldValue(
text = newWordsText,
selection = TextRange(newWordsText.length),
),
)
}
}
private fun suggestNextWord(wordsField: TextFieldValue) {
if (wordsField.text.endsWith(" ")) {
updateUiState { it.copy(suggestionsList = emptyList<String>().toImmutableList()) }
return
}
val text = wordsField.text
val wordsFromText = text.split(" ").filter { it.isNotBlank() }.map { it.trim() }
val lastWord = wordsFromText.lastOrNull() ?: return
if (lastWord.length < 2) return
if (mnemonicRepository.words.contains(lastWord) && mnemonicRepository.words.none { it.startsWith(lastWord) }) {
updateUiState { it.copy(suggestionsList = emptyList<String>().toImmutableList()) }
return
}
updateUiState { st ->
st.copy(
suggestionsList = mnemonicRepository.words
.filter { it.startsWith(lastWord) && it != lastWord }
.toImmutableList(),
)
}
}
private fun launchInterceptWords(wordsField: TextFieldValue) {
modelScope.launch {
delay(timeMillis = 500)
interceptWords(wordsField)
}.saveIn(wordsCheckJobHolder)
}
private fun interceptWords(wordsField: TextFieldValue) {
updateUiState {
it.copy(
createWalletEnabled = false,
wordsErrorText = null,
)
}
state.value = state.value.copy(importedMnemonic = null)
val text = wordsField.text
val wordsFromText = text.split(" ").filter { it.isNotBlank() }.map { it.trim() }
val invalidWords = wordsFromText.filterNot { it in mnemonicRepository.words }
if (invalidWords.isNotEmpty()) {
updateUiState {
it.copy(
invalidWords = invalidWords.toImmutableList(),
wordsErrorText = resourceReference(R.string.onboarding_seed_mnemonic_wrong_words),
createWalletEnabled = false,
)
}
return
}
try {
val mnemonic = mnemonicRepository.generateMnemonic(text)
state.value = state.value.copy(importedMnemonic = mnemonic)
updateUiState {
it.copy(
invalidWords = emptyList<String>().toImmutableList(),
wordsErrorText = null,
createWalletEnabled = true,
)
}
} catch (ex: TangemSdkError.MnemonicException) {
val error = ex.mnemonicResult
if (error is MnemonicErrorResult.InvalidChecksum) {
updateUiState {
it.copy(
wordsErrorText = resourceReference(R.string.onboarding_seed_mnemonic_invalid_checksum),
createWalletEnabled = false,
)
}
} else {
updateUiState {
it.copy(
createWalletEnabled = false,
wordsErrorText = null,
)
}
}
}
}
private fun showInfoBS() {
updateUiState { state ->
state.copy(
infoBottomSheetConfig = TangemBottomSheetConfig.Empty.copy(
isShow = true,
onDismissRequest = {
updateUiState { it.copy(infoBottomSheetConfig = TangemBottomSheetConfig.Empty) }
},
),
)
}
}
}

View file

@ -7,7 +7,7 @@ import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
class SeedPhraseCheckUiStateBuilder(
internal class SeedPhraseCheckUiStateBuilder(
private val state: MutableStateFlow<SeedPhraseState>,
private val currentUiState: () -> MultiWalletSeedPhraseUM,
private val updateUiState: (

View file

@ -10,7 +10,7 @@ import androidx.compose.ui.Modifier
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.state.MultiWalletSeedPhraseUM
@Composable
fun MultiWalletSeedPhrase(state: MultiWalletSeedPhraseUM, modifier: Modifier = Modifier) {
internal fun MultiWalletSeedPhrase(state: MultiWalletSeedPhraseUM, modifier: Modifier = Modifier) {
AnimatedContent(
modifier = modifier.navigationBarsPadding(),
targetState = state,

View file

@ -6,13 +6,14 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material.OutlinedTextField
import androidx.compose.material3.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.draw.clip
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.style.TextAlign
@ -30,8 +31,26 @@ import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.st
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.utils.InvalidWordsColorTransformation
import kotlinx.collections.immutable.ImmutableList
@Suppress("LongMethod")
@Composable
fun MultiWalletSeedPhraseImport(state: MultiWalletSeedPhraseUM.Import, modifier: Modifier = Modifier) {
internal fun MultiWalletSeedPhraseImport(state: MultiWalletSeedPhraseUM.Import, modifier: Modifier = Modifier) {
if (state.dialog != null) {
BasicDialog(
title = state.dialog.title.resolveReference(),
message = state.dialog.message.resolveReference(),
confirmButton = DialogButtonUM(
title = state.dialog.confirmButtonText.resolveReference(),
onClick = state.dialog.onConfirmClick,
),
dismissButton = DialogButtonUM(
title = state.dialog.dismissButtonText.resolveReference(),
warning = state.dialog.dismissWarningColor,
onClick = state.dialog.onDismissButtonClick,
),
onDismissDialog = state.dialog.onDismiss,
)
}
Box(modifier.fillMaxSize()) {
Column(
modifier = Modifier
@ -109,14 +128,16 @@ private fun PhraseBlock(state: MultiWalletSeedPhraseUM.Import, modifier: Modifie
Column(
modifier = modifier.fillMaxWidth(),
) {
// TODO migrate to material3
OutlinedTextField(
modifier = Modifier
.fillMaxWidth()
.height(TangemTheme.dimens.size142),
value = state.passPhrase,
onValueChange = state.passPhraseChange,
value = state.words,
onValueChange = state.wordsChange,
textStyle = TangemTheme.typography.body1,
singleLine = false,
colors = TangemTextFieldsDefault.defaultTextFieldColors,
visualTransformation = invalidWordsColorTransformation,
)
@ -151,6 +172,7 @@ private fun SuggestionsBlock(
visible = suggestionsList.isNotEmpty(),
) {
LazyRow(
modifier = Modifier.padding(bottom = 8.dp),
contentPadding = PaddingValues(horizontal = TangemTheme.dimens.size16),
) {
items(suggestionsList.size) { index ->
@ -171,14 +193,15 @@ private fun SuggestionsBlock(
@Composable
private fun SuggestionButton(text: String, onClick: () -> Unit, modifier: Modifier = Modifier) {
Box(modifier = modifier.clickable(onClick = onClick)) {
Notifier(
text = text,
modifier = modifier
.clip(TangemTheme.shapes.roundedCorners8)
.clickable(onClick = onClick),
backgroundColor = TangemTheme.colors.icon.primary1,
textColor = TangemTheme.colors.text.primary2,
)
}
}
private fun Modifier.rowPadding(index: Int, rowSize: Int, outSide: Dp, inSide: Dp): Modifier = when (index) {
0 -> this.padding(start = outSide, end = inSide)

View file

@ -26,7 +26,10 @@ import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@Composable
fun MultiWalletSeedPhraseWords(state: MultiWalletSeedPhraseUM.GenerateSeedPhrase, modifier: Modifier = Modifier) {
internal fun MultiWalletSeedPhraseWords(
state: MultiWalletSeedPhraseUM.GenerateSeedPhrase,
modifier: Modifier = Modifier,
) {
Column(modifier.fillMaxSize()) {
SegmentSeedBlock(
modifier = Modifier.padding(vertical = 20.dp),

View file

@ -13,18 +13,38 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.BasicDialog
import com.tangem.core.ui.components.DialogButtonUM
import com.tangem.core.ui.components.OutlineTextField
import com.tangem.core.ui.components.PrimaryButtonIconEnd
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.state.MultiWalletSeedPhraseUM
import kotlinx.collections.immutable.persistentListOf
@Composable
fun MultiWalletSeedPhraseWordsCheck(
internal fun MultiWalletSeedPhraseWordsCheck(
state: MultiWalletSeedPhraseUM.GeneratedWordsCheck,
modifier: Modifier = Modifier,
) {
if (state.dialog != null) {
BasicDialog(
title = state.dialog.title.resolveReference(),
message = state.dialog.message.resolveReference(),
confirmButton = DialogButtonUM(
title = state.dialog.confirmButtonText.resolveReference(),
onClick = state.dialog.onConfirmClick,
),
dismissButton = DialogButtonUM(
title = state.dialog.dismissButtonText.resolveReference(),
warning = state.dialog.dismissWarningColor,
onClick = state.dialog.onDismissButtonClick,
),
onDismissDialog = state.dialog.onDismiss,
)
}
Column(
modifier = modifier
.fillMaxSize()

View file

@ -4,11 +4,12 @@ import androidx.compose.runtime.Immutable
import androidx.compose.ui.text.input.TextFieldValue
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.extensions.TextReference
import com.tangem.features.onboarding.v2.multiwallet.impl.common.ui.OnboardingDialogUM
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@Immutable
sealed class MultiWalletSeedPhraseUM(
internal sealed class MultiWalletSeedPhraseUM(
val order: Int,
) {
@ -35,6 +36,7 @@ sealed class MultiWalletSeedPhraseUM(
val createWalletButtonEnabled: Boolean = false,
val createWalletButtonProgress: Boolean = false,
val onCreateWalletButtonClick: () -> Unit = {},
val dialog: OnboardingDialogUM? = null,
) : MultiWalletSeedPhraseUM(order = 2) {
data class WordField(
val index: Int,
@ -58,5 +60,6 @@ sealed class MultiWalletSeedPhraseUM(
val suggestionsList: ImmutableList<String> = persistentListOf(),
val onSuggestionClick: (String) -> Unit = {},
val infoBottomSheetConfig: TangemBottomSheetConfig = TangemBottomSheetConfig.Empty,
val dialog: OnboardingDialogUM? = null,
) : MultiWalletSeedPhraseUM(order = 1)
}

View file

@ -0,0 +1,15 @@
package com.tangem.features.onboarding.v2.multiwallet.impl.common.ui
import com.tangem.common.ui.alerts.models.AlertUM
import com.tangem.core.ui.extensions.TextReference
internal data class OnboardingDialogUM(
override val title: TextReference,
override val message: TextReference,
val dismissButtonText: TextReference,
override val confirmButtonText: TextReference,
val dismissWarningColor: Boolean = false,
override val onConfirmClick: () -> Unit,
val onDismissButtonClick: () -> Unit,
val onDismiss: () -> Unit,
) : AlertUM

View file

@ -1,18 +1,17 @@
package com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.model
package com.tangem.features.onboarding.v2.multiwallet.impl.common.ui
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.features.onboarding.v2.impl.R
import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.ui.state.MultiWalletCreateWalletUM
internal fun resetCardDialog(onConfirm: () -> Unit, dismiss: () -> Unit, onDismissButtonClick: () -> Unit) =
MultiWalletCreateWalletUM.Dialog(
OnboardingDialogUM(
title = resourceReference(R.string.onboarding_activation_error_title),
description = resourceReference(R.string.onboarding_activation_error_message),
message = resourceReference(R.string.onboarding_activation_error_message),
confirmButtonText = resourceReference(R.string.common_support),
dismissButtonText = resourceReference(R.string.common_ok),
dismissWarningColor = true,
onDismiss = dismiss,
onConfirm = {
onConfirmClick = {
onConfirm()
dismiss()
},