Updated on 2026-08-14
This commit is contained in:
commit
ef1ed894bf
34 changed files with 771 additions and 175 deletions
|
|
@ -25,6 +25,7 @@ import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.SaltPay
|
|||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.AddMoreBackupCardsDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.BackupInProgressDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.ConfirmDiscardingBackupDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.ResetBackupCardDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.UnfinishedBackupFoundDialog
|
||||
import com.tangem.tap.features.wallet.redux.models.WalletDialog
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.AmountToSendBottomSheetDialog
|
||||
|
|
@ -131,6 +132,7 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
|||
is BackupDialog.BackupInProgress -> BackupInProgressDialog.create(context)
|
||||
is BackupDialog.UnfinishedBackupFound -> UnfinishedBackupFoundDialog.create(context)
|
||||
is BackupDialog.ConfirmDiscardingBackup -> ConfirmDiscardingBackupDialog.create(context)
|
||||
is BackupDialog.ResetBackupCard -> ResetBackupCardDialog.create(context)
|
||||
is SaltPayDialog.Activation.NoGas -> NoFundsForActivationDialog.create(context)
|
||||
is SaltPayDialog.Activation.PutVisaCard -> PutVisaCardDialog.create(context)
|
||||
is SaltPayDialog.Activation.OnError -> RegistrationErrorDialog.create(context, state.dialog)
|
||||
|
|
|
|||
|
|
@ -5,22 +5,14 @@ import androidx.annotation.StringRes
|
|||
import com.tangem.Message
|
||||
import com.tangem.TangemSdk
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.CardFilter
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.SuccessResponse
|
||||
import com.tangem.common.UserCode
|
||||
import com.tangem.common.UserCodeType
|
||||
import com.tangem.common.*
|
||||
import com.tangem.common.biometric.BiometricManager
|
||||
import com.tangem.common.card.FirmwareVersion
|
||||
import com.tangem.common.core.CardIdDisplayFormat
|
||||
import com.tangem.common.core.CardSessionRunnable
|
||||
import com.tangem.common.core.Config
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.core.UserCodeRequestPolicy
|
||||
import com.tangem.common.core.*
|
||||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.common.map
|
||||
import com.tangem.common.usersCode.UserCodeRepository
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.crypto.bip39.DefaultMnemonic
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
|
|
@ -45,6 +37,7 @@ import kotlinx.coroutines.suspendCancellableCoroutine
|
|||
import kotlinx.coroutines.withContext
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Context) {
|
||||
|
||||
private val userCodeRepository by lazy {
|
||||
|
|
@ -91,6 +84,21 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
|
|||
)
|
||||
}
|
||||
|
||||
suspend fun importWallet(
|
||||
scanResponse: ScanResponse,
|
||||
mnemonic: String,
|
||||
): CompletionResult<CreateProductWalletTaskResponse> {
|
||||
return when (val seedResult = DefaultMnemonic(mnemonic, tangemSdk.wordlist).generateSeed()) {
|
||||
is CompletionResult.Success -> runTaskAsync(
|
||||
CreateProductWalletTask(scanResponse.cardTypesResolver, seedResult.data),
|
||||
scanResponse.card.cardId,
|
||||
Message(context.getString(R.string.initial_message_create_wallet_body)),
|
||||
)
|
||||
|
||||
is CompletionResult.Failure -> CompletionResult.Failure(seedResult.error)
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendScanResultsToAnalytics(result: CompletionResult<ScanResponse>) {
|
||||
if (result is CompletionResult.Failure) {
|
||||
(result.error as? TangemSdkError)?.let { error ->
|
||||
|
|
@ -115,7 +123,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
|
|||
return runTaskAsyncReturnOnMain(DeriveMultipleWalletPublicKeysTask(derivations), cardId)
|
||||
}
|
||||
|
||||
suspend fun resetToFactorySettings(cardId: String): CompletionResult<CardDTO> {
|
||||
suspend fun resetToFactorySettings(cardId: String? = null): CompletionResult<CardDTO> {
|
||||
return runTaskAsyncReturnOnMain(
|
||||
runnable = ResetToFactorySettingsTask(),
|
||||
cardId = cardId,
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ private data class CreateWalletResponse(
|
|||
|
||||
class CreateProductWalletTask(
|
||||
private val cardTypesResolver: CardTypesResolver,
|
||||
private val seed: ByteArray? = null,
|
||||
) : CardSessionRunnable<CreateProductWalletTaskResponse> {
|
||||
|
||||
override val allowsRequestAccessCodeFromRepository: Boolean = false
|
||||
|
|
@ -74,7 +75,8 @@ class CreateProductWalletTask(
|
|||
cardTypesResolver.isTangemNote() -> CreateWalletTangemNote(cardTypesResolver)
|
||||
cardTypesResolver.isTangemTwins() ->
|
||||
throw UnsupportedOperationException("Use the TwinCardsManager to create a wallet")
|
||||
else -> CreateWalletTangemWallet()
|
||||
|
||||
else -> CreateWalletTangemWallet(seed)
|
||||
}
|
||||
commandProcessor.proceed(cardDto, session) {
|
||||
when (it) {
|
||||
|
|
@ -129,7 +131,9 @@ private class CreateWalletTangemNote(private val cardTypesResolver: CardTypesRes
|
|||
}
|
||||
}
|
||||
|
||||
private class CreateWalletTangemWallet : ProductCommandProcessor<CreateProductWalletTaskResponse> {
|
||||
private class CreateWalletTangemWallet(
|
||||
private val seed: ByteArray?,
|
||||
) : ProductCommandProcessor<CreateProductWalletTaskResponse> {
|
||||
|
||||
private var primaryCard: PrimaryCard? = null
|
||||
|
||||
|
|
@ -149,7 +153,7 @@ private class CreateWalletTangemWallet : ProductCommandProcessor<CreateProductWa
|
|||
return
|
||||
}
|
||||
|
||||
CreateWalletsTask(curves).run(session) { result ->
|
||||
CreateWalletsTask(curves, seed).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
proceedWithCreatedWallets(
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ class CreateWalletsResponse(
|
|||
|
||||
class CreateWalletsTask(
|
||||
private val curves: List<EllipticCurve>,
|
||||
private val seed: ByteArray? = null,
|
||||
) : CardSessionRunnable<CreateWalletsResponse> {
|
||||
|
||||
private val createdWalletsResponses = mutableListOf<CreateWalletResponse>()
|
||||
|
|
@ -37,7 +38,7 @@ class CreateWalletsTask(
|
|||
session: CardSession,
|
||||
callback: (result: CompletionResult<CreateWalletsResponse>) -> Unit,
|
||||
) {
|
||||
CreateWalletTask(curve).run(session) { result ->
|
||||
CreateWalletTask(curve, seed).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
createdWalletsResponses.add(result.data)
|
||||
|
|
@ -47,6 +48,7 @@ class CreateWalletsTask(
|
|||
}
|
||||
createWallet(curves[createdWalletsResponses.size], session, callback)
|
||||
}
|
||||
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,14 @@ object OnboardingHelper {
|
|||
cardInfoStorage.isActivationInProgress(cardId)
|
||||
}
|
||||
}
|
||||
|
||||
response.cardTypesResolver.isWallet2() -> {
|
||||
val emptyWallets = response.card.wallets.isEmpty()
|
||||
val activationInProgress = cardInfoStorage.isActivationInProgress(cardId)
|
||||
val backupNotActive = response.card.backupStatus?.isActive != true
|
||||
emptyWallets || activationInProgress || backupNotActive
|
||||
}
|
||||
|
||||
response.card.wallets.isNotEmpty() -> cardInfoStorage.isActivationInProgress(cardId)
|
||||
else -> true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,21 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.redux
|
||||
|
||||
import android.net.Uri
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.feature.onboarding.data.model.CreateWalletResponse
|
||||
import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse
|
||||
import org.rekotlin.Action
|
||||
|
||||
sealed class OnboardingWalletAction : Action {
|
||||
object Init : OnboardingWalletAction()
|
||||
object GetToCreateWalletStep : OnboardingWalletAction()
|
||||
object GetToSaltPayStep : OnboardingWalletAction()
|
||||
|
||||
object CreateWallet : OnboardingWalletAction()
|
||||
data class WalletWasCreated(
|
||||
val result: CompletionResult<CreateProductWalletTaskResponse>,
|
||||
) : OnboardingWalletAction()
|
||||
|
||||
object Done : OnboardingWalletAction()
|
||||
object FinishOnboarding : OnboardingWalletAction()
|
||||
|
||||
|
|
@ -19,6 +27,24 @@ sealed class OnboardingWalletAction : Action {
|
|||
object OnBackPressed : OnboardingWalletAction()
|
||||
}
|
||||
|
||||
sealed class OnboardingWallet2Action : OnboardingWalletAction() {
|
||||
data class Init(val maxProgress: Int) : OnboardingWallet2Action()
|
||||
data class SetDependencies(val maxProgress: Int) : OnboardingWallet2Action()
|
||||
|
||||
data class CreateWallet(
|
||||
val callback: (CompletionResult<CreateWalletResponse>) -> Unit,
|
||||
) : OnboardingWallet2Action()
|
||||
|
||||
data class ImportWallet(
|
||||
val mnemonicComponents: List<String>,
|
||||
val callback: (CompletionResult<CreateWalletResponse>) -> Unit,
|
||||
) : OnboardingWallet2Action()
|
||||
|
||||
data class WalletWasCreated(
|
||||
val result: CompletionResult<CreateWalletResponse>,
|
||||
) : OnboardingWallet2Action()
|
||||
}
|
||||
|
||||
sealed class BackupAction : Action {
|
||||
|
||||
object IntroduceBackup : BackupAction()
|
||||
|
|
@ -62,4 +88,6 @@ sealed class BackupAction : Action {
|
|||
object DiscardBackup : BackupAction()
|
||||
object DiscardSavedBackup : BackupAction()
|
||||
object ResumeFoundUnfinishedBackup : BackupAction()
|
||||
|
||||
object ResetBackupCard : BackupAction()
|
||||
}
|
||||
|
|
@ -3,15 +3,17 @@ package com.tangem.tap.features.onboarding.products.wallet.redux
|
|||
import android.net.Uri
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.extensions.ifNotNull
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.common.TapWorkarounds.isSaltPay
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.feature.onboarding.data.model.CreateWalletResponse
|
||||
import com.tangem.operations.backup.BackupService
|
||||
import com.tangem.tap.backupService
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
|
|
@ -20,6 +22,7 @@ import com.tangem.tap.common.redux.AppState
|
|||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
|
|
@ -29,12 +32,6 @@ import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivat
|
|||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction
|
||||
import com.tangem.tap.features.wallet.models.toCurrencies
|
||||
import com.tangem.tap.features.wallet.redux.Artwork
|
||||
import com.tangem.tap.preferencesStorage
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdk
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import com.tangem.tap.userTokensRepository
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.DispatchFunction
|
||||
|
|
@ -47,7 +44,10 @@ object OnboardingWalletMiddleware {
|
|||
private val onboardingWalletMiddleware: Middleware<AppState> = { dispatch, state ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
handleWalletAction(action, state, dispatch)
|
||||
when (action) {
|
||||
is OnboardingWallet2Action -> handleWallet2Action(action, state)
|
||||
else -> handleWalletAction(action, state, dispatch)
|
||||
}
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
|
|
@ -114,42 +114,44 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch:
|
|||
}
|
||||
}
|
||||
is OnboardingWalletAction.CreateWallet -> {
|
||||
scanResponse ?: return
|
||||
scope.launch {
|
||||
scanResponse ?: return@launch
|
||||
val result = tangemSdkManager.createProductWallet(scanResponse)
|
||||
withMainContext {
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
Analytics.send(Onboarding.CreateWallet.WalletCreatedSuccessfully())
|
||||
// here we must use updated scanResponse after createWallet & derivation
|
||||
val updatedResponse = globalState.onboardingState.onboardingManager.scanResponse.copy(
|
||||
card = result.data.card,
|
||||
derivedKeys = result.data.derivedKeys,
|
||||
primaryCard = result.data.primaryCard,
|
||||
)
|
||||
onboardingManager.scanResponse = updatedResponse
|
||||
store.state.globalState.topUpController?.registerEmptyWallet(updatedResponse)
|
||||
store.dispatchOnMain(OnboardingWalletAction.WalletWasCreated(result))
|
||||
}
|
||||
}
|
||||
is OnboardingWalletAction.WalletWasCreated -> {
|
||||
scanResponse ?: return
|
||||
when (val result = action.result) {
|
||||
is CompletionResult.Success -> {
|
||||
Analytics.send(Onboarding.CreateWallet.WalletCreatedSuccessfully())
|
||||
// here we must use updated scanResponse after createWallet & derivation
|
||||
val updatedResponse = globalState.onboardingState.onboardingManager.scanResponse.copy(
|
||||
card = result.data.card,
|
||||
derivedKeys = result.data.derivedKeys,
|
||||
primaryCard = result.data.primaryCard,
|
||||
)
|
||||
onboardingManager.scanResponse = updatedResponse
|
||||
store.state.globalState.topUpController?.registerEmptyWallet(updatedResponse)
|
||||
|
||||
val blockchainNetworks = if (DemoHelper.isDemoCardId(result.data.card.cardId)) {
|
||||
DemoHelper.config.demoBlockchains
|
||||
} else {
|
||||
listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
|
||||
}.map { blockchain ->
|
||||
BlockchainNetwork(blockchain, result.data.card)
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
userTokensRepository.saveUserTokens(
|
||||
card = result.data.card,
|
||||
tokens = blockchainNetworks.toCurrencies(),
|
||||
)
|
||||
}
|
||||
startCardActivation(updatedResponse)
|
||||
store.dispatch(OnboardingWalletAction.ResumeBackup)
|
||||
}
|
||||
is CompletionResult.Failure -> Unit
|
||||
val blockchainNetworks = if (DemoHelper.isDemoCardId(result.data.card.cardId)) {
|
||||
DemoHelper.config.demoBlockchains
|
||||
} else {
|
||||
listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
|
||||
}.map { blockchain ->
|
||||
BlockchainNetwork(blockchain, result.data.card)
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
userTokensRepository.saveUserTokens(
|
||||
card = result.data.card,
|
||||
tokens = blockchainNetworks.toCurrencies(),
|
||||
)
|
||||
}
|
||||
startCardActivation(updatedResponse)
|
||||
store.dispatch(OnboardingWalletAction.ResumeBackup)
|
||||
}
|
||||
is CompletionResult.Failure -> Unit
|
||||
}
|
||||
}
|
||||
OnboardingWalletAction.FinishOnboarding -> {
|
||||
|
|
@ -194,6 +196,93 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch:
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "ComplexMethod")
|
||||
private fun handleWallet2Action(action: OnboardingWallet2Action, state: () -> AppState?) {
|
||||
val globalState = store.state.globalState
|
||||
val onboardingManager = globalState.onboardingState.onboardingManager
|
||||
|
||||
val scanResponse = onboardingManager?.scanResponse
|
||||
|
||||
when (action) {
|
||||
is OnboardingWallet2Action.Init -> {
|
||||
if (scanResponse?.cardTypesResolver?.isWallet2() == true) {
|
||||
store.dispatch(OnboardingWallet2Action.SetDependencies(action.maxProgress))
|
||||
}
|
||||
}
|
||||
|
||||
is OnboardingWallet2Action.CreateWallet -> {
|
||||
scanResponse ?: return
|
||||
scope.launch {
|
||||
val mediateResult = when (val result = tangemSdkManager.createProductWallet(scanResponse)) {
|
||||
is CompletionResult.Success -> {
|
||||
val response = CreateWalletResponse(
|
||||
card = result.data.card,
|
||||
derivedKeys = result.data.derivedKeys,
|
||||
primaryCard = result.data.primaryCard,
|
||||
)
|
||||
CompletionResult.Success(response)
|
||||
}
|
||||
|
||||
is CompletionResult.Failure -> {
|
||||
CompletionResult.Failure(result.error)
|
||||
}
|
||||
}
|
||||
withMainContext {
|
||||
action.callback(mediateResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is OnboardingWallet2Action.ImportWallet -> {
|
||||
scanResponse ?: return
|
||||
scope.launch {
|
||||
val mediateResult = when (
|
||||
val result = tangemSdkManager.importWallet(
|
||||
scanResponse = scanResponse,
|
||||
mnemonic = action.mnemonicComponents.joinToString(" "),
|
||||
)
|
||||
) {
|
||||
is CompletionResult.Success -> {
|
||||
val response = CreateWalletResponse(
|
||||
card = result.data.card,
|
||||
derivedKeys = result.data.derivedKeys,
|
||||
primaryCard = result.data.primaryCard,
|
||||
)
|
||||
CompletionResult.Success(response)
|
||||
}
|
||||
|
||||
is CompletionResult.Failure -> {
|
||||
CompletionResult.Failure(result.error)
|
||||
}
|
||||
}
|
||||
withMainContext {
|
||||
action.callback(mediateResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is OnboardingWallet2Action.WalletWasCreated -> {
|
||||
val result = when (val mediateResult = action.result) {
|
||||
is CompletionResult.Success -> {
|
||||
val response = CreateProductWalletTaskResponse(
|
||||
card = mediateResult.data.card,
|
||||
derivedKeys = mediateResult.data.derivedKeys,
|
||||
primaryCard = mediateResult.data.primaryCard,
|
||||
)
|
||||
CompletionResult.Success(response)
|
||||
}
|
||||
|
||||
is CompletionResult.Failure -> {
|
||||
CompletionResult.Failure(mediateResult.error)
|
||||
}
|
||||
}
|
||||
store.dispatchOnMain(OnboardingWalletAction.WalletWasCreated(result))
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateScanResponseAfterBackup(scanResponse: ScanResponse, backupState: BackupState): ScanResponse {
|
||||
val card = if (backupState.backupCardsNumber > 0) {
|
||||
val cardsCount = backupState.backupCardsNumber
|
||||
|
|
@ -263,7 +352,18 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
|
|||
is CompletionResult.Success -> {
|
||||
store.dispatchOnMain(BackupAction.AddBackupCard.Success)
|
||||
}
|
||||
is CompletionResult.Failure -> Unit
|
||||
|
||||
is CompletionResult.Failure -> {
|
||||
if (result.error is TangemSdkError.BackupFailedNotEmptyWallets &&
|
||||
onboardingWalletState.wallet2State != null
|
||||
) {
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
BackupDialog.ResetBackupCard,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -368,6 +468,11 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
|
|||
Analytics.send(Onboarding.Finished())
|
||||
finishCardActivation(notActivatedCardIds)
|
||||
}
|
||||
|
||||
is BackupAction.ResetBackupCard -> {
|
||||
scope.launch { tangemSdkManager.resetToFactorySettings() }
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ private fun internalReduce(action: Action, appState: AppState): OnboardingWallet
|
|||
is GlobalAction.Onboarding -> ReducerForGlobalAction.reduce(action, state)
|
||||
is OnboardingSaltPayAction -> OnboardingSaltPayReducer.reduce(action, state)
|
||||
is BackupAction -> state.copy(backupState = BackupReducer.reduce(action, state.backupState, state.isSaltPay))
|
||||
|
||||
is OnboardingWalletAction.GetToCreateWalletStep -> OnboardingWalletState(
|
||||
is OnboardingWallet2Action -> OnboardingWallet2Reducer.reduce(action, state)
|
||||
is OnboardingWalletAction.GetToCreateWalletStep -> state.copy(
|
||||
step = OnboardingWalletStep.CreateWallet,
|
||||
)
|
||||
is OnboardingWalletAction.GetToSaltPayStep -> state.copy(
|
||||
|
|
@ -65,6 +65,17 @@ private object ReducerForGlobalAction {
|
|||
private fun maxBackupCards(isSaltPay: Boolean): Int = if (isSaltPay) 1 else 2
|
||||
}
|
||||
|
||||
private object OnboardingWallet2Reducer {
|
||||
fun reduce(action: OnboardingWallet2Action, state: OnboardingWalletState): OnboardingWalletState = when (action) {
|
||||
is OnboardingWallet2Action.SetDependencies -> state.copy(
|
||||
wallet2State = OnboardingWallet2State(
|
||||
maxProgress = action.maxProgress,
|
||||
),
|
||||
)
|
||||
else -> state
|
||||
}
|
||||
}
|
||||
|
||||
private object BackupReducer {
|
||||
@Suppress("ComplexMethod")
|
||||
fun reduce(action: BackupAction, state: BackupState, isSaltPay: Boolean): BackupState {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import org.rekotlin.StateType
|
|||
*/
|
||||
data class OnboardingWalletState(
|
||||
val step: OnboardingWalletStep = OnboardingWalletStep.None,
|
||||
val wallet2State: OnboardingWallet2State? = null,
|
||||
val backupState: BackupState = BackupState(),
|
||||
val onboardingSaltPayState: OnboardingSaltPayState? = null,
|
||||
val isSaltPay: Boolean = false,
|
||||
|
|
@ -31,14 +32,14 @@ data class OnboardingWalletState(
|
|||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun getMaxProgress(): Int = when {
|
||||
isSaltPay -> 12
|
||||
else -> 6
|
||||
fun getMaxProgress(): Int {
|
||||
val baseProgress = if (isSaltPay) 12 else 6
|
||||
return getWallet2Progress() + baseProgress
|
||||
}
|
||||
|
||||
@Suppress("ComplexMethod", "MagicNumber")
|
||||
fun getProgressStep(): Int {
|
||||
return when {
|
||||
val progressByStep = when {
|
||||
step == OnboardingWalletStep.CreateWallet -> 1
|
||||
step == OnboardingWalletStep.Backup -> {
|
||||
when (backupState.backupStep) {
|
||||
|
|
@ -69,11 +70,19 @@ data class OnboardingWalletState(
|
|||
step == OnboardingWalletStep.Done -> getMaxProgress()
|
||||
else -> 1
|
||||
}
|
||||
|
||||
return getWallet2Progress() + progressByStep
|
||||
}
|
||||
|
||||
private fun getWallet2Progress(): Int = wallet2State?.maxProgress ?: 0
|
||||
}
|
||||
|
||||
data class OnboardingWallet2State(
|
||||
val maxProgress: Int,
|
||||
)
|
||||
|
||||
enum class OnboardingWalletStep {
|
||||
None, CreateWallet, Backup, SaltPay, Done
|
||||
None, CreateWallet, SaltPay, Backup, Done
|
||||
}
|
||||
|
||||
data class BackupState(
|
||||
|
|
@ -113,4 +122,5 @@ sealed class BackupDialog : StateDialog {
|
|||
object BackupInProgress : BackupDialog()
|
||||
object UnfinishedBackupFound : BackupDialog()
|
||||
object ConfirmDiscardingBackup : BackupDialog()
|
||||
object ResetBackupCard : BackupDialog()
|
||||
}
|
||||
|
|
@ -18,15 +18,7 @@ import com.tangem.common.extensions.guard
|
|||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.compose.PinCodeWidget
|
||||
import com.tangem.tap.common.extensions.configureSettings
|
||||
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.extensions.getDrawableCompat
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.setDrawable
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.extensions.stop
|
||||
import com.tangem.tap.common.extensions.toFormattedCurrencyString
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.feedback.SupportInfo
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
|
|
@ -34,11 +26,7 @@ import com.tangem.tap.common.redux.navigation.NavigationAction
|
|||
import com.tangem.tap.common.toggleWidget.IndeterminateProgressButtonWidget
|
||||
import com.tangem.tap.common.toggleWidget.RefreshBalanceWidget
|
||||
import com.tangem.tap.common.transitions.InternalNoteLayoutTransition
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupState
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupStep
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletStep
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.*
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.UtorgWebChromeClient
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.UtorgWebViewClient
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.SaltPayDialog
|
||||
|
|
@ -56,7 +44,7 @@ import com.tangem.wallet.databinding.LayoutOnboardingMainBinding
|
|||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class OnboardingSaltPayView(
|
||||
internal class OnboardingSaltPayStateHandler(
|
||||
private val walletFragment: OnboardingWalletFragment,
|
||||
) {
|
||||
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui
|
||||
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import com.tangem.feature.onboarding.api.OnboardingSeedPhrase
|
||||
import com.tangem.feature.onboarding.api.OnboardingSeedPhraseApi
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseViewModel
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletStep
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class OnboardingSeedPhraseStateHandler(
|
||||
private val onboardingSeedPhraseApi: OnboardingSeedPhraseApi = OnboardingSeedPhrase(),
|
||||
) {
|
||||
|
||||
fun newState(
|
||||
walletFragment: OnboardingWalletFragment,
|
||||
state: OnboardingWalletState,
|
||||
seedPhraseViewModel: SeedPhraseViewModel,
|
||||
) {
|
||||
if (state.step == OnboardingWalletStep.CreateWallet && !seedPhraseViewModel.isFinished) {
|
||||
switchToWallet2SeedPhraseOnboarding(walletFragment, seedPhraseViewModel, state.getMaxProgress())
|
||||
} else {
|
||||
switchToWalletOnboarding(walletFragment, state)
|
||||
}
|
||||
}
|
||||
|
||||
private fun switchToWalletOnboarding(walletFragment: OnboardingWalletFragment, state: OnboardingWalletState) {
|
||||
walletFragment.bindingSeedPhrase.onboardingSeedPhraseContainer.hide()
|
||||
walletFragment.pbBinding.pbState.show()
|
||||
walletFragment.binding.onboardingWalletContainer.show()
|
||||
walletFragment.handleOnboardingStep(state)
|
||||
}
|
||||
|
||||
private fun switchToWallet2SeedPhraseOnboarding(
|
||||
walletFragment: OnboardingWalletFragment,
|
||||
viewModel: SeedPhraseViewModel,
|
||||
onboardingWalletMaxProgress: Int,
|
||||
) {
|
||||
walletFragment.pbBinding.pbState.hide()
|
||||
walletFragment.binding.onboardingWalletContainer.hide()
|
||||
walletFragment.bindingSeedPhrase.onboardingSeedPhraseContainer.show()
|
||||
walletFragment.bindingSeedPhrase.onboardingSeedPhraseContainer.setContent {
|
||||
onboardingSeedPhraseApi.ScreenContent(
|
||||
uiState = viewModel.uiState,
|
||||
subScreen = viewModel.currentScreen.collectAsState().value,
|
||||
progress = viewModel.progress.collectAsState(0).value.toFloat() / onboardingWalletMaxProgress,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import android.widget.FrameLayout
|
|||
import android.widget.ImageView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.MenuProvider
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.transition.TransitionInflater
|
||||
import androidx.transition.TransitionManager
|
||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
|
|
@ -17,46 +18,55 @@ import coil.load
|
|||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import com.tangem.common.CardIdFormatter
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.CardIdDisplayFormat
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.feature.onboarding.data.model.CreateWalletResponse
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseMediator
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseRouter
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseViewModel
|
||||
import com.tangem.sdk.ui.widget.leapfrogWidget.LeapfrogWidget
|
||||
import com.tangem.sdk.ui.widget.leapfrogWidget.PropertyCalculator
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.inflate
|
||||
import com.tangem.tap.common.extensions.resourceUri
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.feedback.SupportInfo
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.BaseFragment
|
||||
import com.tangem.tap.features.FragmentOnBackPressedHandler
|
||||
import com.tangem.tap.features.addBackPressHandler
|
||||
import com.tangem.tap.features.onboarding.OnboardingMenuProvider
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupState
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupStep
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletStep
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.*
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.ui.OnboardingSaltPayView
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.ui.OnboardingSaltPayStateHandler
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.AccessCodeDialog
|
||||
import com.tangem.tap.mainScope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.FragmentOnboardingWalletBinding
|
||||
import com.tangem.wallet.databinding.LayoutOnboardingSaltpayBinding
|
||||
import com.tangem.wallet.databinding.LayoutOnboardingSeedPhraseBinding
|
||||
import com.tangem.wallet.databinding.ViewOnboardingProgressBinding
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
||||
@Suppress("LargeClass", "MagicNumber")
|
||||
@AndroidEntryPoint
|
||||
class OnboardingWalletFragment :
|
||||
BaseFragment(R.layout.fragment_onboarding_wallet),
|
||||
StoreSubscriber<OnboardingWalletState>,
|
||||
FragmentOnBackPressedHandler {
|
||||
|
||||
private val pbBinding: ViewOnboardingProgressBinding by viewBinding(ViewOnboardingProgressBinding::bind)
|
||||
internal val binding: FragmentOnboardingWalletBinding by viewBinding(FragmentOnboardingWalletBinding::bind)
|
||||
internal val pbBinding: ViewOnboardingProgressBinding by viewBinding(ViewOnboardingProgressBinding::bind)
|
||||
|
||||
internal val bindingSeedPhrase: LayoutOnboardingSeedPhraseBinding by lazy { binding.onboardingSeedPhraseContainer }
|
||||
internal val bindingSaltPay: LayoutOnboardingSaltpayBinding by lazy { binding.onboardingSaltpayContainer }
|
||||
|
||||
private val onboardingSaltPayView: OnboardingSaltPayView = OnboardingSaltPayView(this)
|
||||
private val seedPhraseStateHandler: OnboardingSeedPhraseStateHandler = OnboardingSeedPhraseStateHandler()
|
||||
private val seedPhraseViewModel by viewModels<SeedPhraseViewModel>()
|
||||
|
||||
private val saltPayStateHandler: OnboardingSaltPayStateHandler = OnboardingSaltPayStateHandler(this)
|
||||
|
||||
private lateinit var cardsWidget: WalletCardsWidget
|
||||
private var accessCodeDialog: AccessCodeDialog? = null
|
||||
|
|
@ -69,6 +79,13 @@ class OnboardingWalletFragment :
|
|||
exitTransition = inflater.inflateTransition(R.transition.fade)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
seedPhraseViewModel.setRouter(makeSeedPhraseRouter())
|
||||
seedPhraseViewModel.setMediator(makeSeedPhraseMediator())
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
|
|
@ -88,6 +105,7 @@ class OnboardingWalletFragment :
|
|||
binding.toolbar.setNavigationOnClickListener { activity?.onBackPressed() }
|
||||
addBackPressHandler(this)
|
||||
|
||||
store.dispatch(OnboardingWallet2Action.Init(seedPhraseViewModel.maxProgress))
|
||||
store.dispatch(OnboardingWalletAction.Init)
|
||||
store.dispatch(OnboardingSaltPayAction.Init)
|
||||
store.dispatch(
|
||||
|
|
@ -97,7 +115,7 @@ class OnboardingWalletFragment :
|
|||
)
|
||||
}
|
||||
|
||||
override fun loadToolbarMenu(): MenuProvider? = OnboardingMenuProvider()
|
||||
override fun loadToolbarMenu(): MenuProvider = OnboardingMenuProvider()
|
||||
|
||||
private fun reInitCardsWidgetIfNeeded(backupCardsCounts: Int) = with(binding) {
|
||||
val viewBackupCount = flCardsContainer.childCount - 1
|
||||
|
|
@ -159,13 +177,19 @@ class OnboardingWalletFragment :
|
|||
pbBinding.pbState.max = state.getMaxProgress()
|
||||
pbBinding.pbState.progress = state.getProgressStep()
|
||||
|
||||
if (state.isSaltPay) {
|
||||
onboardingSaltPayView.newState(state)
|
||||
} else {
|
||||
loadImageIntoImageView(state.cardArtworkUri, binding.imvFrontCard)
|
||||
loadImageIntoImageView(state.cardArtworkUri, binding.imvFirstBackupCard)
|
||||
loadImageIntoImageView(state.cardArtworkUri, binding.imvSecondBackupCard)
|
||||
handleOnboardingStep(state)
|
||||
when {
|
||||
state.wallet2State != null -> {
|
||||
seedPhraseStateHandler.newState(this, state, seedPhraseViewModel)
|
||||
}
|
||||
state.isSaltPay -> {
|
||||
saltPayStateHandler.newState(state)
|
||||
}
|
||||
else -> {
|
||||
loadImageIntoImageView(state.cardArtworkUri, binding.imvFrontCard)
|
||||
loadImageIntoImageView(state.cardArtworkUri, binding.imvFirstBackupCard)
|
||||
loadImageIntoImageView(state.cardArtworkUri, binding.imvSecondBackupCard)
|
||||
handleOnboardingStep(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -180,7 +204,12 @@ class OnboardingWalletFragment :
|
|||
internal fun handleOnboardingStep(state: OnboardingWalletState) {
|
||||
when (state.step) {
|
||||
OnboardingWalletStep.CreateWallet -> setupCreateWalletState()
|
||||
OnboardingWalletStep.Backup -> setBackupState(state.backupState, state.isSaltPay)
|
||||
OnboardingWalletStep.Backup -> setBackupState(
|
||||
state = state.backupState,
|
||||
isSaltPay = state.isSaltPay,
|
||||
isWallet2 = state.wallet2State != null,
|
||||
)
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
|
@ -201,9 +230,9 @@ class OnboardingWalletFragment :
|
|||
animator.setupCreateWalletState()
|
||||
}
|
||||
|
||||
private fun setBackupState(state: BackupState, isSaltPay: Boolean) {
|
||||
private fun setBackupState(state: BackupState, isSaltPay: Boolean, isWallet2: Boolean) {
|
||||
when (state.backupStep) {
|
||||
BackupStep.InitBackup -> showBackupIntro(state, isSaltPay)
|
||||
BackupStep.InitBackup -> showBackupIntro(state, isSaltPay, isWallet2)
|
||||
BackupStep.ScanOriginCard -> showScanOriginCard()
|
||||
BackupStep.AddBackupCards -> showAddBackupCards(state, isSaltPay)
|
||||
BackupStep.SetAccessCode -> showSetAccessCode()
|
||||
|
|
@ -215,7 +244,7 @@ class OnboardingWalletFragment :
|
|||
}
|
||||
}
|
||||
|
||||
private fun showBackupIntro(state: BackupState, isSaltPay: Boolean) = with(binding) {
|
||||
private fun showBackupIntro(state: BackupState, isSaltPay: Boolean, isWallet2: Boolean) = with(binding) {
|
||||
imvFirstBackupCard.show()
|
||||
imvSecondBackupCard.show()
|
||||
|
||||
|
|
@ -230,7 +259,7 @@ class OnboardingWalletFragment :
|
|||
|
||||
btnWalletAlternativeAction.text = getText(R.string.onboarding_button_skip_backup)
|
||||
btnWalletAlternativeAction.setOnClickListener { store.dispatch(BackupAction.SkipBackup) }
|
||||
btnWalletAlternativeAction.show(state.canSkipBackup && !isSaltPay)
|
||||
btnWalletAlternativeAction.show(state.canSkipBackup && !isSaltPay && !isWallet2)
|
||||
}
|
||||
animator.showBackupIntro(state)
|
||||
}
|
||||
|
|
@ -271,7 +300,7 @@ class OnboardingWalletFragment :
|
|||
layoutButtonsAddCards.btnContinue.isEnabled = state.backupCardsNumber != 0
|
||||
|
||||
if (isSaltPay) {
|
||||
onboardingSaltPayView.showAddBackupCards(state)
|
||||
saltPayStateHandler.showAddBackupCards(state)
|
||||
} else {
|
||||
when (state.backupCardsNumber) {
|
||||
0 -> {
|
||||
|
|
@ -443,4 +472,46 @@ class OnboardingWalletFragment :
|
|||
resources.getValue(R.dimen.device_scale_factor_for_twins_welcome, typedValue, true)
|
||||
return typedValue.float
|
||||
}
|
||||
|
||||
private fun makeSeedPhraseRouter(): SeedPhraseRouter = SeedPhraseRouter(
|
||||
onBack = ::handleOnBackPressed,
|
||||
onOpenChat = {
|
||||
store.dispatch(GlobalAction.OpenChat(SupportInfo()))
|
||||
},
|
||||
onOpenUriClick = { uri ->
|
||||
store.dispatchOpenUrl(uri.toString())
|
||||
},
|
||||
)
|
||||
|
||||
private fun makeSeedPhraseMediator(): SeedPhraseMediator {
|
||||
return object : SeedPhraseMediator {
|
||||
override fun createWallet(callback: (CompletionResult<CreateWalletResponse>) -> Unit) {
|
||||
store.dispatch(OnboardingWallet2Action.CreateWallet(callback))
|
||||
}
|
||||
|
||||
override fun onWalletCreated(result: CompletionResult<CreateWalletResponse>) {
|
||||
store.dispatch(OnboardingWallet2Action.WalletWasCreated(result))
|
||||
}
|
||||
|
||||
override fun importWallet(
|
||||
mnemonicComponents: List<String>,
|
||||
callback: (CompletionResult<CreateWalletResponse>) -> Unit,
|
||||
) {
|
||||
store.dispatch(OnboardingWallet2Action.ImportWallet(mnemonicComponents, callback))
|
||||
}
|
||||
|
||||
override fun allowScreenshots(allow: Boolean) {
|
||||
mainScope.launch {
|
||||
if (allow) {
|
||||
requireActivity().window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||
} else {
|
||||
requireActivity().window.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_SECURE,
|
||||
WindowManager.LayoutParams.FLAG_SECURE,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
object ResetBackupCardDialog {
|
||||
fun create(context: Context): AlertDialog {
|
||||
return AlertDialog.Builder(context).apply {
|
||||
setTitle(R.string.common_warning)
|
||||
setMessage(R.string.onboarding_linking_error_card_with_wallets)
|
||||
setPositiveButton(R.string.common_continue) { _, _ ->
|
||||
store.dispatch(BackupAction.ResetBackupCard)
|
||||
}
|
||||
setNegativeButton(R.string.common_cancel) { _, _ ->
|
||||
}
|
||||
setOnDismissListener {
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
}
|
||||
}.create()
|
||||
}
|
||||
}
|
||||
|
|
@ -228,6 +228,13 @@
|
|||
android:layout_height="match_parent"
|
||||
android:visibility="gone" />
|
||||
|
||||
<include
|
||||
android:id="@+id/onboarding_seed_phrase_container"
|
||||
layout="@layout/layout_onboarding_seed_phrase"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone" />
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
<include
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.compose.ui.platform.ComposeView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/onboarding_seed_phrase_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
|
@ -7,6 +7,6 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:progressBackgroundTint="#401ACE80"
|
||||
android:progressTint="@color/accent" />
|
||||
android:progressBackgroundTint="#32000000"
|
||||
android:progressTint="@color/background_action" />
|
||||
</merge>
|
||||
|
|
|
|||
|
|
@ -45,12 +45,11 @@
|
|||
|
||||
<color name="all_colors_azure">#007AFF</color>
|
||||
|
||||
|
||||
<color name="background_action">#000000</color>
|
||||
<color name="background_primary">#FFFFFF</color>
|
||||
<color name="background_secondary">#F5F5F5</color>
|
||||
|
||||
|
||||
|
||||
<color name="button_disabled">#EBEBEB</color>
|
||||
<color name="button_primary">#1E1E1E</color>
|
||||
<color name="button_secondary">#EBEBEB</color>
|
||||
|
|
|
|||
|
|
@ -184,6 +184,7 @@
|
|||
<string name="onboarding_exit_alert_message">В этом случае вам будет необходимо начать процесс заново.</string>
|
||||
<string name="onboarding_exit_alert_title">Вы хотите выйти из процесса активации?</string>
|
||||
<string name="onboarding_getting_started">Подготовка</string>
|
||||
<string name="onboarding_linking_error_card_with_wallets">Другой кошелек уже был создан на карте, которую вы пытаетесь добавить. Хотите сбросить его и использовать карту для бэкапа?</string>
|
||||
<string name="onboarding_navbar_kyc_progress">Подтвердите свою личность</string>
|
||||
<string name="onboarding_navbar_kyc_start">Верификация клиента</string>
|
||||
<string name="onboarding_navbar_pin">Код доступа</string>
|
||||
|
|
|
|||
|
|
@ -182,6 +182,7 @@
|
|||
<string name="onboarding_exit_alert_message">In this case, you will need to start from the beginning.</string>
|
||||
<string name="onboarding_exit_alert_title">Do you want to exit the activation process?</string>
|
||||
<string name="onboarding_getting_started">Getting started</string>
|
||||
<string name="onboarding_linking_error_card_with_wallets">Another wallet has already been created on the card you\'re trying to add. Do you want to reset it and use the card for a new wallet?</string>
|
||||
<string name="onboarding_navbar_kyc_progress">Verify your identity</string>
|
||||
<string name="onboarding_navbar_kyc_start">KYC</string>
|
||||
<string name="onboarding_navbar_pin">Pin code</string>
|
||||
|
|
|
|||
|
|
@ -26,7 +26,9 @@ class TangemCardTypesResolver(
|
|||
card.firmwareVersion >= FirmwareVersion.MultiWalletAvailable &&
|
||||
!card.isSaltPay
|
||||
|
||||
override fun isWallet2(): Boolean = card.firmwareVersion >= FirmwareVersion.KeysImportAvailable
|
||||
override fun isWallet2(): Boolean =
|
||||
card.firmwareVersion >= FirmwareVersion.KeysImportAvailable && card.settings.isKeysImportAllowed
|
||||
|
||||
override fun isSaltPay(): Boolean = productType == ProductType.SaltPay
|
||||
override fun isSaltPayVisa(): Boolean = card.isSaltPayVisa
|
||||
override fun isSaltPayWallet(): Boolean = card.isSaltPayWallet
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import com.tangem.common.card.EncryptionMode
|
|||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||
import com.tangem.operations.attestation.Attestation
|
||||
import java.util.Date
|
||||
import java.util.*
|
||||
import com.tangem.common.card.FirmwareVersion as SdkFirmwareVersion
|
||||
|
||||
/**
|
||||
|
|
@ -103,6 +103,7 @@ data class CardDTO(
|
|||
val supportedEncryptionModes: List<EncryptionMode>,
|
||||
val isFilesAllowed: Boolean,
|
||||
val isHDWalletAllowed: Boolean,
|
||||
val isKeysImportAllowed: Boolean,
|
||||
) {
|
||||
constructor(settings: Card.Settings) : this(
|
||||
securityDelay = settings.securityDelay,
|
||||
|
|
@ -115,6 +116,7 @@ data class CardDTO(
|
|||
supportedEncryptionModes = settings.supportedEncryptionModes,
|
||||
isFilesAllowed = settings.isFilesAllowed,
|
||||
isHDWalletAllowed = settings.isHDWalletAllowed,
|
||||
isKeysImportAllowed = settings.isKeysImportAllowed,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ plugins {
|
|||
dependencies {
|
||||
/** Core modules */
|
||||
implementation(project(":common"))
|
||||
implementation(project(":domain:models"))
|
||||
implementation(project(":core:featuretoggles"))
|
||||
implementation(project(":core:datasource"))
|
||||
implementation(project(":core:analytics"))
|
||||
|
|
@ -17,6 +18,12 @@ dependencies {
|
|||
implementation(project(":core:ui"))
|
||||
implementation(project(":core:res"))
|
||||
|
||||
/** Tangem libraries */
|
||||
implementation(deps.tangem.card.core)
|
||||
implementation(deps.tangem.card.android) {
|
||||
exclude(module = "joda-time")
|
||||
}
|
||||
|
||||
/** AndroidX libraries */
|
||||
implementation(deps.androidx.core.ktx)
|
||||
implementation(deps.androidx.appCompat)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,79 @@
|
|||
package com.tangem.feature.onboarding.api
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.LinearProgressIndicator
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingSeedPhraseState
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.ui.*
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseScreen
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class OnboardingSeedPhrase : OnboardingSeedPhraseApi {
|
||||
|
||||
@Composable
|
||||
override fun ScreenContent(uiState: OnboardingSeedPhraseState, subScreen: SeedPhraseScreen, progress: Float) {
|
||||
BackHandler(onBack = uiState.onBackClick)
|
||||
TangemTheme {
|
||||
Column {
|
||||
ProgressIndicator(progress)
|
||||
Content(subScreen, uiState)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ProgressIndicator(progress: Float) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(top = TangemTheme.dimens.size8),
|
||||
) {
|
||||
LinearProgressIndicator(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = TangemTheme.dimens.size16),
|
||||
color = TangemTheme.colors.background.action,
|
||||
backgroundColor = TangemTheme.colors.background.action.copy(alpha = 0.081f),
|
||||
progress = progress,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Content(screen: SeedPhraseScreen, uiState: OnboardingSeedPhraseState) {
|
||||
when (screen) {
|
||||
SeedPhraseScreen.Intro -> {
|
||||
IntroScreen(
|
||||
state = uiState.introState,
|
||||
)
|
||||
}
|
||||
|
||||
SeedPhraseScreen.AboutSeedPhrase -> {
|
||||
AboutSeedPhraseScreen(
|
||||
state = uiState.aboutState,
|
||||
)
|
||||
}
|
||||
SeedPhraseScreen.YourSeedPhrase -> {
|
||||
YourSeedPhraseScreen(
|
||||
state = uiState.yourSeedPhraseState,
|
||||
)
|
||||
}
|
||||
SeedPhraseScreen.CheckSeedPhrase -> {
|
||||
CheckSeedPhraseScreen(
|
||||
state = uiState.checkSeedPhraseState,
|
||||
)
|
||||
}
|
||||
SeedPhraseScreen.ImportSeedPhrase -> {
|
||||
ImportSeedPhraseScreen(
|
||||
state = uiState.importSeedPhraseState,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.feature.onboarding.api
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingSeedPhraseState
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseScreen
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface OnboardingSeedPhraseApi {
|
||||
@Suppress("TopLevelComposableFunctions")
|
||||
@Composable
|
||||
fun ScreenContent(uiState: OnboardingSeedPhraseState, subScreen: SeedPhraseScreen, progress: Float)
|
||||
}
|
||||
|
|
@ -1,21 +1,17 @@
|
|||
package com.tangem.feature.onboarding.data
|
||||
|
||||
import com.tangem.crypto.bip39.*
|
||||
import com.tangem.datasource.asset.AssetReader
|
||||
import com.tangem.crypto.bip39.DefaultMnemonic
|
||||
import com.tangem.crypto.bip39.EntropyLength
|
||||
import com.tangem.crypto.bip39.Mnemonic
|
||||
import com.tangem.crypto.bip39.Wordlist
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class DefaultMnemonicRepository(
|
||||
private val assetReader: AssetReader,
|
||||
private val bip39Wordlist: Wordlist,
|
||||
) : MnemonicRepository {
|
||||
|
||||
// TODO: change initialization after adding it to the sdk
|
||||
private val bip39Wordlist: Wordlist by lazy {
|
||||
assetReader.openFile(MNEMONIC_FILE_NAME)
|
||||
.use { BIP39Wordlist(it) }
|
||||
}
|
||||
|
||||
private val bip39Words: Set<String> by lazy { bip39Wordlist.words.toHashSet() }
|
||||
|
||||
override fun getWordsDictionary(): Set<String> = bip39Words
|
||||
|
|
@ -25,8 +21,4 @@ internal class DefaultMnemonicRepository(
|
|||
}
|
||||
|
||||
override fun createMnemonic(mnemonicString: String): Mnemonic = DefaultMnemonic(mnemonicString, bip39Wordlist)
|
||||
|
||||
companion object {
|
||||
private const val MNEMONIC_FILE_NAME = "mnemonic/mnemonic_dictionary_en.txt"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,14 @@
|
|||
package com.tangem.feature.onboarding.data.di
|
||||
|
||||
import com.tangem.datasource.asset.AssetReader
|
||||
import android.content.Context
|
||||
import com.tangem.crypto.bip39.Wordlist
|
||||
import com.tangem.feature.onboarding.data.DefaultMnemonicRepository
|
||||
import com.tangem.feature.onboarding.data.MnemonicRepository
|
||||
import com.tangem.sdk.extensions.getWordlist
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
|
@ -15,7 +18,7 @@ class OnboardingDataModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSeedPhraseSdkRepository(assetReader: AssetReader): MnemonicRepository {
|
||||
return DefaultMnemonicRepository(assetReader)
|
||||
fun provideSeedPhraseSdkRepository(@ApplicationContext context: Context): MnemonicRepository {
|
||||
return DefaultMnemonicRepository(Wordlist.getWordlist(context))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.feature.onboarding.data.model
|
||||
|
||||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.operations.backup.PrimaryCard
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class CreateWalletResponse(
|
||||
val card: CardDTO,
|
||||
val derivedKeys: Map<ByteArrayKey, ExtendedPublicKeysMap> = mapOf(),
|
||||
val primaryCard: PrimaryCard? = null,
|
||||
)
|
||||
|
|
@ -9,20 +9,15 @@ import kotlinx.collections.immutable.persistentListOf
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class OnboardingSeedPhraseState(
|
||||
val step: OnboardingSeedPhraseStep,
|
||||
val introState: IntroState,
|
||||
val aboutState: AboutState,
|
||||
val yourSeedPhraseState: YourSeedPhraseState,
|
||||
val checkSeedPhraseState: CheckSeedPhraseState,
|
||||
val importSeedPhraseState: ImportSeedPhraseState,
|
||||
val menuButtonChat: ButtonState,
|
||||
val isOnboardingFinished: Boolean = false,
|
||||
val onBackClick: () -> Unit,
|
||||
)
|
||||
|
||||
enum class OnboardingSeedPhraseStep {
|
||||
Intro, AboutSeedPhrase, YourSeedPhrase, CheckSeedPhrase, ImportSeedPhrase
|
||||
}
|
||||
|
||||
data class IntroState(
|
||||
val cardImageUrl: String? = null,
|
||||
val buttonCreateWallet: ButtonState,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ data class UiActions(
|
|||
val checkSeedPhraseActions: CheckSeedPhraseUiAction,
|
||||
val importSeedPhraseActions: ImportSeedPhraseUiAction,
|
||||
val menuChatClick: () -> Unit,
|
||||
val menuNavigateBackClick: () -> Unit,
|
||||
)
|
||||
|
||||
data class IntroUiAction(
|
||||
|
|
|
|||
|
|
@ -93,7 +93,6 @@ private fun PhraseBlock(state: ImportSeedPhraseState, modifier: Modifier = Modif
|
|||
),
|
||||
colors = TangemTextFieldsDefault.defaultTextFieldColors,
|
||||
)
|
||||
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ class StateBuilder(
|
|||
val importSeedPhrase: ImportSeedPhraseStateBuilder = ImportSeedPhraseStateBuilder()
|
||||
|
||||
fun init(): OnboardingSeedPhraseState = OnboardingSeedPhraseState(
|
||||
step = OnboardingSeedPhraseStep.Intro,
|
||||
introState = IntroState(
|
||||
buttonCreateWallet = ButtonState(
|
||||
onClick = uiActions.introActions.buttonCreateWalletClick,
|
||||
|
|
@ -77,13 +76,9 @@ class StateBuilder(
|
|||
menuButtonChat = ButtonState(
|
||||
onClick = uiActions.menuChatClick,
|
||||
),
|
||||
onBackClick = uiActions.menuNavigateBackClick,
|
||||
)
|
||||
|
||||
fun changeStep(uiState: OnboardingSeedPhraseState, step: OnboardingSeedPhraseStep): OnboardingSeedPhraseState =
|
||||
uiState.copy(
|
||||
step = step,
|
||||
)
|
||||
|
||||
fun generateMnemonicComponents(uiState: OnboardingSeedPhraseState): OnboardingSeedPhraseState {
|
||||
return uiState.copy(
|
||||
aboutState = uiState.aboutState.copy(
|
||||
|
|
@ -103,7 +98,6 @@ class StateBuilder(
|
|||
): OnboardingSeedPhraseState {
|
||||
return updateMnemonicComponents(uiState, mnemonicGridItems)
|
||||
.copy(
|
||||
step = OnboardingSeedPhraseStep.YourSeedPhrase,
|
||||
aboutState = uiState.aboutState.copy(
|
||||
buttonGenerateSeedPhrase = uiState.aboutState.buttonGenerateSeedPhrase.copy(
|
||||
showProgress = false,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.feature.onboarding.presentation.wallet2.viewmodel
|
||||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.feature.onboarding.data.model.CreateWalletResponse
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface SeedPhraseMediator {
|
||||
fun createWallet(callback: (CompletionResult<CreateWalletResponse>) -> Unit)
|
||||
fun importWallet(mnemonicComponents: List<String>, callback: (CompletionResult<CreateWalletResponse>) -> Unit)
|
||||
|
||||
fun onWalletCreated(result: CompletionResult<CreateWalletResponse>)
|
||||
|
||||
fun allowScreenshots(allow: Boolean)
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package com.tangem.feature.onboarding.presentation.wallet2.viewmodel
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class SeedPhraseRouter(
|
||||
private val onBack: () -> Unit,
|
||||
private val onOpenChat: () -> Unit,
|
||||
private val onOpenUriClick: (Uri) -> Unit,
|
||||
) {
|
||||
|
||||
private val _currentScreen = MutableStateFlow(SeedPhraseScreen.Intro)
|
||||
|
||||
val currentScreen: StateFlow<SeedPhraseScreen>
|
||||
get() = _currentScreen
|
||||
|
||||
var currentScreenState by mutableStateOf(SeedPhraseScreen.Intro)
|
||||
private set
|
||||
|
||||
fun navigateBack() {
|
||||
_currentScreen.value = when (_currentScreen.value) {
|
||||
SeedPhraseScreen.Intro -> {
|
||||
onBack.invoke()
|
||||
return
|
||||
}
|
||||
|
||||
SeedPhraseScreen.AboutSeedPhrase -> SeedPhraseScreen.Intro
|
||||
SeedPhraseScreen.YourSeedPhrase -> SeedPhraseScreen.AboutSeedPhrase
|
||||
SeedPhraseScreen.CheckSeedPhrase -> SeedPhraseScreen.YourSeedPhrase
|
||||
SeedPhraseScreen.ImportSeedPhrase -> SeedPhraseScreen.AboutSeedPhrase
|
||||
}
|
||||
}
|
||||
|
||||
fun openScreen(screen: SeedPhraseScreen) {
|
||||
_currentScreen.value = screen
|
||||
}
|
||||
|
||||
fun openChat() {
|
||||
onOpenChat.invoke()
|
||||
}
|
||||
|
||||
fun openUri(uri: Uri) {
|
||||
onOpenUriClick.invoke(uri)
|
||||
}
|
||||
}
|
||||
|
||||
enum class SeedPhraseScreen {
|
||||
Intro, AboutSeedPhrase, YourSeedPhrase, CheckSeedPhrase, ImportSeedPhrase
|
||||
}
|
||||
|
|
@ -1,14 +1,27 @@
|
|||
package com.tangem.feature.onboarding.presentation.wallet2.viewmodel
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.feature.onboarding.data.model.CreateWalletResponse
|
||||
import com.tangem.feature.onboarding.domain.SeedPhraseError
|
||||
import com.tangem.feature.onboarding.domain.SeedPhraseInteractor
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.*
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.AboutUiAction
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.CheckSeedPhraseUiAction
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.ImportSeedPhraseUiAction
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.IntroUiAction
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.MnemonicGridItem
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingSeedPhraseState
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.SeedPhraseField
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.TextFieldState
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.TextFieldUiAction
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.UiActions
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.YourSeedPhraseUiAction
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.ui.stateBuiders.StateBuilder
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.Debouncer
|
||||
|
|
@ -16,7 +29,16 @@ import com.tangem.utils.extensions.isEven
|
|||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -34,18 +56,48 @@ class SeedPhraseViewModel @Inject constructor(
|
|||
var uiState: OnboardingSeedPhraseState by mutableStateOf(uiBuilder.init())
|
||||
private set
|
||||
|
||||
val currentStep: OnboardingSeedPhraseStep
|
||||
get() = uiState.step
|
||||
private lateinit var router: SeedPhraseRouter
|
||||
private lateinit var mediator: SeedPhraseMediator
|
||||
|
||||
val currentScreen: StateFlow<SeedPhraseScreen>
|
||||
get() = router.currentScreen
|
||||
|
||||
val progress: Flow<Int>
|
||||
get() = currentScreen.map { progressByScreen[it] ?: 0 }
|
||||
|
||||
val maxProgress: Int by lazy { progressByScreen.maxOfOrNull { it.value } ?: 0 }
|
||||
|
||||
var isFinished: Boolean = false
|
||||
private set
|
||||
|
||||
private val progressByScreen = mapOf(
|
||||
SeedPhraseScreen.Intro to 1,
|
||||
SeedPhraseScreen.AboutSeedPhrase to 2,
|
||||
SeedPhraseScreen.YourSeedPhrase to 3,
|
||||
SeedPhraseScreen.CheckSeedPhrase to 3,
|
||||
SeedPhraseScreen.ImportSeedPhrase to 3,
|
||||
)
|
||||
|
||||
private val textFieldsDebouncers = mutableMapOf<String, Debouncer>()
|
||||
private var suggestionWordInserted: AtomicBoolean = AtomicBoolean(false)
|
||||
|
||||
private var generatedMnemonicComponents: List<String>? = null
|
||||
private var importedMnemonicComponents: List<String>? = null
|
||||
|
||||
override fun onCleared() {
|
||||
textFieldsDebouncers.forEach { entry -> entry.value.release() }
|
||||
textFieldsDebouncers.clear()
|
||||
super.onCleared()
|
||||
}
|
||||
|
||||
fun setRouter(router: SeedPhraseRouter) {
|
||||
this.router = router
|
||||
}
|
||||
|
||||
fun setMediator(mediator: SeedPhraseMediator) {
|
||||
this.mediator = mediator
|
||||
}
|
||||
|
||||
private fun createUiActions(): UiActions = UiActions(
|
||||
introActions = IntroUiAction(
|
||||
buttonCreateWalletClick = ::buttonCreateWalletClick,
|
||||
|
|
@ -60,7 +112,7 @@ class SeedPhraseViewModel @Inject constructor(
|
|||
buttonContinueClick = ::buttonContinueClick,
|
||||
),
|
||||
checkSeedPhraseActions = CheckSeedPhraseUiAction(
|
||||
buttonCreateWalletClick = ::buttonCreateWalletWithSeedPhraseClick,
|
||||
buttonCreateWalletClick = { buttonImportWalletClick(generatedMnemonicComponents) },
|
||||
secondTextFieldAction = TextFieldUiAction(
|
||||
onTextFieldChanged = { value -> onTextFieldChanged(SeedPhraseField.Second, value) },
|
||||
),
|
||||
|
|
@ -76,9 +128,10 @@ class SeedPhraseViewModel @Inject constructor(
|
|||
onTextFieldChanged = { value -> onSeedPhraseTextFieldChanged(value) },
|
||||
),
|
||||
suggestedPhraseClick = ::buttonSuggestedPhraseClick,
|
||||
buttonCreateWalletClick = ::buttonCreateWalletWithSeedPhraseClick,
|
||||
buttonCreateWalletClick = { buttonImportWalletClick(importedMnemonicComponents) },
|
||||
),
|
||||
menuChatClick = ::menuChatClick,
|
||||
menuNavigateBackClick = ::menuNavigateBackClick,
|
||||
)
|
||||
|
||||
// region CheckSeedPhrase
|
||||
|
|
@ -116,8 +169,7 @@ class SeedPhraseViewModel @Inject constructor(
|
|||
val isSameText = textFieldValue.text == oldTextFieldValue.text
|
||||
val isCursorMoved = textFieldValue.selection != oldTextFieldValue.selection
|
||||
|
||||
val mediateState = uiBuilder.importSeedPhrase.updateTextField(uiState, textFieldValue)
|
||||
uiState = uiBuilder.importSeedPhrase.updateCreateWalletButton(mediateState, enabled = false)
|
||||
uiState = uiBuilder.importSeedPhrase.updateTextField(uiState, textFieldValue)
|
||||
|
||||
val fieldState = uiState.importSeedPhraseState.tvSeedPhrase
|
||||
val inputMnemonic = fieldState.textFieldValue.text
|
||||
|
|
@ -148,21 +200,29 @@ class SeedPhraseViewModel @Inject constructor(
|
|||
|
||||
private suspend fun validateMnemonic(inputMnemonic: String) {
|
||||
if (inputMnemonic.isEmpty() && uiState.importSeedPhraseState.error != null) {
|
||||
updateUi { uiBuilder.importSeedPhrase.updateError(uiState, null) }
|
||||
val mediateState = uiBuilder.importSeedPhrase.updateCreateWalletButton(uiState, enabled = false)
|
||||
updateUi {
|
||||
uiBuilder.importSeedPhrase.updateError(mediateState, null)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
interactor.validateMnemonicString(inputMnemonic)
|
||||
.onSuccess {
|
||||
updateUi { uiBuilder.importSeedPhrase.updateError(uiState, null) }
|
||||
.onSuccess { mnemonicComponents ->
|
||||
importedMnemonicComponents = mnemonicComponents
|
||||
val mediateState = uiBuilder.importSeedPhrase.updateCreateWalletButton(uiState, enabled = true)
|
||||
updateUi { uiBuilder.importSeedPhrase.updateError(mediateState, null) }
|
||||
}
|
||||
.onFailure {
|
||||
uiState = uiBuilder.importSeedPhrase.updateCreateWalletButton(uiState, enabled = false)
|
||||
val error = it as? SeedPhraseError ?: return
|
||||
|
||||
importedMnemonicComponents = null
|
||||
val mediateState = when (error) {
|
||||
is SeedPhraseError.InvalidWords -> {
|
||||
uiBuilder.importSeedPhrase.updateInvalidWords(uiState, error.words)
|
||||
}
|
||||
|
||||
else -> uiState
|
||||
}
|
||||
updateUi { uiBuilder.importSeedPhrase.updateError(mediateState, error) }
|
||||
|
|
@ -180,22 +240,46 @@ class SeedPhraseViewModel @Inject constructor(
|
|||
// endregion ImportSeedPhrase
|
||||
|
||||
// region ButtonClickHandlers
|
||||
@Suppress("EmptyFunctionBlock")
|
||||
private fun buttonCreateWalletClick() {
|
||||
}
|
||||
|
||||
@Suppress("EmptyFunctionBlock")
|
||||
private fun buttonCreateWalletWithSeedPhraseClick() {
|
||||
}
|
||||
|
||||
private fun buttonOtherOptionsClick() {
|
||||
viewModelScope.launchSingle {
|
||||
updateUi { uiBuilder.changeStep(uiState, OnboardingSeedPhraseStep.AboutSeedPhrase) }
|
||||
viewModelScope.launch(dispatchers.io) {
|
||||
mediator.createWallet(::handleWalletCreationResult)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buttonImportWalletClick(mnemonicComponents: List<String>?) {
|
||||
mnemonicComponents ?: return
|
||||
|
||||
viewModelScope.launch(dispatchers.io) {
|
||||
mediator.importWallet(mnemonicComponents, ::handleWalletCreationResult)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleWalletCreationResult(result: CompletionResult<CreateWalletResponse>) {
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
isFinished = true
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
// errors shows on the TangemSdk bottom sheet dialog
|
||||
}
|
||||
}
|
||||
mediator.onWalletCreated(result)
|
||||
}
|
||||
|
||||
private fun menuNavigateBackClick() {
|
||||
router.navigateBack()
|
||||
}
|
||||
|
||||
private fun menuChatClick() {
|
||||
router.openChat()
|
||||
}
|
||||
|
||||
private fun buttonOtherOptionsClick() {
|
||||
router.openScreen(SeedPhraseScreen.AboutSeedPhrase)
|
||||
}
|
||||
|
||||
private fun buttonReadMoreAboutSeedPhraseClick() {
|
||||
// TODO: open the about info through a router
|
||||
router.openUri(URI_ABOUT_SEED_PHRASE)
|
||||
}
|
||||
|
||||
private fun buttonGenerateSeedPhraseClick() {
|
||||
|
|
@ -204,15 +288,28 @@ class SeedPhraseViewModel @Inject constructor(
|
|||
delay(DELAY_GENERATE_SEED_PHRASE)
|
||||
interactor.generateMnemonic()
|
||||
.onSuccess { mnemonic ->
|
||||
generatedMnemonicComponents = mnemonic.mnemonicComponents
|
||||
val mnemonicGridItems = generateMnemonicGridList(mnemonic.mnemonicComponents)
|
||||
updateUi { uiBuilder.mnemonicGenerated(uiState, mnemonicGridItems.toImmutableList()) }
|
||||
setScreenshotsEnabled()
|
||||
updateUi {
|
||||
router.openScreen(SeedPhraseScreen.YourSeedPhrase)
|
||||
uiBuilder.mnemonicGenerated(uiState, mnemonicGridItems.toImmutableList())
|
||||
}
|
||||
}
|
||||
.onFailure {
|
||||
generatedMnemonicComponents = null
|
||||
// TODO: show error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setScreenshotsEnabled() {
|
||||
router.currentScreen.onEach {
|
||||
val allowScreenshots = it != SeedPhraseScreen.YourSeedPhrase
|
||||
mediator.allowScreenshots(allowScreenshots)
|
||||
}.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
private suspend fun generateMnemonicGridList(mnemonicComponents: List<String>): ImmutableList<MnemonicGridItem> {
|
||||
val size = mnemonicComponents.size
|
||||
val splitIndex = if (size.isEven()) size / 2 else size / 2 + 1
|
||||
|
|
@ -240,15 +337,12 @@ class SeedPhraseViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun buttonImportSeedPhraseClick() {
|
||||
viewModelScope.launchSingle {
|
||||
updateUi { uiBuilder.changeStep(uiState, OnboardingSeedPhraseStep.ImportSeedPhrase) }
|
||||
}
|
||||
router.openScreen(SeedPhraseScreen.ImportSeedPhrase)
|
||||
}
|
||||
|
||||
private fun buttonContinueClick() {
|
||||
viewModelScope.launchSingle {
|
||||
updateUi { uiBuilder.changeStep(uiState, OnboardingSeedPhraseStep.CheckSeedPhrase) }
|
||||
}
|
||||
router.openScreen(SeedPhraseScreen.CheckSeedPhrase)
|
||||
mediator.allowScreenshots(true)
|
||||
}
|
||||
|
||||
private fun buttonSuggestedPhraseClick(suggestionIndex: Int) {
|
||||
|
|
@ -269,10 +363,6 @@ class SeedPhraseViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun menuChatClick() {
|
||||
// TODO: open the support chat through a router
|
||||
}
|
||||
// endregion ButtonClickHandlers
|
||||
|
||||
// region Utils
|
||||
|
|
@ -311,6 +401,8 @@ class SeedPhraseViewModel @Inject constructor(
|
|||
// endregion Utils
|
||||
|
||||
companion object {
|
||||
private val URI_ABOUT_SEED_PHRASE =
|
||||
Uri.parse("https://tangem.com/ru/blog/post/seed-phrase-a-risky-solution/")
|
||||
private const val MNEMONIC_DEBOUNCER = "MnemonicDebouncer"
|
||||
private const val MNEMONIC_DEBOUNCE_DELAY = 700L
|
||||
private const val DELAY_GENERATE_SEED_PHRASE = 300L
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue