From 2a754c50cf7684e3842397b89c50fcbc846cc4b5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 22 Oct 2022 18:24:47 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../tap/features/home/redux/HomeMiddleware.kt | 5 +- .../onboarding/OnboardingSaltPayHelper.kt | 29 +- .../note/redux/OnboardingNoteMiddleware.kt | 14 +- .../wallet/redux/OnboardingWalletState.kt | 9 +- .../wallet/saltPay/GnosisRegistrator.kt | 18 ++ .../saltPay/SaltPayRegistrationManager.kt | 40 +++ .../saltPay/message/SaltPayModuleMessage.kt | 9 +- .../saltPay/redux/OnboardingSaltPayAction.kt | 18 +- .../redux/OnboardingSaltPayMiddleware.kt | 257 +++++++++++++++--- .../saltPay/redux/OnboardingSaltPayReducer.kt | 42 ++- .../saltPay/redux/OnboardingSaltPayState.kt | 21 +- .../saltPay/ui/OnboardingSaltPayView.kt | 200 +++++++++----- .../wallet/ui/OnboardingWalletFragment.kt | 58 ++-- .../layout_onboarding_container_bottom.xml | 10 +- .../layout_onboarding_buttons_common.xml | 4 +- .../layout_onboarding_container_bottom.xml | 10 +- .../res/layout/layout_onboarding_saltpay.xml | 5 + app/src/main/res/values-de/strings_final.xml | 7 + app/src/main/res/values-fr/strings_final.xml | 7 + app/src/main/res/values-it/strings_final.xml | 7 + app/src/main/res/values-ru/strings_final.xml | 7 + app/src/main/res/values/strings_final.xml | 7 + dependencies.gradle | 2 +- 23 files changed, 584 insertions(+), 202 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index 7ecc17b56e..1354659274 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -111,7 +111,8 @@ private fun onScanSuccess(scanResponse: ScanResponse) { if (scanResponse.isSaltPay()) { if (scanResponse.isSaltPayVisa()) { scope.launch { - val result = OnboardingSaltPayHelper.isOnboardingCase(scanResponse) + val (manager, config) = OnboardingSaltPayState.initDependency(scanResponse) + val result = OnboardingSaltPayHelper.isOnboardingCase(scanResponse, manager) delay(500) withMainContext { when (result) { @@ -119,10 +120,8 @@ private fun onScanSuccess(scanResponse: ScanResponse) { val isOnboardingCase = result.data if (isOnboardingCase) { store.dispatch(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = false)) - val (manager, config) = OnboardingSaltPayState.initDependency(scanResponse) store.dispatch(OnboardingSaltPayAction.Init.SetDependencies(manager, config)) store.dispatch(OnboardingSaltPayAction.Update) - // store.dispatch(OnboardingSaltPayAction.Init.DiscardBackupSteps) navigateTo(AppScreen.OnboardingWallet) } else { navigateTo(AppScreen.Wallet) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingSaltPayHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingSaltPayHelper.kt index 2ee4ef00de..a849c4219e 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingSaltPayHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingSaltPayHelper.kt @@ -3,10 +3,10 @@ package com.tangem.tap.features.onboarding import com.tangem.common.services.Result import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.extensions.successOr +import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayRegistrationManager import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayRegistrationError -import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayState import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.SaltPayRegistrationStep -import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.toSaltPayStep +import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.updateSaltPayStatus /** [REDACTED_AUTHOR] @@ -14,19 +14,22 @@ import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.toSaltPa class OnboardingSaltPayHelper { companion object { - suspend fun isOnboardingCase(scanResponse: ScanResponse): Result { + suspend fun isOnboardingCase( + scanResponse: ScanResponse, + manager: SaltPayRegistrationManager + ): Result { return try { - val (manager, config) = OnboardingSaltPayState.initDependency(scanResponse) - val registrationResponseItem = manager.checkRegistration().successOr { return it } - val registrationStep = registrationResponseItem.toSaltPayStep() - manager.checkHasGas().successOr { return it } + val status = manager.updateSaltPayStatus( + amountToClaim = null, + step = SaltPayRegistrationStep.None, + ).successOr { return it } - val isRegistrationOnboardingCase = registrationStep != SaltPayRegistrationStep.Finished - val isBackupOnboardingCase = scanResponse.card.backupStatus?.isActive == false - Result.Success(isRegistrationOnboardingCase || isBackupOnboardingCase) + val isRegistrationCase = status.step != SaltPayRegistrationStep.ClaimSuccess + val isBackupCase = scanResponse.card.backupStatus?.isActive == false + Result.Success(isRegistrationCase || isBackupCase) } catch (ex: Exception) { Result.Failure(ex) - } catch (error: SaltPayRegistrationError){ + } catch (error: SaltPayRegistrationError) { Result.Failure(error) } } @@ -37,7 +40,7 @@ class OnboardingSaltPayHelper { Result.Success(isBackupOnboardingCase) } catch (ex: Exception) { Result.Failure(ex) - } catch (error: SaltPayRegistrationError){ + } catch (error: SaltPayRegistrationError) { Result.Failure(error) } } @@ -47,7 +50,7 @@ class OnboardingSaltPayHelper { Result.Success(false) } catch (ex: Exception) { Result.Failure(ex) - } catch (error: SaltPayRegistrationError){ + } catch (error: SaltPayRegistrationError) { Result.Failure(error) } } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt index 34c0d381b2..335e3fdf46 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt @@ -4,7 +4,12 @@ import com.tangem.common.CompletionResult import com.tangem.common.extensions.guard import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE -import com.tangem.tap.common.extensions.* +import com.tangem.tap.common.extensions.dispatchDialogShow +import com.tangem.tap.common.extensions.dispatchErrorNotification +import com.tangem.tap.common.extensions.dispatchOpenUrl +import com.tangem.tap.common.extensions.getAddressData +import com.tangem.tap.common.extensions.getToUpUrl +import com.tangem.tap.common.extensions.onCardScanned import com.tangem.tap.common.postUi import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.AppState @@ -15,6 +20,7 @@ import com.tangem.tap.domain.TapError import com.tangem.tap.domain.extensions.hasWallets import com.tangem.tap.domain.extensions.makePrimaryWalletManager import com.tangem.tap.features.demo.DemoHelper +import com.tangem.tap.features.onboarding.WalletBalanceFetcher import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.tap.scope @@ -115,16 +121,16 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch val balanceIsLoading = noteState.walletBalance.copy( currency = Currency.Blockchain( walletManager.wallet.blockchain, - walletManager.wallet.publicKey.derivationPath?.rawPath + walletManager.wallet.publicKey.derivationPath?.rawPath, ), state = ProgressState.Loading, error = null, - criticalError = null + criticalError = null, ) store.dispatch(OnboardingNoteAction.Balance.Set(balanceIsLoading)) scope.launch { - val loadedBalance = onboardingManager.updateBalance(walletManager) + val loadedBalance = WalletBalanceFetcher().updateBalance(walletManager) delay(if (isLoadedBefore) 0 else 300) loadedBalance.criticalError?.let { store.dispatchErrorNotification(it) } withMainContext { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt index 566cf6aaf3..16a3165bfe 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt @@ -30,7 +30,7 @@ data class OnboardingWalletState( } fun getMaxProgress(): Int = when { - isSaltPay -> 12 + isSaltPay -> 14 else -> 6 } @@ -41,7 +41,6 @@ data class OnboardingWalletState( when (backupState.backupStep) { BackupStep.InitBackup -> 2 BackupStep.ScanOriginCard -> 3 - BackupStep.AddBackupCards -> 4 BackupStep.EnterAccessCode -> 4 BackupStep.ReenterAccessCode -> 4 @@ -52,13 +51,15 @@ data class OnboardingWalletState( } step == OnboardingWalletStep.SaltPay && isSaltPay -> { when (onboardingSaltPayState!!.step) { - SaltPayRegistrationStep.NoGas -> 1 + SaltPayRegistrationStep.None, SaltPayRegistrationStep.NoGas -> 0 SaltPayRegistrationStep.NeedPin -> 7 SaltPayRegistrationStep.CardRegistration -> 8 SaltPayRegistrationStep.KycIntro -> 9 SaltPayRegistrationStep.KycStart -> 10 SaltPayRegistrationStep.KycWaiting -> 11 - SaltPayRegistrationStep.Finished -> getMaxProgress() + SaltPayRegistrationStep.Claim -> 12 + SaltPayRegistrationStep.ClaimInProgress -> 13 + SaltPayRegistrationStep.ClaimSuccess -> getMaxProgress() } } step == OnboardingWalletStep.Done -> getMaxProgress() diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt index 5937e34621..771fca7d4b 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt @@ -7,6 +7,7 @@ import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.BlockchainSdkError +import com.tangem.blockchain.common.TransactionSigner import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.extensions.Result import com.tangem.blockchain.extensions.SimpleResult @@ -42,6 +43,8 @@ class GnosisRegistrator( else -> throw IllegalArgumentException("GnosisRegistrator supports only the SaltPay blockchain") } + private val addressTreasureSafe = "0x8e9260a049d3Aa9ac60D0d4F27017320E0e2396B" + private val atomicNonce = AtomicLong() suspend fun checkHasGas(): Result { @@ -142,6 +145,21 @@ class GnosisRegistrator( return Result.Success(compiledEthereumTransaction) } + suspend fun getAllowance(): Result { + return walletManager.getAllowance(addressTreasureSafe, token) + } + + suspend fun transferFrom(amountToClaim: BigDecimal, signer: TransactionSigner): Result { + val amount = Amount(token, amountToClaim) + val feeAmount = walletManager.getFeeToTransferFrom(amount, addressTreasureSafe) + .extractFeeAmount().successOr { return it } + + val transactionData = walletManager.createTransferFromTransaction(amount, feeAmount, addressTreasureSafe) + walletManager.transferFrom(transactionData, signer).successOr { return Result.Failure(it.error) } + + return Result.Success(Unit) + } + private fun Result>.extractFeeAmount(): Result { return when (this) { is Result.Success -> { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayRegistrationManager.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayRegistrationManager.kt index 93e129737c..6f4bad5574 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayRegistrationManager.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayRegistrationManager.kt @@ -2,6 +2,12 @@ package com.tangem.tap.features.onboarding.products.wallet.saltPay import android.net.Uri import com.tangem.blockchain.blockchains.ethereum.SignedEthereumTransaction +import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.common.AmountType +import com.tangem.blockchain.common.TransactionSigner +import com.tangem.blockchain.extensions.successOr +import com.tangem.common.extensions.guard +import com.tangem.common.extensions.isZero import com.tangem.common.services.Result import com.tangem.domain.common.extensions.successOr import com.tangem.network.api.paymentology.AttestationResponse @@ -11,6 +17,8 @@ import com.tangem.network.api.paymentology.RegisterWalletRequest import com.tangem.network.api.paymentology.RegisterWalletResponse import com.tangem.network.api.paymentology.RegistrationResponse import com.tangem.operations.attestation.AttestWalletKeyResponse +import com.tangem.tap.common.extensions.safeUpdate +import com.tangem.tap.domain.getFirstToken import com.tangem.tap.domain.tokens.UserWalletId import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayRegistrationError import com.tangem.tap.persistence.SaltPayRegistrationStorage @@ -104,6 +112,38 @@ class SaltPayRegistrationManager( return result } + suspend fun getAmountToClaim(): Result { + val amount = gnosisRegistrator.getAllowance().successOr { + return Result.Failure(SaltPayRegistrationError.FailedToGetFundsToClaim) + } + + return if (amount.value == null || amount.value?.isZero() == true) { + Result.Failure(SaltPayRegistrationError.NoFundsToClaim) + } else { + Result.Success(amount) + } + } + + suspend fun claim(amountToClaim: BigDecimal, signer: TransactionSigner): Result { + val result = gnosisRegistrator.transferFrom(amountToClaim, signer).successOr { + return Result.Failure(SaltPayRegistrationError.ClaimTransactionFailed) + } + return Result.Success(Unit) + } + + suspend fun getTokenAmount(): Result { + val wallet = gnosisRegistrator.walletManager.safeUpdate().successOr { return it } + val token = wallet.getFirstToken().guard { + throw UnsupportedOperationException() + } + + val amountValue = wallet.amounts[AmountType.Token(token)]?.value.guard { + throw UnsupportedOperationException() + } + + return Result.Success(amountValue) + } + fun makeRegistrationTask(challenge: ByteArray): SaltPayRegistrationTask { return SaltPayRegistrationTask( gnosisRegistrator = gnosisRegistrator, diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/message/SaltPayModuleMessage.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/message/SaltPayModuleMessage.kt index 144647b876..aaedced27b 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/message/SaltPayModuleMessage.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/message/SaltPayModuleMessage.kt @@ -42,8 +42,13 @@ sealed class SaltPayRegistrationError( class CardDisabled(message: String?) : SaltPayRegistrationError(9, message) class CardNotPassed(message: String?) : SaltPayRegistrationError(10, message) object EmptyDynamicAttestResponse : SaltPayRegistrationError(11) - object EmptyBackupCardScanned : SaltPayRegistrationError(12) // - object WeakPin : SaltPayRegistrationError(13) // + object EmptyBackupCardScanned : SaltPayRegistrationError(12) + object WeakPin : SaltPayRegistrationError(13) + + object FailedToGetFundsToClaim : SaltPayRegistrationError(14) + object NoFundsToClaim : SaltPayRegistrationError(15) + object ClaimTransactionFailed : SaltPayRegistrationError(16) + object NotClaimed : SaltPayRegistrationError(17) companion object { const val EMPTY_MESSAGE = "" diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayAction.kt index 54876f0a64..8a8fb9cf2e 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayAction.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayAction.kt @@ -1,8 +1,10 @@ package com.tangem.tap.features.onboarding.products.wallet.saltPay.redux +import com.tangem.blockchain.common.Amount import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayConfig import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayRegistrationManager import org.rekotlin.Action +import java.math.BigDecimal /** [REDACTED_AUTHOR] @@ -13,21 +15,25 @@ sealed class OnboardingSaltPayAction : Action { val registrationManager: SaltPayRegistrationManager, val saltPayConfig: SaltPayConfig, ) : Init() - - // only for tests - object DiscardBackupSteps : Init() } - data class SetIsBusy(val isBusy: Boolean) : OnboardingSaltPayAction() + data class SetInProgress(val isInProgress: Boolean) : OnboardingSaltPayAction() + data class SetClaimRefreshInProgress(val isInProgress: Boolean) : OnboardingSaltPayAction() + data class SetAccessCode(val accessCode: String?) : OnboardingSaltPayAction() object Update : OnboardingSaltPayAction() object RegisterCard : OnboardingSaltPayAction() - object KYCStart : OnboardingSaltPayAction() - object OnFinishKYC : OnboardingSaltPayAction() + object OpenUtorgKYC : OnboardingSaltPayAction() + object UtorgKYCRedirectSuccess : OnboardingSaltPayAction() + object RegisterKYC : OnboardingSaltPayAction() + object Claim : OnboardingSaltPayAction() + object RefreshClaim : OnboardingSaltPayAction() data class TrySetPin(val pin: String) : OnboardingSaltPayAction() data class SetPin(val pin: String) : OnboardingSaltPayAction() data class SetStep(val newStep: SaltPayRegistrationStep? = null) : OnboardingSaltPayAction() + data class SetAmountToClaim(val amount: Amount?) : OnboardingSaltPayAction() + data class SetTokenBalance(val balanceValue: BigDecimal) : OnboardingSaltPayAction() } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt index 16c7594774..f0ff158c8f 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt @@ -1,6 +1,7 @@ package com.tangem.tap.features.onboarding.products.wallet.saltPay.redux import com.tangem.Message +import com.tangem.blockchain.common.Amount import com.tangem.blockchain.extensions.successOr import com.tangem.common.Filter import com.tangem.common.core.TangemSdkError @@ -13,6 +14,8 @@ import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchErrorNotification import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.AppState +import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.domain.TangemSigner import com.tangem.tap.domain.TapError import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState @@ -22,6 +25,7 @@ import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.SaltPay import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayRegistrationError import com.tangem.tap.scope import com.tangem.tap.store +import com.tangem.tap.tangemSdk import com.tangem.tap.tangemSdkManager import com.tangem.wallet.R import kotlinx.coroutines.launch @@ -57,8 +61,55 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App fun getState(): OnboardingSaltPayState = getOnboardingWalletState().onboardingSaltPayState!! when (action) { + is OnboardingSaltPayAction.Update -> { + handleInProgress = true + + val state = getState() + scope.launch { + val updateResult = state.saltPayManager.updateSaltPayStatus( + amountToClaim = state.amountToClaim, + step = state.step, + ).successOr { + onException(it.error) + return@launch + } + + handleInProgress = false + withMainContext { + // store.dispatch(OnboardingSaltPayAction.SetStep(updateResult.step)) + store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayRegistrationStep.KycWaiting)) + } + } + + // handleIsBusy = true + // scope.launch { + // checkGasIfNeeded(state.saltPayManager, state.step).successOr { + // onException(it.error) + // return@launch + // } + // + // registerKYCIfNeeded(state).successOr { + // onException(it.error) + // return@launch + // } + // + // val amount = getAmountToClaimIfNeeded(state)?.let { + // dispatchOnMain(OnboardingSaltPayAction.SetAmountToClaim(it)) + // } + // + // val newStep = checkRegistration(state, amount != null).successOr { + // onException(it.error) + // return@launch + // } + // + // withMainContext { + // handleIsBusy = false + // store.dispatch(OnboardingSaltPayAction.SetStep(newStep)) + // } + // } + } is OnboardingSaltPayAction.RegisterCard -> { - handleIsBusy = true + handleInProgress = true val state = getState() scope.launch { @@ -107,18 +158,30 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App } withMainContext { - handleIsBusy = false + handleInProgress = false store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayRegistrationStep.KycIntro)) } } } - is OnboardingSaltPayAction.KYCStart -> { + is OnboardingSaltPayAction.OpenUtorgKYC -> { store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayRegistrationStep.KycStart)) } - is OnboardingSaltPayAction.OnFinishKYC -> { + is OnboardingSaltPayAction.UtorgKYCRedirectSuccess -> { + store.dispatch(OnboardingSaltPayAction.RegisterKYC) + } + is OnboardingSaltPayAction.RegisterKYC -> { + val state = getState() + handleInProgress = true scope.launch { - registerKYCIfNeeded(getState()).successOr { + registerKYCIfNeeded(state.saltPayManager, state.step).successOr { onException(it.error) + return@launch + } + handleInProgress = false + if (state.readyToClaim()) { + withMainContext { store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayRegistrationStep.Claim)) } + } else { + onError(SaltPayRegistrationError.NoFundsToClaim) } } } @@ -131,30 +194,58 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App handleError = error } } - is OnboardingSaltPayAction.Update -> { - handleIsBusy = true + is OnboardingSaltPayAction.Claim -> { + val state = getState() + handleInProgress = true + + scope.launch { + val amountToClaim = getAmountToClaimIfNeeded(state.saltPayManager, state.amountToClaim).guard { + onException(SaltPayRegistrationError.NoFundsToClaim) + return@launch + } + + val scanResponse = store.state.globalState.scanResponse + ?: store.state.globalState.onboardingState.onboardingManager?.scanResponse + val signer = TangemSigner( + card = scanResponse!!.card, + tangemSdk = tangemSdk, + initialMessage = Message(), + accessCode = state.accessCode, + ) { signResponse -> + store.dispatch( + GlobalAction.UpdateWalletSignedHashes( + walletSignedHashes = signResponse.totalSignedHashes, + walletPublicKey = state.saltPayManager.walletPublicKey, + remainingSignatures = signResponse.remainingSignatures, + ), + ) + } + state.saltPayManager.claim(amountToClaim.value!!, signer).successOr { + dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayRegistrationStep.Claim)) + onException(it.error) + return@launch + } + + dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayRegistrationStep.ClaimInProgress)) + dispatchOnMain(OnboardingSaltPayAction.RefreshClaim) + } + } + is OnboardingSaltPayAction.RefreshClaim -> { + handleInProgress = true + handleClaimRefreshInProgress = true val state = getState() scope.launch { - checkGasIfNeeded(state.saltPayManager, state.step).successOr { - onException(it.error) + val balance = state.saltPayManager.getTokenAmount().successOr { + handleException = it.error + handleClaimRefreshInProgress = false return@launch } - registerKYCIfNeeded(state).successOr { - onException(it.error) - return@launch - } - - val newStep = checkRegistration(state).successOr { - onException(it.error) - return@launch - } - - withMainContext { - handleIsBusy = false - store.dispatch(OnboardingSaltPayAction.SetStep(newStep)) - } + handleInProgress = false + handleClaimRefreshInProgress = false + dispatchOnMain(OnboardingSaltPayAction.SetTokenBalance(balance)) + dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayRegistrationStep.ClaimSuccess)) } } else -> { @@ -163,22 +254,93 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App } } -private var handleIsBusy: Boolean = false +data class UpdateResult( + val step: SaltPayRegistrationStep = SaltPayRegistrationStep.None, + val amountToClaim: Amount? = null, +) + +suspend fun SaltPayRegistrationManager.updateSaltPayStatus( + amountToClaim: Amount?, + step: SaltPayRegistrationStep, +): Result { + Timber.d("updateSaltPayStatus") + var updateResult = UpdateResult() + + checkGasIfNeeded(this, step).successOr { + return Result.Failure(it.error) + + } + registerKYCIfNeeded(this, step).successOr { + return Result.Failure(it.error) + + } + val saltPayStep = checkRegistration(this).successOr { + return Result.Failure(it.error) + + } + + val fetchedAmountToClaim = getAmountToClaimIfNeeded(this, amountToClaim) + updateResult = when { + saltPayStep == SaltPayRegistrationStep.Claim && fetchedAmountToClaim == null -> { + updateResult.copy( + step = SaltPayRegistrationStep.ClaimSuccess, + amountToClaim = fetchedAmountToClaim, + ) + } + else -> updateResult.copy( + step = saltPayStep, + amountToClaim = fetchedAmountToClaim, + ) + } + + Timber.d("updateSaltPayStatus: success: %s", updateResult) + return Result.Success(updateResult) +} + +private suspend fun getAmountToClaimIfNeeded( + saltPayManager: SaltPayRegistrationManager, + amountToClaim: Amount?, +): Amount? { + Timber.d("getAmountToClaimIfNeeded: for amount: %s", amountToClaim) + if (amountToClaim != null) { + Timber.d("getAmountToClaimIfNeeded: checks no need, amount already persist") + return amountToClaim + } + + return when (val result = saltPayManager.getAmountToClaim()) { + is Result.Success -> { + Timber.d("getAmountToClaimIfNeeded: success: %s", result.data) + return result.data + } + is Result.Failure -> { + Timber.e(result.error, "getAmountToClaimIfNeeded: failure") + null + } + } +} + +private var handleClaimRefreshInProgress: Boolean = false set(value) { field = value - store.dispatchOnMain(OnboardingSaltPayAction.SetIsBusy(value)) + store.dispatchOnMain(OnboardingSaltPayAction.SetClaimRefreshInProgress(value)) + } + +private var handleInProgress: Boolean = false + set(value) { + field = value + store.dispatchOnMain(OnboardingSaltPayAction.SetInProgress(value)) } private suspend fun onError(error: SaltPayRegistrationError) { withMainContext { - handleIsBusy = false + handleInProgress = false handleError = error } } private suspend fun onException(error: Throwable) { withMainContext { - handleIsBusy = false + handleInProgress = false handleException = error } } @@ -221,35 +383,58 @@ private suspend fun checkGasIfNeeded( saltPayManager: SaltPayRegistrationManager, step: SaltPayRegistrationStep, ): Result { + Timber.d("checkGasIfNeeded: for step: %s", step) if (step == SaltPayRegistrationStep.KycStart || step == SaltPayRegistrationStep.KycWaiting || - step == SaltPayRegistrationStep.Finished + step == SaltPayRegistrationStep.ClaimSuccess ) { + Timber.d("checkGasIfNeeded: no need to check for step: %s", step) return Result.Success(Unit) } - return saltPayManager.checkHasGas() + val result = saltPayManager.checkHasGas() + when (result) { + is Result.Success -> Timber.d("checkGasIfNeeded: has GAS") + is Result.Failure -> Timber.d("checkGasIfNeeded: has NO GAS") + } + return result } -private suspend fun registerKYCIfNeeded(state: OnboardingSaltPayState): Result { - if (state.step != SaltPayRegistrationStep.KycStart) { +private suspend fun registerKYCIfNeeded( + saltPayManager: SaltPayRegistrationManager, + step: SaltPayRegistrationStep, +): Result { + Timber.d("registerKYCIfNeeded: step: %s", step) + if (step != SaltPayRegistrationStep.KycStart) { + Timber.d("registerKYCIfNeeded: return Success, because step != KycStart") return Result.Success(Unit) } withMainContext { + Timber.d("registerKYCIfNeeded: set new step: KycWaiting") store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayRegistrationStep.KycWaiting)) } - return state.saltPayManager.registerKYC() + + Timber.d("saltPayManager.registerKYC()") + return saltPayManager.registerKYC() } private suspend fun checkRegistration( - state: OnboardingSaltPayState, + saltPayManager: SaltPayRegistrationManager, ): Result { + Timber.d("checkRegistration") return try { - val registrationResponseItem = state.saltPayManager.checkRegistration().successOr { return it } + val registrationResponseItem = saltPayManager.checkRegistration().successOr { + Timber.e(it.error, "checkRegistration: failure") + return it + } + + Timber.d("checkRegistration: success: try convert to SaltPayStep") val step = registrationResponseItem.toSaltPayStep() + Timber.d("checkRegistration: step: %s", step) Result.Success(step) } catch (ex: SaltPayRegistrationError) { + Timber.e(ex, "checkRegistration: step") Result.Failure(ex) } } @@ -260,8 +445,8 @@ fun RegistrationResponse.Item.toSaltPayStep(): SaltPayRegistrationStep { passed != true -> throw SaltPayRegistrationError.CardNotPassed(this.error) disabledByAdmin == true -> throw SaltPayRegistrationError.CardDisabled(this.error) - // go to success screen - active == true -> SaltPayRegistrationStep.Finished + // go to claim screen + active == true -> SaltPayRegistrationStep.Claim // pinSet is false, go toPin screen pinSet == false -> SaltPayRegistrationStep.NeedPin diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayReducer.kt index ac9ed3c67b..264829c157 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayReducer.kt @@ -1,8 +1,6 @@ package com.tangem.tap.features.onboarding.products.wallet.saltPay.redux -import com.tangem.tap.features.onboarding.products.wallet.redux.BackupStep import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState -import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletStep import org.rekotlin.Action /** @@ -31,20 +29,6 @@ private fun internalReduce(anyAction: Action, onboardingWalletState: OnboardingW val oldState = onboardingWalletState.onboardingSaltPayState ?: return onboardingWalletState val newState = when (action) { - OnboardingSaltPayAction.Init.DiscardBackupSteps -> { - return onboardingWalletState.copy( - step = OnboardingWalletStep.SaltPay, - backupState = onboardingWalletState.backupState.copy( - backupStep = BackupStep.Finished, - maxBackupCards = 1, - canSkipBackup = false, - ), - onboardingSaltPayState = oldState.copy( - isTest = true, - step = SaltPayRegistrationStep.NeedPin, - ), - ) - } is OnboardingSaltPayAction.SetAccessCode -> { oldState.copy( accessCode = action.accessCode, @@ -62,19 +46,27 @@ private fun internalReduce(anyAction: Action, onboardingWalletState: OnboardingW ) } } - is OnboardingSaltPayAction.SetIsBusy -> { + is OnboardingSaltPayAction.SetAmountToClaim -> { oldState.copy( - isBusy = action.isBusy, + amountToClaim = action.amount, + ) + } + is OnboardingSaltPayAction.SetTokenBalance -> { + oldState.copy( + tokenAmount = oldState.tokenAmount.copy(value = action.balanceValue), + ) + } + is OnboardingSaltPayAction.SetInProgress -> { + oldState.copy( + inProgress = action.isInProgress, + ) + } + is OnboardingSaltPayAction.SetClaimRefreshInProgress -> { + oldState.copy( + claimInProgress = action.isInProgress, ) } else -> oldState - // is OnboardingSaltPayAction.Init.PrepareAndSetDependencies -> oldState - // OnboardingSaltPayAction.OnCreate -> oldState - // is OnboardingSaltPayAction.OnFinishKYC -> oldState - // is OnboardingSaltPayAction.Register -> oldState - // is OnboardingSaltPayAction.TrySetPin -> oldState - // is OnboardingSaltPayAction.Update -> oldState - // is OnboardingSaltPayAction.TryUpdateStep -> oldState } return when (newState) { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayState.kt index 2ecaa4b382..4851a49fb3 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayState.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayState.kt @@ -1,9 +1,12 @@ package com.tangem.tap.features.onboarding.products.wallet.saltPay.redux +import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.common.extensions.guard import com.tangem.common.extensions.toHexString import com.tangem.common.json.MoshiJsonConverter +import com.tangem.domain.common.SaltPayWorkaround import com.tangem.domain.common.ScanResponse import com.tangem.network.api.paymentology.PaymentologyApiService import com.tangem.tap.common.toggleWidget.WidgetState @@ -17,6 +20,7 @@ import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.tap.persistence.SaltPayRegistrationStorage import com.tangem.tap.preferencesStorage import com.tangem.tap.store +import java.math.BigDecimal /** [REDACTED_AUTHOR] @@ -27,14 +31,18 @@ data class OnboardingSaltPayState( val saltPayConfig: SaltPayConfig = SaltPayConfig.stub(), val pinCode: String? = null, val accessCode: String? = null, - val step: SaltPayRegistrationStep = SaltPayRegistrationStep.NeedPin, + val amountToClaim: Amount? = null, + val tokenAmount: Amount = Amount(SaltPayWorkaround.tokenFrom(Blockchain.SaltPay), BigDecimal.ZERO), + val step: SaltPayRegistrationStep = SaltPayRegistrationStep.None, val saltPayCardArtworkUrl: String? = null, - val isBusy: Boolean = false, - val isTest: Boolean = false, + val inProgress: Boolean = false, + val claimInProgress: Boolean = false, ) { val mainButtonState: WidgetState - get() = if (isBusy) ProgressState.Loading else ProgressState.Done + get() = if (inProgress) ProgressState.Loading else ProgressState.Done + + fun readyToClaim(): Boolean = amountToClaim != null val pinLength: Int = 4 @@ -108,11 +116,14 @@ data class OnboardingSaltPayState( } enum class SaltPayRegistrationStep { + None, NoGas, NeedPin, CardRegistration, KycIntro, KycStart, KycWaiting, - Finished; + Claim, + ClaimInProgress, + ClaimSuccess; } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt index 4ba27934ae..5174bf7329 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt @@ -2,26 +2,34 @@ package com.tangem.tap.features.onboarding.products.wallet.saltPay.ui import android.view.View import android.view.ViewGroup +import android.view.animation.OvershootInterpolator import android.widget.ProgressBar +import androidx.annotation.LayoutRes +import androidx.annotation.StringRes +import androidx.constraintlayout.widget.ConstraintSet import androidx.core.view.children +import androidx.transition.TransitionManager import coil.load import coil.size.Scale import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.button.MaterialButton import com.tangem.common.extensions.guard import com.tangem.tap.common.compose.PinCodeWidget -import com.tangem.tap.common.extensions.beginDelayedTransition 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.show import com.tangem.tap.common.extensions.stop +import com.tangem.tap.common.extensions.toFormattedCurrencyString import com.tangem.tap.common.feedback.SupportInfo 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.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 @@ -34,8 +42,12 @@ import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.Onboardi import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayState import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.SaltPayRegistrationStep import com.tangem.tap.features.onboarding.products.wallet.ui.OnboardingWalletFragment +import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.tap.store import com.tangem.wallet.R +import com.tangem.wallet.databinding.LayoutOnboardingContainerBottomBinding +import com.tangem.wallet.databinding.LayoutOnboardingContainerTopBinding +import com.tangem.wallet.databinding.LayoutOnboardingMainBinding /** [REDACTED_AUTHOR] @@ -45,6 +57,11 @@ internal class OnboardingSaltPayView( ) { private val toolbar: MaterialToolbar by lazy { walletFragment.binding.toolbar } + private val claimBinding: LayoutOnboardingMainBinding by lazy { walletFragment.bindingSaltPay.claim } + + private val btnRefreshBalanceWidget by lazy { + RefreshBalanceWidget(claimBinding.onboardingTopContainer.onboardingWalletContainer) + } private var progressButton: SaltPayProgressButton? = null @@ -86,11 +103,6 @@ internal class OnboardingSaltPayView( // walletFragment.loadImageIntoImageView(state.onboardingSaltPayState.saltPayCardArtworkUrl, imvFrontCard) // } // walletFragment.loadImageIntoImageView(state.cardArtworkUrl, imvFirstBackupCard) - - //TODO: at now we can hide the image only by changing alpha channel to 0, because - // the OnboardingWalletFragment and WalletCardsWidget manipulate it visibility through changing - // View.VISIBILITY states. - // imvSecondBackupCard.alpha = 0f } fun showAddBackupCards(state: BackupState) = with(walletFragment.binding) { @@ -123,10 +135,9 @@ internal class OnboardingSaltPayView( SaltPayRegistrationStep.KycIntro -> handleKycIntro() SaltPayRegistrationStep.KycStart -> handleKycStart(state) SaltPayRegistrationStep.KycWaiting -> handleKycWaiting() - // SaltPayRegistrationStep.Claim -> handleKycWaiting() - // SaltPayRegistrationStep.ClaimInProgress -> handleKycWaiting() - // SaltPayRegistrationStep.ClaimSuccess -> handleKycWaiting() - SaltPayRegistrationStep.Finished -> handleFinished() + SaltPayRegistrationStep.Claim -> handleClaim(state) + SaltPayRegistrationStep.ClaimInProgress -> handleClaim(state) + SaltPayRegistrationStep.ClaimSuccess -> handleClaim(state) } progressButton?.changeState(state.mainButtonState) } @@ -138,7 +149,7 @@ internal class OnboardingSaltPayView( } private fun handleNeedPin() = with(walletFragment.bindingSaltPay) { - toolbar.title = walletFragment.getString(R.string.onboarding_navbar_pin) + toolbar.title = getString(R.string.onboarding_navbar_pin) showOnlyView(pinCode.root) { progressButton = SaltPayProgressButton(pinCode.root) progressButton?.mainView?.isEnabled = false @@ -157,7 +168,7 @@ internal class OnboardingSaltPayView( } private fun handleCardRegistration() = with(walletFragment.bindingSaltPay) { - toolbar.title = walletFragment.getString(R.string.onboarding_navbar_register_wallet) + toolbar.title = getString(R.string.onboarding_navbar_register_wallet) showOnlyView(connectCard.root) { progressButton = SaltPayProgressButton(connectCard.root) connectCard.btnConnect.setOnClickListener { @@ -167,17 +178,17 @@ internal class OnboardingSaltPayView( } private fun handleKycIntro() = with(walletFragment.bindingSaltPay) { - toolbar.title = walletFragment.getString(R.string.onboarding_navbar_kyc_start) + toolbar.title = getString(R.string.onboarding_navbar_kyc_start) showOnlyView(verifyIdentity.root) { progressButton = SaltPayProgressButton(verifyIdentity.root) verifyIdentity.btnVerify.setOnClickListener { - store.dispatch(OnboardingSaltPayAction.KYCStart) + store.dispatch(OnboardingSaltPayAction.OpenUtorgKYC) } } } private fun handleKycStart(state: OnboardingSaltPayState) = with(walletFragment.bindingSaltPay) { - toolbar.title = walletFragment.getString(R.string.onboarding_navbar_kyc_start) + toolbar.title = getString(R.string.onboarding_navbar_kyc_start) showOnlyView(webvVerifyIdentity) { progressButton = null webvVerifyIdentity.configureSettings() @@ -185,7 +196,7 @@ internal class OnboardingSaltPayView( successUrl = state.saltPayManager.kycUrlProvider.doneUrl, onSuccess = { webvVerifyIdentity.stop() - store.dispatch(OnboardingSaltPayAction.OnFinishKYC) + store.dispatch(OnboardingSaltPayAction.UtorgKYCRedirectSuccess) }, ) webvVerifyIdentity.webChromeClient = UtorgWebChromeClient(walletFragment.requireActivity()) @@ -194,7 +205,7 @@ internal class OnboardingSaltPayView( } private fun handleKycWaiting() = with(walletFragment.bindingSaltPay) { - toolbar.title = walletFragment.getString(R.string.onboarding_navbar_kyc_waiting) + toolbar.title = getString(R.string.onboarding_navbar_kyc_waiting) showOnlyView(verifyIdentityInProgress.root) { progressButton = SaltPayProgressButton(verifyIdentityInProgress.root) verifyIdentityInProgress.btnOpenSupportChat.setOnClickListener { @@ -206,56 +217,105 @@ internal class OnboardingSaltPayView( } } - // private fun setupTopUpWalletState(state: OnboardingNoteState) = with(binding.onboardingActionContainer) { - // if (state.isBuyAllowed) { - // btnMainAction.setText(com.tangem.wallet.R.string.onboarding_top_up_button_but_crypto) - // btnMainAction.setOnClickListener { - // com.tangem.tap.store.dispatch(com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction.TopUp) - // } - // - // btnAlternativeAction.isVisible = true - // btnAlternativeAction.setText(com.tangem.wallet.R.string.onboarding_top_up_button_show_wallet_address) - // btnAlternativeAction.setOnClickListener { - // com.tangem.tap.store.dispatch(com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction.ShowAddressInfoDialog) - // } - // } else { - // btnMainAction.setText(com.tangem.wallet.R.string.onboarding_button_receive_crypto) - // btnMainAction.setOnClickListener { - // com.tangem.tap.store.dispatch(com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction.ShowAddressInfoDialog) - // } - // - // btnAlternativeAction.isVisible = false - // } - // - // tvHeader.setText(com.tangem.wallet.R.string.onboarding_top_up_header) - // if (state.balanceNonCriticalError == null) { - // tvBody.setText(com.tangem.wallet.R.string.onboarding_top_up_body) - // } else { - // state.walletBalance.amountToCreateAccount?.let { amount -> - // val tvBodyMessage = getString( - // com.tangem.wallet.R.string.onboarding_top_up_body_no_account_error, - // amount, state.walletBalance.currency.currencySymbol, - // ) - // tvBody.text = tvBodyMessage - // } - // } - // - // btnRefreshBalanceWidget.changeState(state.walletBalance.state) - // if (btnRefreshBalanceWidget.isShowing != true) { - // btnRefreshBalanceWidget.mainView.setOnClickListener { - // com.tangem.tap.store.dispatch(com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction.Balance.Update) - // } - // } - // binding.onboardingTopContainer.imvCardBackground - // .setBackgroundDrawable(requireContext().getDrawableCompat(com.tangem.wallet.R.drawable.shape_rectangle_rounded_8)) - // updateConstraints(state.currentStep, com.tangem.wallet.R.layout.lp_onboarding_topup_wallet) - // } + private val topContainer: LayoutOnboardingContainerTopBinding by lazy { claimBinding.onboardingTopContainer } + private val actionContainer: LayoutOnboardingContainerBottomBinding by lazy { claimBinding.onboardingActionContainer } + private var previousStep: SaltPayRegistrationStep = SaltPayRegistrationStep.None - private fun handleFinished() { - walletFragment.binding.onboardingRoot.beginDelayedTransition() - walletFragment.bindingSaltPay.onboardingSaltpayContainer.hide() - walletFragment.binding.onboardingWalletContainer.show() - walletFragment.showSuccess() + private fun handleClaim(state: OnboardingSaltPayState) = with(walletFragment.bindingSaltPay) { + toolbar.title = getString(R.string.onboarding_navbar_claim) + val btnMain = actionContainer.btnContainer.findViewById(R.id.btn_main_action) + val tvHeader = actionContainer.tvHeader + val tvBody = actionContainer.tvBody + + showOnlyView(claimBinding.root) { + // topContainer.progress.pbState.hide() + progressButton = SaltPayProgressButton(actionContainer.root) + topContainer.imvCardBackground.apply { + setBackgroundDrawable(context.getDrawableCompat(R.drawable.shape_rectangle_rounded_8)) + } + topContainer.imvFrontCard.load(R.drawable.img_salt_pay_visa) { + scale(Scale.FILL) + crossfade(enable = true) + } + actionContainer.btnAlternativeAction.hide() + updateConstraints(state.step, R.layout.lp_onboarding_topup_wallet) + } + + btnRefreshBalanceWidget.changeState(if (state.claimInProgress) ProgressState.Loading else ProgressState.Done) + if (btnRefreshBalanceWidget.isShowing != true) { + btnRefreshBalanceWidget.mainView.setOnClickListener { + store.dispatch(OnboardingSaltPayAction.RefreshClaim) + } + } + val tokenAmountString = tokenAmountString(state) + topContainer.onboardingTvBalance.tvBalanceValue.text = tokenAmountString ?: "" + topContainer.onboardingTvBalance.tvBalanceCurrency.text = "" + + when (state.step) { + SaltPayRegistrationStep.Claim -> { + btnRefreshBalanceWidget.mainView.hide() + claimValueString(state)?.let { + tvHeader.text = getString(R.string.onboarding_title_claim, it) + } + tvBody.text = getString(R.string.onboarding_subtitle_claim) + + btnMain.text = getString(R.string.onboarding_button_claim) + btnMain.setOnClickListener { + store.dispatch(OnboardingSaltPayAction.Claim) + } + } + SaltPayRegistrationStep.ClaimInProgress -> { + btnRefreshBalanceWidget.mainView.show() + tvHeader.text = getString(R.string.onboarding_title_claim_progress) + tvBody.text = getString(R.string.onboarding_subtitle_claim_progress) + btnMain.text = getString(R.string.onboarding_button_claim) + } + SaltPayRegistrationStep.ClaimSuccess -> { + tvHeader.setText(R.string.common_success) + tvBody.setText(R.string.onboarding_subtitle_success_claim) + + btnMain.setText(R.string.onboarding_button_continue_wallet) + btnMain.setOnClickListener { + walletFragment.showConfetti(false) + store.dispatch(OnboardingWalletAction.FinishOnboarding) + } + + btnRefreshBalanceWidget.mainView.setOnClickListener(null) + updateConstraints(state.step, R.layout.lp_onboarding_done_activation) + walletFragment.showConfetti(true) + } + else -> {} + } + + // need to recreate after any step + progressButton = SaltPayProgressButton(actionContainer.root) + } + + private fun tokenAmountString(state: OnboardingSaltPayState): String? { + return state.tokenAmount.value?.toFormattedCurrencyString( + decimals = state.tokenAmount.decimals, + currency = state.tokenAmount.currencySymbol, + ) + } + + private fun claimValueString(state: OnboardingSaltPayState): String? { + return state.amountToClaim?.value?.toFormattedCurrencyString( + decimals = state.amountToClaim.decimals, + currency = state.amountToClaim.currencySymbol, + ) + } + + private fun updateConstraints(currentStep: SaltPayRegistrationStep, @LayoutRes layoutId: Int) { + if (this.previousStep == currentStep) return + + with(claimBinding.onboardingTopContainer) { + val constraintSet = ConstraintSet() + constraintSet.clone(root.context, layoutId) + constraintSet.applyTo(onboardingWalletContainer) + val transition = InternalNoteLayoutTransition() + transition.interpolator = OvershootInterpolator() + TransitionManager.beginDelayedTransition(onboardingWalletContainer, transition) + } } private fun showOnlyView(view: View, onShowListener: (() -> Unit)? = null) { @@ -265,6 +325,14 @@ internal class OnboardingSaltPayView( view.show { onShowListener?.invoke() } } + private fun getString(@StringRes resId: Int): String { + return walletFragment.getString(resId) + } + + private fun getString(@StringRes resId: Int, vararg formatArgs: Any?): String { + return walletFragment.getString(resId, *formatArgs) + } + private class SaltPayProgressButton( root: ViewGroup, ) : IndeterminateProgressButtonWidget(findButton(root), findProgress(root)) { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt index 75c027dfee..47b6beb3aa 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt @@ -169,9 +169,9 @@ class OnboardingWalletFragment : BaseFragment(R.layout.fragment_onboarding_walle } private fun setupCreateWalletState() = with(binding) { - layoutButtonsCommon.btnMainAction.setText(R.string.onboarding_create_wallet_button_create_wallet) - layoutButtonsCommon.btnMainAction.setOnClickListener { store.dispatch(OnboardingWalletAction.CreateWallet) } - layoutButtonsCommon.btnAlternativeAction.hide() + layoutButtonsCommon.btnWalletMainAction.setText(R.string.onboarding_create_wallet_button_create_wallet) + layoutButtonsCommon.btnWalletMainAction.setOnClickListener { store.dispatch(OnboardingWalletAction.CreateWallet) } + layoutButtonsCommon.btnWalletAlternativeAction.hide() toolbar.title = getText(R.string.onboarding_getting_started) @@ -205,12 +205,12 @@ class OnboardingWalletFragment : BaseFragment(R.layout.fragment_onboarding_walle tabLayoutBackupInfo.show() with(layoutButtonsCommon) { - btnMainAction.text = getText(R.string.onboarding_button_backup_now) - btnMainAction.setOnClickListener { store.dispatch(BackupAction.StartBackup) } + btnWalletMainAction.text = getText(R.string.onboarding_button_backup_now) + btnWalletMainAction.setOnClickListener { store.dispatch(BackupAction.StartBackup) } - btnAlternativeAction.text = getText(R.string.onboarding_button_skip_backup) - btnAlternativeAction.setOnClickListener { store.dispatch(BackupAction.DismissBackup) } - btnAlternativeAction.show(state.canSkipBackup && !isSaltPay) + btnWalletAlternativeAction.text = getText(R.string.onboarding_button_skip_backup) + btnWalletAlternativeAction.setOnClickListener { store.dispatch(BackupAction.DismissBackup) } + btnWalletAlternativeAction.show(state.canSkipBackup && !isSaltPay) } animator.showBackupIntro(state) } @@ -223,9 +223,9 @@ class OnboardingWalletFragment : BaseFragment(R.layout.fragment_onboarding_walle ) with(layoutButtonsCommon) { - btnMainAction.text = getString(R.string.onboarding_button_scan_origin_card) - btnAlternativeAction.hide() - btnMainAction.setOnClickListener { store.dispatch(BackupAction.ScanPrimaryCard) } + btnWalletMainAction.text = getString(R.string.onboarding_button_scan_origin_card) + btnWalletAlternativeAction.hide() + btnWalletMainAction.setOnClickListener { store.dispatch(BackupAction.ScanPrimaryCard) } } animator.showScanOriginCard() @@ -323,16 +323,16 @@ class OnboardingWalletFragment : BaseFragment(R.layout.fragment_onboarding_walle if (isSaltPay) { tvHeader.text = getText(R.string.onboarding_saltpay_title_prepare_origin) tvBody.text = getString(R.string.onboarding_twins_interrupt_warning) - layoutButtonsCommon.btnMainAction.text = getText(R.string.onboarding_saltpay_button_backup_origin) + layoutButtonsCommon.btnWalletMainAction.text = getText(R.string.onboarding_saltpay_button_backup_origin) } else { tvHeader.text = getText(R.string.onboarding_title_prepare_origin) tvBody.text = getString( R.string.onboarding_subtitle_scan_primary_card_format, state.primaryCardId?.let { cardIdFormatter.getFormattedCardId(it) }, ) - layoutButtonsCommon.btnMainAction.text = getText(R.string.onboarding_button_backup_origin) + layoutButtonsCommon.btnWalletMainAction.text = getText(R.string.onboarding_button_backup_origin) } - layoutButtonsCommon.btnMainAction.setOnClickListener { store.dispatch(BackupAction.WritePrimaryCard) } + layoutButtonsCommon.btnWalletMainAction.setOnClickListener { store.dispatch(BackupAction.WritePrimaryCard) } animator.showWritePrimaryCard(state) } @@ -342,7 +342,7 @@ class OnboardingWalletFragment : BaseFragment(R.layout.fragment_onboarding_walle layoutButtonsAddCards.root.hide() layoutButtonsCommon.root.show() - layoutButtonsCommon.btnAlternativeAction.hide() + layoutButtonsCommon.btnWalletAlternativeAction.hide() imvCardBackground.hide() imvFirstBackupCard.show() @@ -356,7 +356,7 @@ class OnboardingWalletFragment : BaseFragment(R.layout.fragment_onboarding_walle if (isSaltPay) { tvHeader.text = getString(R.string.onboarding_saltpay_title_backup_card) tvBody.text = getString(R.string.onboarding_twins_interrupt_warning) - layoutButtonsCommon.btnMainAction.text = getString(R.string.onboarding_saltpay_title_backup_card) + layoutButtonsCommon.btnWalletMainAction.text = getString(R.string.onboarding_saltpay_title_backup_card) } else { val cardIdFormatter = CardIdFormatter(CardIdDisplayFormat.LastMasked(4)) tvHeader.text = getString(R.string.onboarding_title_backup_card_format, cardNumber) @@ -364,12 +364,12 @@ class OnboardingWalletFragment : BaseFragment(R.layout.fragment_onboarding_walle R.string.onboarding_subtitle_scan_backup_card_format, cardIdFormatter.getFormattedCardId(state.backupCardIds[cardNumber - 1]), ) - layoutButtonsCommon.btnMainAction.text = getString( + layoutButtonsCommon.btnWalletMainAction.text = getString( R.string.onboarding_button_backup_card_format, cardNumber, ) } - layoutButtonsCommon.btnMainAction.setOnClickListener { + layoutButtonsCommon.btnWalletMainAction.setOnClickListener { store.dispatch(BackupAction.WriteBackupCard(cardNumber)) } @@ -386,25 +386,33 @@ class OnboardingWalletFragment : BaseFragment(R.layout.fragment_onboarding_walle tabLayoutBackupInfo.hide() tvBody.text = getText(R.string.onboarding_subtitle_success_tangem_wallet_onboarding) - layoutButtonsCommon.btnMainAction.text = getText(R.string.onboarding_button_continue_wallet) - layoutButtonsCommon.btnAlternativeAction.hide() - layoutButtonsCommon.btnMainAction.setOnClickListener { - vConfetti.lavConfetti.cancelAnimation() - vConfetti.lavConfetti.hide() + layoutButtonsCommon.btnWalletMainAction.text = getText(R.string.onboarding_button_continue_wallet) + layoutButtonsCommon.btnWalletAlternativeAction.hide() + layoutButtonsCommon.btnWalletMainAction.setOnClickListener { + showConfetti(false) store.dispatch(OnboardingWalletAction.FinishOnboarding) } animator.showSuccess { flCardsContainer.hide() imvCardBackground.hide() - vConfetti.lavConfetti.show() - vConfetti.lavConfetti.playAnimation() + showConfetti(true) imvSuccess.alpha = 0f imvSuccess.show() imvSuccess.animate()?.alpha(1f)?.duration = 400 } } + internal fun showConfetti(show: Boolean) = with(binding.vConfetti) { + lavConfetti.show(show) + + if (show) { + lavConfetti.playAnimation() + } else { + lavConfetti.cancelAnimation() + } + } + override fun handleOnBackPressed() { store.dispatch(OnboardingWalletAction.OnBackPressed) } diff --git a/app/src/main/res/layout-h680dp/layout_onboarding_container_bottom.xml b/app/src/main/res/layout-h680dp/layout_onboarding_container_bottom.xml index e635e3ee6f..b14f8571de 100644 --- a/app/src/main/res/layout-h680dp/layout_onboarding_container_bottom.xml +++ b/app/src/main/res/layout-h680dp/layout_onboarding_container_bottom.xml @@ -13,7 +13,7 @@ android:layout_marginBottom="16dp" android:gravity="center" android:orientation="vertical" - app:layout_constraintBottom_toTopOf="@+id/fl_scan_card_container" + app:layout_constraintBottom_toTopOf="@+id/btn_container" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" @@ -39,7 +39,7 @@ android:layout_marginTop="10dp" android:layout_marginEnd="64dp" android:minLines="3" - app:layout_constraintBottom_toTopOf="@+id/fl_scan_card_container" + app:layout_constraintBottom_toTopOf="@+id/btn_container" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/tv_header" @@ -55,14 +55,14 @@ android:textStyle="bold" android:visibility="gone" app:buttonTint="@color/accent" - app:layout_constraintBottom_toTopOf="@+id/fl_scan_card_container" + app:layout_constraintBottom_toTopOf="@+id/btn_container" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/linearLayoutCompat" tools:visibility="visible" /> + tools:text="@string/home_button_scan" /> + tools:text="@string/home_button_scan" /> + + \ No newline at end of file diff --git a/app/src/main/res/values-de/strings_final.xml b/app/src/main/res/values-de/strings_final.xml index 0b1aa41913..1549136e24 100644 --- a/app/src/main/res/values-de/strings_final.xml +++ b/app/src/main/res/values-de/strings_final.xml @@ -142,4 +142,11 @@ Tap the SaltPay card Tap the Tangem card Support + Getting started + Claim %s + To get started, simply claim wxDAI to your wallet + Claim + Congratulations! Your first payment crypto card has been activated + Claiming + It will take a few seconds \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings_final.xml b/app/src/main/res/values-fr/strings_final.xml index 0b1aa41913..1549136e24 100644 --- a/app/src/main/res/values-fr/strings_final.xml +++ b/app/src/main/res/values-fr/strings_final.xml @@ -142,4 +142,11 @@ Tap the SaltPay card Tap the Tangem card Support + Getting started + Claim %s + To get started, simply claim wxDAI to your wallet + Claim + Congratulations! Your first payment crypto card has been activated + Claiming + It will take a few seconds \ No newline at end of file diff --git a/app/src/main/res/values-it/strings_final.xml b/app/src/main/res/values-it/strings_final.xml index 0b1aa41913..1549136e24 100644 --- a/app/src/main/res/values-it/strings_final.xml +++ b/app/src/main/res/values-it/strings_final.xml @@ -142,4 +142,11 @@ Tap the SaltPay card Tap the Tangem card Support + Getting started + Claim %s + To get started, simply claim wxDAI to your wallet + Claim + Congratulations! Your first payment crypto card has been activated + Claiming + It will take a few seconds \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings_final.xml b/app/src/main/res/values-ru/strings_final.xml index 8688a7b14c..82ff421103 100644 --- a/app/src/main/res/values-ru/strings_final.xml +++ b/app/src/main/res/values-ru/strings_final.xml @@ -140,4 +140,11 @@ Приложите SaltPay карту Приложите Tangem карту Чат + Начало работы + Запросить %s + Для начала работы просто запросите начисление xDai на свой кошелек + Запросить + Поздравляем! Ваша платежная крипто карта теперь активирована! + Запрашивается + Это займет несколько секунд diff --git a/app/src/main/res/values/strings_final.xml b/app/src/main/res/values/strings_final.xml index 8da137a723..fdbccb6f42 100644 --- a/app/src/main/res/values/strings_final.xml +++ b/app/src/main/res/values/strings_final.xml @@ -142,4 +142,11 @@ Tap the SaltPay card Tap the Tangem card Support + Getting started + Claim %s + To get started, simply claim wxDAI to your wallet + Claim + Congratulations! Your first payment crypto card has been activated + Claiming + It will take a few seconds diff --git a/dependencies.gradle b/dependencies.gradle index 7ecf2fc3a7..a7b0e1623d 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -3,7 +3,7 @@ ext.versions = [ build_gradle : '7.1.3', tangem_card_sdk : 'develop-165', // tangem_card_sdk : '0.0.1', - tangem_blockchain_sdk: 'develop-129', + tangem_blockchain_sdk: 'develop-130', // tangem_blockchain_sdk: '0.0.1', ]