From c9d5d65799a858ba5bdeac7c9de90db381eb2bdd Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 18 Aug 2022 12:49:06 +0400 Subject: [PATCH 001/113] Updated on 2026-08-14 --- .../features/details/ui/resetcard/ResetCardScreen.kt | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt index 2c07b092ad..eda04bf95c 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt @@ -2,6 +2,7 @@ package com.tangem.tap.features.details.ui.resetcard import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -86,11 +87,14 @@ fun ResetCardView( color = colorResource(id = R.color.text_secondary), ) - Spacer(modifier = modifier.size(44.dp)) + Spacer(modifier = modifier.size(28.dp)) Row( modifier = modifier .fillMaxWidth() - .padding(end = 20.dp), + .clickable( + onClick = { state.onAcceptWarningToggleClick(!state.accepted) }, + ) + .padding(top = 16.dp, bottom = 16.dp), ) { IconToggleButton( checked = state.accepted, @@ -117,10 +121,12 @@ fun ResetCardView( text = stringResource(id = R.string.reset_card_to_factory_warning_message), style = TangemTypography.body2, color = colorResource(id = R.color.text_secondary), + modifier = modifier + .padding(end = 20.dp), ) } - Spacer(modifier = modifier.size(32.dp)) + Spacer(modifier = modifier.size(16.dp)) Box( modifier = modifier .padding(start = 16.dp, end = 16.dp, bottom = 32.dp), From 21997b5e9bd24ac196e4b2d1c607e39853527576 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 15 Sep 2022 22:26:38 +0400 Subject: [PATCH 002/113] Updated on 2026-08-14 --- .../java/com/tangem/tap/TapApplication.kt | 2 +- .../java/com/tangem/tap/domain/TapErrors.kt | 1 + .../com/tangem/tap/domain/TapWalletManager.kt | 7 +- .../com/tangem/tap/domain/extensions/Card.kt | 6 +- .../domain/extensions/WalletManagerFactory.kt | 12 ++- .../domain/tasks/product/ScanProductTask.kt | 47 ++++------- .../features/details/redux/DetailsReducer.kt | 2 +- .../features/onboarding/OnboardingHelper.kt | 6 ++ .../wallet/redux/OnboardingWalletAction.kt | 4 +- .../redux/OnboardingWalletMiddleware.kt | 13 +-- .../wallet/redux/OnboardingWalletReducer.kt | 1 + .../wallet/redux/OnboardingWalletState.kt | 1 + .../wallet/ui/OnboardingWalletFragment.kt | 21 ++--- .../redux/middlewares/WalletMiddleware.kt | 14 +++- .../redux/reducers/OnWalletLoadedReducer.kt | 9 +- .../wallet/redux/reducers/WalletReducer.kt | 84 +++++++++++++++---- .../tap/features/wallet/ui/BalanceWidget.kt | 10 ++- .../tap/features/wallet/ui/WalletFragment.kt | 2 +- .../ui/wallet/SaltPaySingleWalletView.kt | 17 ++-- app/src/main/res/layout/fragment_wallet.xml | 1 - .../layout/layout_single_wallet_balance.xml | 24 +++--- app/src/main/res/values-de/strings_final.xml | 2 + app/src/main/res/values-fr/strings_final.xml | 2 + app/src/main/res/values-it/strings_final.xml | 2 + app/src/main/res/values-ru/strings_final.xml | 2 + app/src/main/res/values/strings_final.xml | 2 + .../com/tangem/domain/common/ScanResponse.kt | 38 ++++++--- .../tangem/domain/common/TapWorkarounds.kt | 47 ++++++++++- .../domain/common/extensions/Blockchain.kt | 2 + 29 files changed, 262 insertions(+), 119 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index 0c69e5de88..08da1e8b3b 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -147,7 +147,7 @@ class TapApplication : Application(), ImageLoaderFactory { } data class LogConfig( - val coil: Boolean = BuildConfig.DEBUG, + val coil: Boolean = false, val storeAction: Boolean = BuildConfig.DEBUG, val zendesk: Boolean = BuildConfig.DEBUG, ) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt index 9d3cc79eee..45a963ddaa 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt @@ -64,6 +64,7 @@ sealed class TapSdkError(override val messageResId: Int?) : Throwable(), TangemE object CardForDifferentApp : TapSdkError(R.string.alert_unsupported_card) object CardNotSupportedByRelease : TapSdkError(R.string.error_update_app) + object ScanPrimaryCard : TapSdkError(R.string.saltpay_backup_warning) } diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index 8bd8cf8ef7..16ab2b0629 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -160,6 +160,7 @@ class TapWalletManager { val primaryWalletManager = walletManagerFactory.makePrimaryWalletManager(data) if (blockchain != Blockchain.Unknown && primaryWalletManager != null) { + val blockchainNetwork = BlockchainNetwork.fromWalletManager(primaryWalletManager) val primaryToken = data.getPrimaryToken() dispatchOnMain(WalletAction.MultiWallet.SetPrimaryBlockchain(blockchain)) @@ -169,9 +170,9 @@ class TapWalletManager { } dispatchOnMain( WalletAction.MultiWallet.AddBlockchains( - listOf(BlockchainNetwork.fromWalletManager(primaryWalletManager)), - listOf(primaryWalletManager) - ) + listOf(blockchainNetwork), + listOf(primaryWalletManager), + ), ) } } diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt b/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt index c627a7de91..438b257916 100644 --- a/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt +++ b/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt @@ -6,6 +6,7 @@ import com.tangem.common.card.EllipticCurve import com.tangem.common.card.FirmwareVersion import com.tangem.common.extensions.toHexString import com.tangem.common.services.Result +import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.common.TapWorkarounds.isTangemNote import com.tangem.domain.common.TwinCardNumber @@ -23,11 +24,14 @@ val Card.isWalletDataSupported: Boolean val Card.isMultiwalletAllowed: Boolean get() { - return !isTangemTwin() && !isStart2Coin && !isTangemNote() + return !isTangemTwin() && !isStart2Coin && !isTangemNote() && !isSaltPay && (firmwareVersion >= FirmwareVersion.MultiWalletAvailable || getSingleWallet()?.curve == EllipticCurve.Secp256k1) } +val Card.isHdWalletAllowedByApp: Boolean + get() = settings.isHDWalletAllowed && !isSaltPay + fun Card.getSingleWallet(): CardWallet? { return wallets.firstOrNull() } diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt index 34b8289884..654eb2185c 100644 --- a/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt +++ b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt @@ -1,6 +1,10 @@ package com.tangem.tap.domain.extensions -import com.tangem.blockchain.common.* +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.DerivationParams +import com.tangem.blockchain.common.DerivationStyle +import com.tangem.blockchain.common.WalletManager +import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.common.card.Card import com.tangem.common.card.CardWallet import com.tangem.common.card.EllipticCurve @@ -35,10 +39,10 @@ fun WalletManagerFactory.makeWalletManagerForApp( walletPublicKey = wallet.publicKey, pairPublicKey = scanResponse.secondTwinPublicKey!!.hexToBytes(), blockchain = environmentBlockchain, - curve = wallet.curve + curve = wallet.curve, ) } - seedKey != null && derivationParams != null -> { + scanResponse.card.isHdWalletAllowedByApp && (seedKey != null && derivationParams != null) -> { val derivedKeys = scanResponse.derivedKeys[wallet.publicKey.toMapKey()] val derivationPath = when (derivationParams) { is DerivationParams.Default -> blockchain.derivationPath(derivationParams.style) @@ -51,7 +55,7 @@ fun WalletManagerFactory.makeWalletManagerForApp( blockchain = environmentBlockchain, seedKey = wallet.publicKey, derivedKey = derivedKey, - derivation = derivationParams + derivation = derivationParams, ) } else -> { diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt index e54c3744cb..1963d69143 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt @@ -4,7 +4,6 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.common.CompletionResult import com.tangem.common.card.Card import com.tangem.common.card.EllipticCurve -import com.tangem.common.card.FirmwareVersion import com.tangem.common.core.CardSession import com.tangem.common.core.CardSessionRunnable import com.tangem.common.core.TangemError @@ -18,10 +17,11 @@ import com.tangem.domain.common.ProductType import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TapWorkarounds.isExcluded import com.tangem.domain.common.TapWorkarounds.isNotSupportedInThatRelease -import com.tangem.domain.common.TapWorkarounds.isTangemNote +import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation import com.tangem.domain.common.TwinsHelper import com.tangem.domain.common.isTangemTwins +import com.tangem.domain.common.productType import com.tangem.operations.PreflightReadMode import com.tangem.operations.PreflightReadTask import com.tangem.operations.ScanTask @@ -32,6 +32,9 @@ import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand import com.tangem.tap.domain.TapSdkError import com.tangem.tap.domain.extensions.getPrimaryCurve import com.tangem.tap.domain.extensions.getSingleWallet +import com.tangem.tap.domain.extensions.hasNoWallets +import com.tangem.tap.domain.extensions.isHdWalletAllowedByApp +import com.tangem.tap.domain.extensions.isMultiwalletAllowed import com.tangem.tap.domain.tokens.CurrenciesRepository import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.preferencesStorage @@ -60,7 +63,6 @@ class ScanProductTask( } val commandProcessor = when { - card.isTangemNote() -> ScanNoteProcessor() card.isTangemTwins() -> ScanTwinProcessor() else -> ScanWalletProcessor(currenciesRepository, additionalBlockchainsToDerive) } @@ -87,29 +89,11 @@ class ScanProductTask( private fun getErrorIfExcludedCard(card: Card): TangemError? { if (card.isExcluded()) return TapSdkError.CardForDifferentApp if (card.isNotSupportedInThatRelease()) return TapSdkError.CardNotSupportedByRelease - + if (card.isSaltPay && card.hasNoWallets()) return TapSdkError.ScanPrimaryCard return null } } -private class ScanNoteProcessor : ProductCommandProcessor { - override fun proceed( - card: Card, - session: CardSession, - callback: (result: CompletionResult) -> Unit - ) { - callback( - CompletionResult.Success( - ScanResponse( - card = card, - productType = ProductType.Note, - walletData = session.environment.walletData, - ), - ), - ) - } -} - private class ScanWalletProcessor( private val currenciesRepository: CurrenciesRepository?, private val additionalBlockchainsToDerive: Collection? = null @@ -123,12 +107,13 @@ private class ScanWalletProcessor( ) { createMissingWalletsIfNeeded(card, session, callback) } + private fun createMissingWalletsIfNeeded( card: Card, session: CardSession, callback: (result: CompletionResult) -> Unit, ) { - if (card.wallets.isEmpty() || card.firmwareVersion < FirmwareVersion.MultiWalletAvailable) { + if (card.wallets.isEmpty() || !card.isMultiwalletAllowed) { startLinkingForBackupIfNeeded(card, session, callback) return } @@ -186,16 +171,16 @@ private class ScanWalletProcessor( ) { scope.launch { val derivations = collectDerivations(card) - if (derivations.isEmpty() || !card.settings.isHDWalletAllowed) { + if (derivations.isEmpty() || !card.isHdWalletAllowedByApp) { callback( CompletionResult.Success( ScanResponse( card = card, - productType = ProductType.Wallet, + productType = card.productType, walletData = session.environment.walletData, - primaryCard = primaryCard - ) - ) + primaryCard = primaryCard, + ), + ), ) return@launch } @@ -205,10 +190,10 @@ private class ScanWalletProcessor( is CompletionResult.Success -> { val response = ScanResponse( card = card, - productType = ProductType.Wallet, + productType = card.productType, walletData = session.environment.walletData, derivedKeys = result.data.entries, - primaryCard = primaryCard + primaryCard = primaryCard, ) callback(CompletionResult.Success(response)) } @@ -222,7 +207,7 @@ private class ScanWalletProcessor( val currenciesRepository = currenciesRepository ?: return emptyList() val cardCurrencies = currenciesRepository - .loadSavedCurrencies(card.cardId, card.settings.isHDWalletAllowed).toMutableList() + .loadSavedCurrencies(card.cardId, card.isHdWalletAllowedByApp).toMutableList() val blockchainsToDerive = cardCurrencies.ifEmpty { mutableListOf( diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt index b327e24452..d2013dba98 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt @@ -79,7 +79,7 @@ private fun prepareSecurityOptions(card: Card): ManageSecurityState { } } val allowedSecurityOptions = when { - card.isStart2Coin || card.isTangemNote() -> { + card.isStart2Coin || card.isTangemNote() || card.isSaltPay -> { EnumSet.of(SecurityOption.LongTap) } card.settings.isBackupAllowed -> { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index 7efb84deb6..0b58d4aa0b 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -2,6 +2,7 @@ package com.tangem.tap.features.onboarding import com.tangem.domain.common.ProductType import com.tangem.domain.common.ScanResponse +import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.domain.extensions.hasWallets import com.tangem.tap.preferencesStorage @@ -15,6 +16,10 @@ class OnboardingHelper { fun isOnboardingCase(response: ScanResponse): Boolean { val cardInfoStorage = preferencesStorage.usedCardsPrefStorage return when { + response.card.isSaltPay -> { + // response.card.backupStatus?.isActive != true //TODO: restore after presentation + false + } response.productType == ProductType.Twins -> { if (!response.twinsIsTwinned()) { true @@ -38,6 +43,7 @@ class OnboardingHelper { AppScreen.OnboardingOther } ProductType.Twins -> AppScreen.OnboardingTwins + ProductType.SaltPay -> AppScreen.OnboardingWallet } } } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt index 78631a152f..8431644dbf 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt @@ -10,7 +10,7 @@ sealed class OnboardingWalletAction : Action { object Done : OnboardingWalletAction() object FinishOnboarding : OnboardingWalletAction() - object ProceedBackup : OnboardingWalletAction() + data class ProceedBackup(val isSaltPay: Boolean = false) : OnboardingWalletAction() object LoadArtwork : OnboardingWalletAction() class SetArtworkUrl(val artworkUrl: String?) : OnboardingWalletAction() @@ -21,7 +21,7 @@ sealed class OnboardingWalletAction : Action { sealed class BackupAction : Action { object DetermineBackupStep : BackupAction() - object IntroduceBackup : BackupAction() + data class IntroduceBackup(val isSaltPay: Boolean = false) : BackupAction() object StartBackup : BackupAction() object DismissBackup : BackupAction() diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index 391d65cb96..de8e3a77c4 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -4,6 +4,7 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.common.CompletionResult import com.tangem.common.card.Card import com.tangem.domain.common.ScanResponse +import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.domain.common.extensions.withMainContext import com.tangem.operations.backup.BackupService import com.tangem.tap.* @@ -51,10 +52,10 @@ private fun handleWalletAction(action: Action) { OnboardingWalletAction.Init -> { val action = when { card == null -> { - OnboardingWalletAction.ProceedBackup + OnboardingWalletAction.ProceedBackup() } card.hasWallets() && card.backupStatus == Card.BackupStatus.NoBackup -> - OnboardingWalletAction.ProceedBackup + OnboardingWalletAction.ProceedBackup(card.isSaltPay) card.hasWallets() && card.backupStatus?.isActive == true -> BackupAction.FinishBackup else -> OnboardingWalletAction.GetToCreateWalletStep @@ -98,7 +99,7 @@ private fun handleWalletAction(action: Action) { ) ) onboardingManager.activationStarted(updatedResponse.card.cardId) - store.dispatch(OnboardingWalletAction.ProceedBackup) + store.dispatch(OnboardingWalletAction.ProceedBackup()) } is CompletionResult.Failure -> { // do nothing @@ -120,7 +121,7 @@ private fun handleWalletAction(action: Action) { store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) } } - OnboardingWalletAction.ProceedBackup -> { + is OnboardingWalletAction.ProceedBackup -> { val newAction = when (val backupState = backupService.currentState) { BackupService.State.FinalizingPrimaryCard -> BackupAction.PrepareToWritePrimaryCard is BackupService.State.FinalizingBackupCard -> BackupAction.PrepareToWriteBackupCard(backupState.index) @@ -128,7 +129,7 @@ private fun handleWalletAction(action: Action) { if (walletState.backupState.backupStep == BackupStep.InitBackup || walletState.backupState.backupStep == BackupStep.Finished ) { - BackupAction.IntroduceBackup + BackupAction.IntroduceBackup(action.isSaltPay) } else { null } @@ -319,7 +320,7 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) } is BackupAction.ResumeBackup -> { store.dispatch(GlobalAction.Onboarding.Start(null, true)) - store.dispatch(OnboardingWalletAction.ProceedBackup) + store.dispatch(OnboardingWalletAction.ProceedBackup()) store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet)) } is BackupAction.DismissBackup -> { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt index ab36973e17..13b86b3e08 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt @@ -42,6 +42,7 @@ class BackupReducer { is BackupAction.IntroduceBackup -> BackupState( backupStep = BackupStep.InitBackup, canSkipBackup = state.canSkipBackup, + isSaltPay = action.isSaltPay, ) BackupAction.StartAddingPrimaryCard -> state.copy(backupStep = BackupStep.ScanOriginCard) BackupAction.StartAddingBackupCards -> { 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 591573b35b..05a307b5b9 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 @@ -52,6 +52,7 @@ data class BackupState( val backupStep: BackupStep = BackupStep.InitBackup, val maxBackupCards: Int = 2, val canSkipBackup: Boolean = true, + val isSaltPay: Boolean = false, ) enum class AccessCodeError { 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 056d5a28bb..748c7f088b 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 @@ -181,6 +181,8 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), btnAlternativeAction.show(state.canSkipBackup) btnMainAction.setOnClickListener { store.dispatch(BackupAction.StartBackup) } btnAlternativeAction.setOnClickListener { store.dispatch(BackupAction.DismissBackup) } + + btnAlternativeAction.show(!state.isSaltPay) } startPostponedEnterTransition() @@ -232,9 +234,9 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), when (state.backupCardsNumber) { 0 -> { - cardsWidget.toFan() { - cardsWidget.getFirstBackupCardView().animate().alpha(0.6f).setDuration(200) - cardsWidget.getSecondBackupCardView().animate().alpha(0.2f).setDuration(200) + cardsWidget.toFan { + cardsWidget.getFirstBackupCardView().animate().alpha(0.6f).duration = 200 + cardsWidget.getSecondBackupCardView().animate().alpha(0.2f).duration = 200 } tvHeader.text = getText(R.string.onboarding_title_no_backup_cards) tvBody.text = getText(R.string.onboarding_subtitle_no_backup_cards) @@ -244,7 +246,7 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), tvBody.text = getText(R.string.onboarding_subtitle_one_backup_card) cardsWidget.toFan(false) - cardsWidget.getFirstBackupCardView().animate().alpha(1f).setDuration(400) + cardsWidget.getFirstBackupCardView().animate().alpha(1f).duration = 400 cardsWidget.getSecondBackupCardView().alpha = 0.2f } 2 -> { @@ -253,7 +255,7 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), cardsWidget.toFan(false) cardsWidget.getFirstBackupCardView().alpha = 1f - cardsWidget.getSecondBackupCardView().animate().alpha(1f).setDuration(400) + cardsWidget.getSecondBackupCardView().animate().alpha(1f).duration = 400 } } @@ -296,7 +298,7 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), prepareViewForFinalizeStep() cardsWidget.getSecondBackupCardView().show(state.backupCardsNumber == 2) - cardsWidget.toLeapfrog() { + cardsWidget.toLeapfrog { cardsWidget.getFirstBackupCardView().alpha = 0.6f cardsWidget.getSecondBackupCardView().alpha = 0.2f } @@ -335,12 +337,12 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), val cardNumber = (state.backupStep as? BackupStep.WriteBackupCard)?.cardNumber ?: 1 when (cardNumber) { - 1 -> cardsWidget.leapfrogWidget.leap() { + 1 -> cardsWidget.leapfrogWidget.leap { cardsWidget.getOriginCardView().alpha = 0.4f cardsWidget.getFirstBackupCardView().alpha = 0.2f cardsWidget.getSecondBackupCardView().alpha = 1f } - 2 -> cardsWidget.leapfrogWidget.leap() { + 2 -> cardsWidget.leapfrogWidget.leap { cardsWidget.getOriginCardView().alpha = 0.4f cardsWidget.getFirstBackupCardView().alpha = 0.2f cardsWidget.getSecondBackupCardView().alpha = 1f @@ -389,8 +391,7 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), imvSuccess.show() imvSuccess.animate() - ?.alpha(1f) - ?.setDuration(400) + ?.alpha(1f)?.duration = 400 } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index ecf9579bb9..b54a5168a9 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -132,7 +132,19 @@ class WalletMiddleware { .plus(Currency.Blockchain(wallet.blockchain, wallet.publicKey.derivationPath?.rawPath)) } action.coinsList != null -> action.coinsList - else -> walletState.walletsData.map { it.currency } + else -> { + if (walletState.isMultiwalletAllowed) { + walletState.walletsData.map { it.currency } + } else { + val derivationPath = walletState.primaryWallet?.currency?.derivationPath + val primaryBlockchain = walletState.primaryBlockchain + val primaryToken = walletState.primaryToken + listOfNotNull( + primaryBlockchain?.let { Currency.Blockchain(it, derivationPath) }, + primaryToken?.let { Currency.Token(it, primaryBlockchain!!, derivationPath) }, + ) + } + } } val ratesResult = globalState.tapWalletManager.rates.loadFiatRate( currencyId = appCurrencyId, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt index 63d378718f..7d124f2a41 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt @@ -21,6 +21,7 @@ import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.features.wallet.ui.BalanceWidgetData import com.tangem.tap.features.wallet.ui.TokenData import com.tangem.tap.store +import java.math.BigDecimal class OnWalletLoadedReducer { @@ -124,10 +125,12 @@ class OnWalletLoadedReducer { val tokenFiatAmount = tokenFiatRate?.let { tokenAmount.value?.toFiatString(it, fiatCurrencyName) } TokenData( - tokenAmount.value?.toFormattedCurrencyString( - token.decimals, token.symbol + amount = tokenAmount.value ?: BigDecimal.ZERO, + tokenSymbol = tokenAmount.currencySymbol, fiatAmountFormatted = tokenFiatAmount, + fiatAmount = tokenFiatRate?.let { tokenAmount.value?.toFiatValue(tokenFiatRate) }, + amountFormatted = tokenAmount.value?.toFormattedCurrencyString( + token.decimals, token.symbol, ) ?: "", - tokenAmount.currencySymbol, tokenFiatAmount ) } else { null diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt index a4b679512e..413e9e6f77 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt @@ -31,6 +31,7 @@ import com.tangem.tap.features.wallet.redux.WalletStore import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.features.wallet.ui.BalanceWidgetData import org.rekotlin.Action +import timber.log.Timber import java.math.BigDecimal class WalletReducer { @@ -386,16 +387,11 @@ private fun setNewFiatRate( state = state ) } else { - val fiatRate = fiatRates.entries.firstOrNull() - val currency = fiatRate?.key ?: return state - val rate = fiatRate.value ?: return state - - setSingleWalletFiatRate( - rate = rate, - rateFormatted = rateFormatter(rate), - currency = currency, + setSingleWalletFiatRates( + fiatRates = fiatRates.mapNotNullValues { it.value }, + rateFormatter = rateFormatter, appCurrency = appCurrency, - state = state + state = state, ) } } @@ -426,10 +422,10 @@ private fun setMultiWalletFiatRate( walletData.copy( currencyData = currencyData.copy( fiatAmountFormatted = fiatAmountFormatted, - fiatAmount = fiatAmount + fiatAmount = fiatAmount, ), fiatRate = rate, - fiatRateString = rateFormatter(rate) + fiatRateString = rateFormatter(rate), ) } @@ -437,15 +433,52 @@ private fun setMultiWalletFiatRate( .updateWalletsData(newWalletsData) } +private fun setSingleWalletFiatRates( + fiatRates: Map, + appCurrency: FiatCurrency, + rateFormatter: (BigDecimal) -> String, + state: WalletState, +): + WalletState { + val blockchainFiatRate = fiatRates.entries.firstOrNull { it.key.isBlockchain() } + val tokenFiatRate = fiatRates.entries.firstOrNull { it.key.isToken() } + Timber.e("Token Fiat Rate is ${tokenFiatRate ?: "NULL"}") + val updatedState = updateStateWithFiatRate(blockchainFiatRate?.toPair(), appCurrency, rateFormatter, state) + return updateStateWithFiatRate(tokenFiatRate?.toPair(), appCurrency, rateFormatter, updatedState) +} + +private fun updateStateWithFiatRate( + fiatRate: Pair?, + appCurrency: FiatCurrency, + rateFormatter: (BigDecimal) -> String, + state: WalletState, +): WalletState { + return if (fiatRate != null) { + val currency = fiatRate.first + val rate = fiatRate.second + + setSingleWalletFiatRate( + rate = rate, + rateFormatted = rateFormatter(rate), + currency = currency, + appCurrency = appCurrency, + state = state, + ) + } else { + state + } +} + private fun setSingleWalletFiatRate( rate: BigDecimal, rateFormatted: String, currency: Currency, appCurrency: FiatCurrency, - state: WalletState + state: WalletState, ): WalletState { val wallet = state.primaryWalletManager?.wallet ?: return state val token = wallet.getFirstToken() + Timber.e("Working with currency: ${currency.currencyName}") if (currency == state.primaryWallet?.currency) { val fiatAmount = wallet.amounts[AmountType.Coin]?.value @@ -456,23 +489,38 @@ private fun setSingleWalletFiatRate( fiatRateString = rateFormatted ) return state.updateWalletData(walletData) + } else if (currency is Currency.Token && currency.token == token) { + Timber.e("Working with token fiat rate") + val tokenFiatAmount = wallet.getTokenAmount(token) ?.value - ?.toFiatString(rate, appCurrency.code) + val tokenAmountFormatted = tokenFiatAmount?.toFiatString(rate, appCurrency.code) + val tokenData = state.primaryWallet?.currencyData?.token?.copy( + fiatAmountFormatted = tokenAmountFormatted, fiatAmount = tokenFiatAmount, fiatRate = rate, - fiatRateString = rateFormatted + fiatRateString = rateFormatted, ) + // ?: TokenData( + // fiatAmountFormatted = tokenAmountFormatted, + // fiatAmount = tokenFiatAmount, + // fiatRate = rate, + // fiatRateString = rateFormatted, + // amount = "", + // tokenSymbol = currency.currencySymbol + // ) + + Timber.e("Token Data is ${tokenData ?: "NULL"}") val walletData = state.primaryWallet?.copy( currencyData = state.primaryWallet.currencyData.copy( - token = tokenData - ) + token = tokenData, + ), ) - val wallets = walletData?.let { listOf(walletData) } ?: emptyList() - return state.updateWalletsData(wallets) + Timber.e("Wallet Data is ${walletData ?: "NULL"}") + return state.updateWalletData(walletData) } return state } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt index c627aa2e32..34ca1f10c1 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt @@ -34,9 +34,11 @@ data class BalanceWidgetData( ) data class TokenData( - val amount: String, + val amountFormatted: String, + val amount: BigDecimal? = null, val tokenSymbol: String, - val fiatAmount: String? = null, + val fiatAmountFormatted: String? = null, + val fiatAmount: BigDecimal? = null, val fiatRateString: String? = null, val fiatRate: BigDecimal? = null, ) @@ -151,11 +153,11 @@ class BalanceWidget( groupBaseCurrency.show() tvCurrency.text = data.token?.tokenSymbol tvBaseCurrency.text = data.currency - tvAmount.text = if (showAmount) data.token?.amount else "" + tvAmount.text = if (showAmount) data.token?.amountFormatted else "" tvBaseAmount.text = if (showAmount) data.amountFormatted else "" if (showAmount) { tvFiatAmount.show() - tvFiatAmount.text = data.token?.fiatAmount + tvFiatAmount.text = data.token?.fiatAmountFormatted } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index 1389600f94..27d49105a4 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -127,7 +127,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber { + !state.isMultiwalletAllowed && !isSaltPay && walletView !is SingleWalletView -> { walletView = SingleWalletView() walletView.changeWalletView(this, binding) } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPaySingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPaySingleWalletView.kt index 09d00b294e..8b0d0e639e 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPaySingleWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPaySingleWalletView.kt @@ -2,11 +2,12 @@ package com.tangem.tap.features.wallet.ui.wallet import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.extensions.show -import com.tangem.tap.features.wallet.redux.WalletData import com.tangem.tap.features.wallet.redux.WalletState +import com.tangem.tap.features.wallet.ui.TokenData import com.tangem.tap.features.wallet.ui.WalletFragment import com.tangem.tap.store import com.tangem.wallet.databinding.FragmentWalletBinding +import timber.log.Timber class SaltPaySingleWalletView : WalletView() { override fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding) { @@ -18,6 +19,7 @@ class SaltPaySingleWalletView : WalletView() { private fun showSingleWalletView(binding: FragmentWalletBinding) = with(binding) { rvMultiwallet.hide() btnAddToken.hide() + rowButtons.hide() rvPendingTransaction.hide() tvTwinCardNumber.hide() lCardBalance.root.hide() @@ -30,20 +32,21 @@ class SaltPaySingleWalletView : WalletView() { override fun onNewState(state: WalletState) { val binding = binding ?: return - state.primaryWallet ?: return + val tokenData = state.primaryWallet?.currencyData?.token ?: return - setupBalance(state, state.primaryWallet, binding) + setupBalance(state, tokenData, binding) } - private fun setupBalance(state: WalletState, primaryWallet: WalletData, binding: FragmentWalletBinding) { + private fun setupBalance(state: WalletState, tokenData: TokenData, binding: FragmentWalletBinding) { binding.lSingleWalletBalance.root.show() + Timber.e("Current address is ${state.primaryWalletManager?.wallet?.address}") SaltPayBalanceWidget( binding = binding.lSingleWalletBalance, data = SaltPayBalanceWidgetData( state = state.state, - currencySymbol = primaryWallet.currencyData.currencySymbol, - currency = primaryWallet.currencyData.amountFormatted, - fiatAmount = primaryWallet.currencyData.fiatAmount, + currencySymbol = tokenData.tokenSymbol, + currency = tokenData.amountFormatted, + fiatAmount = tokenData.amount, // TODO: show fiatAmount fiatCurrency = store.state.globalState.appCurrency, ), ).setup() diff --git a/app/src/main/res/layout/fragment_wallet.xml b/app/src/main/res/layout/fragment_wallet.xml index d5daad21ea..8bab86feb8 100644 --- a/app/src/main/res/layout/fragment_wallet.xml +++ b/app/src/main/res/layout/fragment_wallet.xml @@ -60,7 +60,6 @@ android:adjustViewBounds="true" android:contentDescription="@null" android:elevation="3dp" - android:maxHeight="215dp" android:scaleType="fitCenter" app:layout_constraintTop_toTopOf="parent" tools:src="@drawable/card_placeholder_black" /> diff --git a/app/src/main/res/layout/layout_single_wallet_balance.xml b/app/src/main/res/layout/layout_single_wallet_balance.xml index 119baed8cf..984d73bf24 100644 --- a/app/src/main/res/layout/layout_single_wallet_balance.xml +++ b/app/src/main/res/layout/layout_single_wallet_balance.xml @@ -4,15 +4,16 @@ xmlns:tools="http://schemas.android.com/tools" android:id="@+id/card_balance" android:layout_width="match_parent" - android:layout_height="wrap_content"> + android:layout_height="wrap_content" + app:cardCornerRadius="12dp"> @@ -50,8 +52,8 @@ android:layout_height="wrap_content" android:maxLines="1" android:minWidth="152dp" - android:textColor="@color/darkGray6" - android:textSize="26sp" + android:textColor="@color/text_primary_1" + android:textSize="24sp" android:textStyle="bold" tools:text="22 325.40 $" /> @@ -104,10 +106,12 @@ android:id="@+id/tv_currency_name" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textColor="@color/darkGray1" - android:textSize="16sp" + android:gravity="center_vertical" + android:textColor="@color/text_tertiary" + android:textSize="14sp" + android:textStyle="bold" app:drawableEndCompat="@drawable/ic_arrow_angle_down" - app:drawableTint="@color/darkGray1" + app:drawableTint="@color/text_tertiary" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" tools:text="USD" /> diff --git a/app/src/main/res/values-de/strings_final.xml b/app/src/main/res/values-de/strings_final.xml index 4243136291..501e8200d9 100644 --- a/app/src/main/res/values-de/strings_final.xml +++ b/app/src/main/res/values-de/strings_final.xml @@ -87,4 +87,6 @@ Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. Select network %s network has a concept of Existential Deposit. If your account drops below %s %s it will be deactivated and any remaining funds will be destroyed. + + To start working with the card scan the first card and make a backup \ 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 6e840c3e83..7c59ccee98 100644 --- a/app/src/main/res/values-fr/strings_final.xml +++ b/app/src/main/res/values-fr/strings_final.xml @@ -87,4 +87,6 @@ Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. Select network %s network has a concept of Existential Deposit. If your account drops below %s %s it will be deactivated and any remaining funds will be destroyed. + + To start working with the card scan the first card and make a backup \ 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 6e840c3e83..7c59ccee98 100644 --- a/app/src/main/res/values-it/strings_final.xml +++ b/app/src/main/res/values-it/strings_final.xml @@ -87,4 +87,6 @@ Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. Select network %s network has a concept of Existential Deposit. If your account drops below %s %s it will be deactivated and any remaining funds will be destroyed. + + To start working with the card scan the first card and make a backup \ 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 0f2737c2bb..27f6ed4b8a 100644 --- a/app/src/main/res/values-ru/strings_final.xml +++ b/app/src/main/res/values-ru/strings_final.xml @@ -98,4 +98,6 @@ Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек или использовать эту карту для восстановления кода доступа. Выберите сеть Сеть %s использует концепцию экзистенциального депозита. Если баланс вашего счета опустится ниже %s %s, он будет деактивирован, а все оставшиеся средства будут уничтожены. + + Для начала работы с картой сначала отсканируйте первую карту и сделайте бэкап diff --git a/app/src/main/res/values/strings_final.xml b/app/src/main/res/values/strings_final.xml index c3c6f58fe1..dd49ea9071 100644 --- a/app/src/main/res/values/strings_final.xml +++ b/app/src/main/res/values/strings_final.xml @@ -100,4 +100,6 @@ Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. Select network %s network has a concept of Existential Deposit. If your account drops below %s %s it will be deactivated and any remaining funds will be destroyed. + + To start working with the card scan the first card and make a backup diff --git a/domain/src/main/java/com/tangem/domain/common/ScanResponse.kt b/domain/src/main/java/com/tangem/domain/common/ScanResponse.kt index 35ec0dbe8b..35c6b31736 100644 --- a/domain/src/main/java/com/tangem/domain/common/ScanResponse.kt +++ b/domain/src/main/java/com/tangem/domain/common/ScanResponse.kt @@ -8,7 +8,10 @@ import com.tangem.common.card.WalletData import com.tangem.common.extensions.ByteArrayKey import com.tangem.common.extensions.toMapKey import com.tangem.common.hdWallet.DerivationPath +import com.tangem.domain.common.TapWorkarounds.getSaltPayBlockchain import com.tangem.domain.common.TapWorkarounds.getTangemNoteBlockchain +import com.tangem.domain.common.TapWorkarounds.isSaltPay +import com.tangem.domain.common.TapWorkarounds.isTangemNote import com.tangem.domain.common.TapWorkarounds.isTestCard import com.tangem.operations.CommandResponse import com.tangem.operations.backup.PrimaryCard @@ -23,17 +26,26 @@ data class ScanResponse( val walletData: WalletData?, val secondTwinPublicKey: String? = null, val derivedKeys: Map = mapOf(), - val primaryCard: PrimaryCard? = null + val primaryCard: PrimaryCard? = null, ) : CommandResponse { - fun getBlockchain(): Blockchain { - if (productType == ProductType.Note) return card.getTangemNoteBlockchain() - ?: return Blockchain.Unknown - val blockchainName: String = walletData?.blockchain ?: return Blockchain.Unknown - return Blockchain.fromId(blockchainName) + return when (productType) { + ProductType.SaltPay -> { + card.getSaltPayBlockchain() + } + ProductType.Note -> { + card.getTangemNoteBlockchain() ?: Blockchain.Unknown + } + else -> { + val blockchainName: String = walletData?.blockchain ?: return Blockchain.Unknown + Blockchain.fromId(blockchainName) + } + } } fun getPrimaryToken(): Token? { + if (card.isSaltPay) return TapWorkarounds.saltPayToken + val cardToken = walletData?.token ?: return null return Token( cardToken.name, @@ -46,10 +58,8 @@ data class ScanResponse( fun isTangemNote(): Boolean = productType == ProductType.Note fun isTangemWallet(): Boolean = productType == ProductType.Wallet fun isTangemTwins(): Boolean = productType == ProductType.Twins - fun supportsHdWallet(): Boolean = card.settings.isHDWalletAllowed fun supportsBackup(): Boolean = card.settings.isBackupAllowed - fun twinsIsTwinned(): Boolean = card.isTangemTwins() && walletData != null && secondTwinPublicKey != null @@ -73,18 +83,24 @@ data class ScanResponse( fun hasDerivation(curve: EllipticCurve, derivationPath: DerivationPath): Boolean { val foundWallet = card.wallets.firstOrNull { it.curve == curve } ?: return false - val extendedPublicKeysMap = derivedKeys[foundWallet.publicKey.toMapKey()] ?: return false - val extendedPublicKey = extendedPublicKeysMap[derivationPath] return extendedPublicKey != null } } enum class ProductType { - Note, Twins, Wallet + Note, Twins, Wallet, SaltPay } +val Card.productType: ProductType + get() = when { + isTangemTwins() -> ProductType.Twins + isTangemNote() -> ProductType.Note + isSaltPay -> ProductType.SaltPay + else -> ProductType.Wallet + } + typealias KeyWalletPublicKey = ByteArrayKey fun Card.isTangemTwins(): Boolean = TwinsHelper.getTwinCardNumber(cardId) != null \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/common/TapWorkarounds.kt b/domain/src/main/java/com/tangem/domain/common/TapWorkarounds.kt index a11441c51c..9b72f5d06d 100644 --- a/domain/src/main/java/com/tangem/domain/common/TapWorkarounds.kt +++ b/domain/src/main/java/com/tangem/domain/common/TapWorkarounds.kt @@ -2,6 +2,7 @@ package com.tangem.domain.common import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.DerivationStyle +import com.tangem.blockchain.common.Token import com.tangem.common.card.Card import java.util.* @@ -16,7 +17,8 @@ object TapWorkarounds { val Card.isStart2Coin: Boolean get() = isStart2CoinIssuer(issuer.name) val Card.isSaltPay: Boolean - get() = false //TODO fix when we know which cards are SaltPay cards + get() = saltPayCardIds.contains(cardId) || saltPayBatches.contains(batchId) + val Card.isTestCard: Boolean get() = batchId == TEST_CARD_BATCH && cardId.startsWith(TEST_CARD_ID_STARTS_WITH) val Card.useOldStyleDerivation: Boolean @@ -40,10 +42,12 @@ object TapWorkarounds { return false } - fun Card.isTangemNote(): Boolean = tangemNoteBatches.contains(batchId) || isSaltPay + fun Card.isTangemNote(): Boolean = tangemNoteBatches.contains(batchId) fun isTangemWalletBatch(card: Card): Boolean = tangemWalletBatches.contains(card.batchId) fun Card.getTangemNoteBlockchain(): Blockchain? = - tangemNoteBatches[batchId] ?: if (isSaltPay) Blockchain.Gnosis else null + tangemNoteBatches[batchId] ?: null + + fun Card.getSaltPayBlockchain(): Blockchain = Blockchain.SaltPay private const val START_2_COIN_ISSUER = "start2coin" private const val TEST_CARD_BATCH = "99FF" @@ -77,6 +81,41 @@ object TapWorkarounds { ) private val tangemWalletBatchesWithStandardDerivationType = listOf( - "AC01", "AC02", "CB95" + "AC01", "AC02", "CB95", ) + + private val saltPayCardIds = listOf( + "AE02000000000194", + "AC03000000076195", + "AC79000000000012", // TODO: remove my testing CIDs + "AC79000000000004",// TODO: remove my testing CIDs + "AC03000000076070", + "AC03000000076088", + "AC03000000076096", + "AC03000000076104", + "AC03000000076112", + "AC03000000076120", + "AC03000000076138", + "AC03000000076146", + "AC03000000076153", + "AC03000000076161", + "AC03000000076179", + "AC03000000076187", + "AC03000000076195", + "AC03000000076203", + "AC03000000076211", + "AC03000000076229", + // TODO: add cids + ) + + private val saltPayBatches = listOf("AE02") + + val saltPayToken: Token // TODO: Add real token + get() = Token( + name = "Wrapped xDAI", + "WxDAI", + "0x4346186e7461cB4DF06bCFCB4cD591423022e417", + 18, + id = "xdai", + ) } \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt b/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt index a162d197b2..6a4eb3b3e1 100644 --- a/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt +++ b/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt @@ -86,6 +86,7 @@ fun Blockchain.toNetworkId(): String { Blockchain.Polkadot -> "polkadot" Blockchain.PolkadotTestnet -> "polkadot/test" Blockchain.Kusama -> "kusama" + Blockchain.SaltPay -> "xdai"//TODO } } @@ -113,6 +114,7 @@ fun Blockchain.toCoinId(): String { Blockchain.Dogecoin -> "dogecoin" Blockchain.Gnosis -> "xdai" Blockchain.Kusama -> "kusama" + Blockchain.SaltPay -> "xdai" //TODO Blockchain.Unknown -> "unknown" } } \ No newline at end of file From 1d536906ea166fc064b1db89c12a58497e46ff3a Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 21 Sep 2022 13:14:32 +0400 Subject: [PATCH 003/113] Updated on 2026-08-14 --- app/src/main/assets/testnet_tokens.json | 20 ++- .../tap/common/extensions/Blockchain.kt | 4 + .../res/drawable/ic_ethereumfair_no_color.xml | 40 +++++ .../res/drawable/ic_ethereumfair_round.xml | 40 +++++ .../res/drawable/ic_ethereumpow_no_color.xml | 31 ++++ .../res/drawable/ic_ethereumpow_round.xml | 166 ++++++++++++++++++ dependencies.gradle | 2 +- .../domain/common/extensions/Blockchain.kt | 8 + 8 files changed, 305 insertions(+), 6 deletions(-) create mode 100644 app/src/main/res/drawable/ic_ethereumfair_no_color.xml create mode 100644 app/src/main/res/drawable/ic_ethereumfair_round.xml create mode 100644 app/src/main/res/drawable/ic_ethereumpow_no_color.xml create mode 100644 app/src/main/res/drawable/ic_ethereumpow_round.xml diff --git a/app/src/main/assets/testnet_tokens.json b/app/src/main/assets/testnet_tokens.json index 84c055f105..e9e185e5f0 100644 --- a/app/src/main/assets/testnet_tokens.json +++ b/app/src/main/assets/testnet_tokens.json @@ -412,12 +412,22 @@ ] }, { - "id" : "stellar", - "symbol" : "XLM", - "name" : "Stellar", - "networks" : [ + "id": "stellar", + "symbol": "XLM", + "name": "Stellar", + "networks": [ { - "networkId" : "stellar/test" + "networkId": "stellar/test" + } + ] + }, + { + "id": "ethereum-pow-iou", + "symbol": "ETHW", + "name": "Ethereum PoW", + "networks": [ + { + "networkId": "ethereum-pow-iou/test" } ] } diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt b/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt index e2490e7886..3b12f0ec88 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt @@ -28,6 +28,8 @@ fun Blockchain.getRoundIconRes(): Int { Blockchain.Dogecoin -> R.drawable.ic_dogecoin_round Blockchain.Tron, Blockchain.TronTestnet -> R.drawable.ic_tron_round Blockchain.Gnosis -> R.drawable.ic_gnosis_round + Blockchain.EthereumPow, Blockchain.EthereumPowTestnet -> R.drawable.ic_ethereumpow_round + Blockchain.EthereumFair -> R.drawable.ic_ethereumfair_round else -> R.drawable.ic_tangem_logo } } @@ -55,6 +57,8 @@ fun Blockchain.getGreyedOutIconRes(): Int { Blockchain.Dogecoin -> R.drawable.ic_dogecoin_no_color Blockchain.Tron, Blockchain.TronTestnet -> R.drawable.ic_tron_no_color Blockchain.Gnosis -> R.drawable.ic_gnosis_no_color + Blockchain.EthereumPow, Blockchain.EthereumPowTestnet -> R.drawable.ic_ethereumpow_no_color + Blockchain.EthereumFair -> R.drawable.ic_ethereumfair_no_color else -> R.drawable.ic_tangem_logo } } diff --git a/app/src/main/res/drawable/ic_ethereumfair_no_color.xml b/app/src/main/res/drawable/ic_ethereumfair_no_color.xml new file mode 100644 index 0000000000..dd68efa83e --- /dev/null +++ b/app/src/main/res/drawable/ic_ethereumfair_no_color.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_ethereumfair_round.xml b/app/src/main/res/drawable/ic_ethereumfair_round.xml new file mode 100644 index 0000000000..b8e6604a7a --- /dev/null +++ b/app/src/main/res/drawable/ic_ethereumfair_round.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_ethereumpow_no_color.xml b/app/src/main/res/drawable/ic_ethereumpow_no_color.xml new file mode 100644 index 0000000000..aa369fd0ab --- /dev/null +++ b/app/src/main/res/drawable/ic_ethereumpow_no_color.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_ethereumpow_round.xml b/app/src/main/res/drawable/ic_ethereumpow_round.xml new file mode 100644 index 0000000000..052ac58398 --- /dev/null +++ b/app/src/main/res/drawable/ic_ethereumpow_round.xml @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dependencies.gradle b/dependencies.gradle index 0fdf41d259..18c09c7c2d 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -2,7 +2,7 @@ ext.versions = [ kotlin : '1.6.10', build_gradle : '7.1.3', tangem_card_sdk : 'develop-159', - tangem_blockchain_sdk: 'AND-2102-develop-105-hotfix_xlm-109', + tangem_blockchain_sdk: 'AND-2140_develop_105_hotfix_eth_pow_fair-116', // tangem_blockchain_sdk: '0.0.1', ] diff --git a/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt b/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt index 8708f1a0a8..3fd24aa445 100644 --- a/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt +++ b/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt @@ -38,6 +38,9 @@ fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? { "tron/test" -> Blockchain.TronTestnet "xrp", "ripple" -> Blockchain.XRP "xdai" -> Blockchain.Gnosis + "ethereum-pow-iou" -> Blockchain.EthereumPow + "ethereum-pow-iou/test" -> Blockchain.EthereumPowTestnet + "ethereumfair" -> Blockchain.EthereumFair else -> null } } @@ -80,6 +83,9 @@ fun Blockchain.toNetworkId(): String { Blockchain.Tron -> "tron" Blockchain.TronTestnet -> "tron/test" Blockchain.Gnosis -> "xdai" + Blockchain.EthereumPow -> "ethereum-pow-iou" + Blockchain.EthereumPowTestnet -> "ethereum-pow-iou/test" + Blockchain.EthereumFair -> "ethereumfair" } } @@ -105,6 +111,8 @@ fun Blockchain.toCoinId(): String { Blockchain.Dogecoin -> "dogecoin" Blockchain.Tron, Blockchain.TronTestnet -> "tron" Blockchain.Gnosis -> "xdai" + Blockchain.EthereumPow, Blockchain.EthereumPowTestnet -> "ethereum-pow-iou" + Blockchain.EthereumFair -> "ethereumfair" Blockchain.Unknown -> "unknown" } } \ No newline at end of file From a642b39aa7bd751846097cebc356079767b90eb8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 22 Sep 2022 11:23:23 +0400 Subject: [PATCH 004/113] Updated on 2026-08-14 --- .../com/tangem/tap/common/analytics/TangemSdkError.kt | 2 +- .../tangem/tap/features/onboarding/OnboardingHelper.kt | 3 +-- .../products/wallet/redux/OnboardingWalletMiddleware.kt | 9 ++++++++- .../features/wallet/ui/wallet/SaltPaySingleWalletView.kt | 2 +- app/src/main/res/values-it/strings_final.xml | 1 - app/src/main/res/values-ru/strings_final.xml | 2 -- dependencies.gradle | 1 + .../main/java/com/tangem/domain/common/TapWorkarounds.kt | 6 +++--- .../com/tangem/domain/common/extensions/Blockchain.kt | 2 ++ 9 files changed, 17 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/TangemSdkError.kt b/app/src/main/java/com/tangem/tap/common/analytics/TangemSdkError.kt index 58b026e655..17e31d88c4 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/TangemSdkError.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/TangemSdkError.kt @@ -65,7 +65,7 @@ object TangemSdk { is TangemSdkError.Busy -> TangemSdkError.Busy() is TangemSdkError.MissingPreflightRead -> TangemSdkError.MissingPreflightRead() is TangemSdkError.WrongCardNumber -> TangemSdkError.WrongCardNumber() - is TangemSdkError.WrongCardType -> TangemSdkError.WrongCardType() + is TangemSdkError.WrongCardType -> TangemSdkError.WrongCardType(null) is TangemSdkError.CardError -> TangemSdkError.CardError() is TangemSdkError.NotSupportedFirmwareVersion -> TangemSdkError.NotSupportedFirmwareVersion() is TangemSdkError.WalletError -> TangemSdkError.WalletError() diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index 0b58d4aa0b..cd6f7c5197 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -17,8 +17,7 @@ class OnboardingHelper { val cardInfoStorage = preferencesStorage.usedCardsPrefStorage return when { response.card.isSaltPay -> { - // response.card.backupStatus?.isActive != true //TODO: restore after presentation - false + response.card.backupStatus?.isActive != true } response.productType == ProductType.Twins -> { if (!response.twinsIsTwinned()) { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index de8e3a77c4..6f332cfd34 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -1,9 +1,11 @@ package com.tangem.tap.features.onboarding.products.wallet.redux import com.tangem.blockchain.common.Blockchain +import com.tangem.common.CardFilter import com.tangem.common.CompletionResult import com.tangem.common.card.Card import com.tangem.domain.common.ScanResponse +import com.tangem.domain.common.TapWorkarounds import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.domain.common.extensions.withMainContext import com.tangem.operations.backup.BackupService @@ -213,13 +215,18 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) } } is BackupAction.AddBackupCard -> { + backupService.skipCompatibilityChecks = true + tangemSdk.config.filter.cardIdFilter = + CardFilter.Companion.ItemFilter.Allow(TapWorkarounds.saltPayCardIds.toSet()) + backupService.addBackupCard { result -> + backupService.skipCompatibilityChecks = false + tangemSdk.config.filter.cardIdFilter = null when (result) { is CompletionResult.Success -> { store.dispatchOnMain(BackupAction.AddBackupCard.Success) } is CompletionResult.Failure -> { - } } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPaySingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPaySingleWalletView.kt index 8b0d0e639e..1e348987fc 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPaySingleWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPaySingleWalletView.kt @@ -46,7 +46,7 @@ class SaltPaySingleWalletView : WalletView() { state = state.state, currencySymbol = tokenData.tokenSymbol, currency = tokenData.amountFormatted, - fiatAmount = tokenData.amount, // TODO: show fiatAmount + fiatAmount = tokenData.fiatAmount, // TODO: show fiatAmount fiatCurrency = store.state.globalState.appCurrency, ), ).setup() diff --git a/app/src/main/res/values-it/strings_final.xml b/app/src/main/res/values-it/strings_final.xml index 1c6908d47b..42c604b593 100644 --- a/app/src/main/res/values-it/strings_final.xml +++ b/app/src/main/res/values-it/strings_final.xml @@ -86,6 +86,5 @@ Reset to Factory Settings Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. Select network - %s network has a concept of Existential Deposit. If your account drops below %s %s it will be deactivated and any remaining funds will be destroyed. To start working with the card scan the first card and make a backup \ 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 23f2c7f576..9dae8e2e27 100644 --- a/app/src/main/res/values-ru/strings_final.xml +++ b/app/src/main/res/values-ru/strings_final.xml @@ -101,7 +101,5 @@ Tag Недопустимый Memo. Он не будет добавлен в транзакцию. Недопустимый Tag. Он не будет добавлен в транзакцию. - Сеть %s использует концепцию экзистенциального депозита. Если баланс вашего счета опустится ниже %s %s, он будет деактивирован, а все оставшиеся средства будут уничтожены. - Для начала работы с картой сначала отсканируйте первую карту и сделайте бэкап diff --git a/dependencies.gradle b/dependencies.gradle index 5d1d0d6fa5..b0dc5d199b 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -2,6 +2,7 @@ ext.versions = [ kotlin : '1.6.10', build_gradle : '7.1.3', tangem_card_sdk : 'develop-159', + // tangem_card_sdk: '0.0.1', tangem_blockchain_sdk: 'develop-115', // tangem_blockchain_sdk: '0.0.1', ] diff --git a/domain/src/main/java/com/tangem/domain/common/TapWorkarounds.kt b/domain/src/main/java/com/tangem/domain/common/TapWorkarounds.kt index 9b72f5d06d..ab57eb34d8 100644 --- a/domain/src/main/java/com/tangem/domain/common/TapWorkarounds.kt +++ b/domain/src/main/java/com/tangem/domain/common/TapWorkarounds.kt @@ -84,7 +84,7 @@ object TapWorkarounds { "AC01", "AC02", "CB95", ) - private val saltPayCardIds = listOf( + val saltPayCardIds = listOf( "AE02000000000194", "AC03000000076195", "AC79000000000012", // TODO: remove my testing CIDs @@ -108,9 +108,9 @@ object TapWorkarounds { // TODO: add cids ) - private val saltPayBatches = listOf("AE02") + val saltPayBatches = listOf("AE02") - val saltPayToken: Token // TODO: Add real token + val saltPayToken: Token get() = Token( name = "Wrapped xDAI", "WxDAI", diff --git a/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt b/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt index 8dabde8702..a335a847a4 100644 --- a/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt +++ b/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt @@ -93,6 +93,7 @@ fun Blockchain.toNetworkId(): String { Blockchain.OptimismTestnet -> "optimistic-ethereum/test" Blockchain.Dash -> "dash" Blockchain.SaltPay -> "xdai"//TODO + else -> "unknown" // TODO } } @@ -124,5 +125,6 @@ fun Blockchain.toCoinId(): String { Blockchain.Dash -> "dash" Blockchain.SaltPay -> "xdai" //TODO Blockchain.Unknown -> "unknown" + else -> "unknown" // TODO } } \ No newline at end of file From dfe1e389d334c4a8b7c44bb8a0dd21cb2f75c465 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 22 Sep 2022 12:57:27 +0400 Subject: [PATCH 005/113] Updated on 2026-08-14 --- .../tangem/tap/features/demo/DemoHelper.kt | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt index 650ce5ea09..b0d473cde0 100644 --- a/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt @@ -448,6 +448,33 @@ class DemoConfig { "AB02000000049533", "AB02000000049541", "AB02000000049830", + // === more cids === + "AC03000000091418", + "AC03000000091400", + "AC03000000099007", + "AC03000000098991", + "AC03000000098942", + "AC03000000091715", + "AC03000000091301", + "AC03000000091343", + "AB01000000055705", + "AB01000000052918", + "AB01000000047710", + "AB01000000052306", + "AB01000000047645", + "AB01000000048957", + "AB01000000052900", + "AB01000000050391", + "AB01000000047363", + "AB02000000053998", + "AB02000000019809", + "AB02000000020872", + "AB02000000022027", + "AB02000000058955", + "AB02000000053253", + "AB02000000048063", + "AB02000000023736", + "AB02000000058187", ) private val testDemoCardIds = listOf( From d02dac09ecb1bba6b01701125b3864c34c36c9f5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 22 Sep 2022 17:07:13 +0400 Subject: [PATCH 006/113] Updated on 2026-08-14 --- .../java/com/tangem/tap/domain/TapErrors.kt | 8 +++- .../walletconnect/WalletConnectManager.kt | 33 ++++++++++----- .../walletconnect/extensions/WcPeerMeta.kt | 9 ++++ .../walletconnect/WalletConnectAction.kt | 3 +- .../walletconnect/WalletConnectMiddleware.kt | 41 ++++++++++++------- app/src/main/res/values-de/strings_final.xml | 2 + app/src/main/res/values-fr/strings_final.xml | 2 + app/src/main/res/values-it/strings_final.xml | 2 + app/src/main/res/values-ru/strings_final.xml | 2 + app/src/main/res/values/strings_final.xml | 2 + 10 files changed, 75 insertions(+), 29 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/domain/walletconnect/extensions/WcPeerMeta.kt diff --git a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt index 9d3cc79eee..03dc7f645b 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt @@ -41,7 +41,7 @@ sealed class TapError( data class UnsupportedState( val stateError: String, - val customMessage: String = "Unsupported state:" + val customMessage: String = "Unsupported state:", ) : TapError(R.string.common_custom_string, listOf("$customMessage $stateError")) sealed class WalletManager { @@ -52,9 +52,13 @@ sealed class TapError( object BlockchainIsUnreachableTryLater : TapError(R.string.wallet_balance_blockchain_unreachable_try_later) } + sealed class WalletConnect { + object UnsupportedDapp : TapError(R.string.wallet_connect_error_unsupported_dapp) + } + data class ValidateTransactionErrors( override val errorList: List, - override val builder: (List) -> String + override val builder: (List) -> String, ) : TapError(-1), MultiMessageError } diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt index 7e4fae297a..064c3b180b 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt @@ -5,6 +5,8 @@ import com.tangem.common.extensions.guard import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.domain.TapError +import com.tangem.tap.domain.walletconnect.extensions.isDappSupported import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction import com.tangem.tap.features.details.redux.walletconnect.WalletConnectDialog import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession @@ -325,21 +327,30 @@ class WalletConnectManager { val session = client.session val data = sessions[session]?.copy(peerMeta = peer, remotePeerId = client.remotePeerId) if (data != null && session != null) { - sessions[session] = data - val sessionData = data.toWalletConnectSession() - sessionData?.let { + if (!peer.isDappSupported()) { store.dispatchOnMain( - WalletConnectAction.ScanCard( - session = sessionData, - chainId = client.chainId?.toIntOrNull() + WalletConnectAction.FailureEstablishingSession( + session = session, + error = TapError.WalletConnect.UnsupportedDapp, + ), + ) + } else { + sessions[session] = data + val sessionData = data.toWalletConnectSession() + sessionData?.let { + store.dispatchOnMain( + WalletConnectAction.ScanCard( + session = sessionData, + chainId = client.chainId?.toIntOrNull(), + ), ) + } + store.state.globalState.analyticsHandlers?.logWcEvent( + Analytics.WcAnalyticsEvent.Session( + Analytics.WcSessionEvent.Connect, peer.url, + ), ) } - store.state.globalState.analyticsHandlers?.logWcEvent( - Analytics.WcAnalyticsEvent.Session( - Analytics.WcSessionEvent.Connect, peer.url - ) - ) } } client.onSessionUpdate = { id: Long, update: WCSessionUpdate -> diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/extensions/WcPeerMeta.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/extensions/WcPeerMeta.kt new file mode 100644 index 0000000000..057caf7bd2 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/extensions/WcPeerMeta.kt @@ -0,0 +1,9 @@ +package com.tangem.tap.domain.walletconnect.extensions + +import com.trustwallet.walletconnect.models.WCPeerMeta + +fun WCPeerMeta.isDappSupported(): Boolean { + return !unsupportedDappsList.any { this.url.contains(it) } +} + +private val unsupportedDappsList: List = listOf("dydx.exchange") \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt index 14a871fbd9..9106094b1a 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt @@ -2,6 +2,7 @@ package com.tangem.tap.features.details.redux.walletconnect import com.tangem.blockchain.common.Blockchain import com.tangem.tap.common.redux.NotificationAction +import com.tangem.tap.domain.TapError import com.tangem.wallet.R import com.trustwallet.walletconnect.models.binance.WCBinanceTradeOrder import com.trustwallet.walletconnect.models.binance.WCBinanceTransferOrder @@ -53,7 +54,7 @@ sealed class WalletConnectAction : Action { val updatedSession: WalletConnectSession, ) : WalletConnectAction() - data class FailureEstablishingSession(val session: WCSession?) : WalletConnectAction() + data class FailureEstablishingSession(val session: WCSession?, val error: TapError? = null) : WalletConnectAction() data class SetSessionsRestored(val sessions: List) : WalletConnectAction() diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt index d98fb0b0f5..faf6594595 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt @@ -8,6 +8,7 @@ import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen @@ -90,6 +91,16 @@ class WalletConnectMiddleware { store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.SessionTimeout)) } is WalletConnectAction.FailureEstablishingSession -> { + if (action.error != null) { + store.dispatch( + GlobalAction.ShowDialog( + AppDialog.SimpleOkDialogRes( + headerId = R.string.common_warning, + messageId = action.error.messageResource, + ), + ), + ) + } if (action.session != null) { walletConnectManager.disconnect(action.session) } @@ -273,21 +284,21 @@ class WalletConnectMiddleware { ) return } - val wallet = walletManager.wallet - val derivedKey = - if (wallet.publicKey.blockchainKey.contentEquals(wallet.publicKey.seedKey)) { - null - } else { - walletManager.wallet.publicKey.blockchainKey - } - val walletForSession = WalletForSession( - cardId = scanResponse.card.cardId, - walletPublicKey = wallet.publicKey.seedKey, - derivedPublicKey = derivedKey, - derivationPath = wallet.publicKey.derivationPath, - derivationStyle = scanResponse.card.derivationStyle, - blockchain = wallet.blockchain, - ) + val wallet = walletManager.wallet + val derivedKey = + if (wallet.publicKey.blockchainKey.contentEquals(wallet.publicKey.seedKey)) { + null + } else { + walletManager.wallet.publicKey.blockchainKey + } + val walletForSession = WalletForSession( + cardId = scanResponse.card.cardId, + walletPublicKey = wallet.publicKey.seedKey, + derivedPublicKey = derivedKey, + derivationPath = wallet.publicKey.derivationPath, + derivationStyle = scanResponse.card.derivationStyle, + blockchain = wallet.blockchain, + ) withMainContext { val updatedSession = session.copy(wallet = walletForSession) diff --git a/app/src/main/res/values-de/strings_final.xml b/app/src/main/res/values-de/strings_final.xml index a2c94fc2da..042969a1b3 100644 --- a/app/src/main/res/values-de/strings_final.xml +++ b/app/src/main/res/values-de/strings_final.xml @@ -99,4 +99,6 @@ Tag Invalid Memo. It won\'t be added to the transaction Invalid Tag. It won\'t be added to the transaction + + Connection with this Dapp cannot be established due to its technical implementation. \ 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 01b15baa13..44cf70e8a4 100644 --- a/app/src/main/res/values-fr/strings_final.xml +++ b/app/src/main/res/values-fr/strings_final.xml @@ -99,4 +99,6 @@ Tag Invalid Memo. It won\'t be added to the transaction Invalid Tag. It won\'t be added to the transaction + + Connection with this Dapp cannot be established due to its technical implementation. \ 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 f6b98a333f..8052503722 100644 --- a/app/src/main/res/values-it/strings_final.xml +++ b/app/src/main/res/values-it/strings_final.xml @@ -100,4 +100,6 @@ Tag Invalid Memo. It won\'t be added to the transaction Invalid Tag. It won\'t be added to the transaction + + Connection with this Dapp cannot be established due to its technical implementation. \ 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 3ccd9d13aa..22f9894ae8 100644 --- a/app/src/main/res/values-ru/strings_final.xml +++ b/app/src/main/res/values-ru/strings_final.xml @@ -116,4 +116,6 @@ Tag Недопустимый Memo. Он не будет добавлен в транзакцию. Недопустимый Tag. Он не будет добавлен в транзакцию. + + Cоединение с этим Dapp сервисом не может быть установлено из-за его технической реализации. diff --git a/app/src/main/res/values/strings_final.xml b/app/src/main/res/values/strings_final.xml index 0e11c29566..bd35c99d4e 100644 --- a/app/src/main/res/values/strings_final.xml +++ b/app/src/main/res/values/strings_final.xml @@ -118,4 +118,6 @@ Tag Invalid Memo. It won\'t be added to the transaction Invalid Tag. It won\'t be added to the transaction + + Connection with this Dapp cannot be established due to its technical implementation. From f1d61dad13ffd00683a0024a46000d4f783c5041 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 22 Sep 2022 17:09:06 +0400 Subject: [PATCH 007/113] Updated on 2026-08-14 --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index 18c09c7c2d..e1b2eb8ab6 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -2,7 +2,7 @@ ext.versions = [ kotlin : '1.6.10', build_gradle : '7.1.3', tangem_card_sdk : 'develop-159', - tangem_blockchain_sdk: 'AND-2140_develop_105_hotfix_eth_pow_fair-116', + tangem_blockchain_sdk: 'AND-2145_develop_105_hotfix_eth_classic-118', // tangem_blockchain_sdk: '0.0.1', ] From 0e4c0349de82239c6c1e3455f9bda5111d31ffe0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 23 Sep 2022 10:24:28 +0400 Subject: [PATCH 008/113] Updated on 2026-08-14 --- .../wallet/ui/images/CurrencyIconRequest.kt | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconRequest.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconRequest.kt index cd7959319e..f6d1124e89 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconRequest.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconRequest.kt @@ -52,9 +52,6 @@ class CurrencyIconRequest( private fun loadTokenIcon() { loadTokenIconBase( - onStart = { - currencyImageView.setColorFilter(it.getColor()) - }, onSuccess = { currencyImageView.colorFilter = null } @@ -94,17 +91,25 @@ class CurrencyIconRequest( data = getTokenIcon(token, blockchain), placeholderRes = R.drawable.shape_circle, onStart = { - currencyTextView.text = token.symbol.take(1) - currencyTextView.setTextColor(token.getTextColor()) + showPlaceholder(token) onStart(token) }, onSuccess = { currencyTextView.text = null onSuccess(token) }, - onError = { onError(token) }, + onError = { + showPlaceholder(token) + onError(token) + }, ) } + + private fun showPlaceholder(token: Token) { + currencyTextView.text = token.symbol.take(1) + currencyTextView.setTextColor(token.getTextColor()) + currencyImageView.setColorFilter(token.getColor()) + } } private inline fun ImageView.loadIcon( From 5ad45b5e22ef6e66530392da0b46e22440c09104 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 15 Sep 2022 01:00:26 +0400 Subject: [PATCH 009/113] Updated on 2026-08-14 --- .../com/tangem/tap/common/extensions/Token.kt | 20 +++++---- .../wallet/ui/images/CurrencyIconRequest.kt | 38 +++++++++------- .../wallet/ui/images/CurrencyIconView.kt | 43 ++++++++----------- .../main/res/layout/view_currency_icon.xml | 7 +-- 4 files changed, 58 insertions(+), 50 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Token.kt b/app/src/main/java/com/tangem/tap/common/extensions/Token.kt index 10897bc069..59c918d736 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Token.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Token.kt @@ -5,19 +5,23 @@ import androidx.annotation.ColorInt import androidx.core.graphics.luminance import androidx.core.graphics.toColorInt import com.tangem.blockchain.common.Token -import com.tangem.wallet.R @ColorInt -fun Token.getColor(): Int { - return try { - ("#" + this.contractAddress.subSequence(2..7).toString()) - .toColorInt() +fun Token.getColor(isTestnet: Boolean = false): Int { + val defaultColor = "#C7C7CC".toColorInt() // equivalent to R.color.lightGray4 + + return if (isTestnet) + defaultColor + else try { + ("#" + this.contractAddress.subSequence(2..7).toString()).toColorInt() } catch (exception: Exception) { - R.color.lightGray4 + defaultColor } } @ColorInt -fun Token.getTextColor(): Int { - return if (this.getColor().luminance > 0.5) Color.BLACK else Color.WHITE +fun Token.getTextColor(isTestnet: Boolean = false): Int = when { + isTestnet -> Color.WHITE + this.getColor().luminance > 0.5 -> Color.BLACK + else -> Color.WHITE } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconRequest.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconRequest.kt index f6d1124e89..62f015909c 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconRequest.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconRequest.kt @@ -21,7 +21,7 @@ private const val VOYR = "VOYRME" class CurrencyIconRequest( private val currencyImageView: ImageFilterView, - private val currencyTextView: TextView, + private val currencyTextView: TextView?, private val token: Token?, private val blockchain: Blockchain, ) { @@ -38,7 +38,7 @@ class CurrencyIconRequest( loadBlockchainIconBase( onStart = { currencyImageView.colorFilter = null - } + }, ) } @@ -46,15 +46,22 @@ class CurrencyIconRequest( loadBlockchainIconBase( onStart = { currencyImageView.saturation = 0f - } + }, ) } private fun loadTokenIcon() { loadTokenIconBase( + onStart = { + currencyImageView.setColorFilter(it.getColor()) + }, onSuccess = { currencyImageView.colorFilter = null - } + }, + onError = { + currencyImageView.setColorFilter(it.getColor()) + currencyTextView?.setTextColor(it.getTextColor()) + }, ) } @@ -62,7 +69,12 @@ class CurrencyIconRequest( loadTokenIconBase( onStart = { currencyImageView.saturation = 0f - } + }, + onError = { + currencyImageView.saturation = 0f + currencyImageView.setColorFilter(it.getColor(true)) + currencyTextView?.setTextColor(it.getTextColor(true)) + }, ) } @@ -91,25 +103,21 @@ class CurrencyIconRequest( data = getTokenIcon(token, blockchain), placeholderRes = R.drawable.shape_circle, onStart = { - showPlaceholder(token) + currencyTextView?.text = token.name.take(1) + currencyTextView?.setTextColor(token.getTextColor()) onStart(token) }, onSuccess = { - currencyTextView.text = null + currencyTextView?.text = null onSuccess(token) }, onError = { - showPlaceholder(token) + // for some reason the onStart doesn't call if an error occurs + currencyTextView?.text = token.name.take(1) onError(token) }, ) } - - private fun showPlaceholder(token: Token) { - currencyTextView.text = token.symbol.take(1) - currencyTextView.setTextColor(token.getTextColor()) - currencyImageView.setColorFilter(token.getColor()) - } } private inline fun ImageView.loadIcon( @@ -127,7 +135,7 @@ private inline fun ImageView.loadIcon( .listener( onStart = { onStart() }, onSuccess = { _, _ -> onSuccess() }, - onError = { _, _ -> onError() } + onError = { _, _ -> onError() }, ) .target(imageView = this) .build() diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconView.kt index ec9ff38519..96d03cdd22 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconView.kt @@ -4,13 +4,11 @@ import android.content.Context import android.util.AttributeSet import android.view.LayoutInflater import android.widget.TextView -import androidx.annotation.DrawableRes import androidx.constraintlayout.utils.widget.ImageFilterView import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.isVisible import com.tangem.blockchain.common.DerivationStyle import com.tangem.tangem_sdk_new.extensions.dpToPx -import com.tangem.tap.common.extensions.getRoundIconRes import com.tangem.tap.features.wallet.models.Currency import com.tangem.wallet.databinding.ViewCurrencyIconBinding import kotlin.math.roundToInt @@ -24,29 +22,16 @@ class CurrencyIconView @JvmOverloads constructor( LayoutInflater.from(context), this, ) - private val currencyImageView: ImageFilterView get() = binding.ivCurrency - private val currencyTextView: TextView get() = binding.tvTokenLetter - - private var isBlockchainIconVisible: Boolean - get() = binding.ivBlockchain.isVisible - set(value) = binding.ivBlockchain::isVisible.set(value) - - private var isBadgeVisible: Boolean - get() = binding.badge.isVisible - set(value) = binding.badge::isVisible.set(value) - - @DrawableRes - private var blockchainIconRes: Int? = null - set(value) { - if (value != null && value != field && isBlockchainIconVisible) { - binding.ivBlockchain.setImageResource(value) - field = value - } - } + private var isBlockchainBadgeVisible: Boolean + get() = binding.ivBlockchainBadge.isVisible + set(value) = binding.ivBlockchainBadge::isVisible.set(value) + private var isCustomCurrencyBadgeVisible: Boolean + get() = binding.customBadge.isVisible + set(value) = binding.customBadge::isVisible.set(value) init { minWidth = dpToPx(48f).roundToInt() @@ -57,9 +42,7 @@ class CurrencyIconView @JvmOverloads constructor( currency: Currency, derivationStyle: DerivationStyle?, ) { - isBlockchainIconVisible = currency.isToken() - isBadgeVisible = currency.isCustomCurrency(derivationStyle) - blockchainIconRes = currency.blockchain.getRoundIconRes() + isCustomCurrencyBadgeVisible = currency.isCustomCurrency(derivationStyle) CurrencyIconRequest( currencyImageView = currencyImageView, @@ -67,5 +50,17 @@ class CurrencyIconView @JvmOverloads constructor( token = (currency as? Currency.Token)?.token, blockchain = currency.blockchain, ).load() + + if (currency.isToken()) { + // load a blockchain icon into the blockchain badge + isBlockchainBadgeVisible = true + + CurrencyIconRequest( + currencyImageView = binding.ivBlockchainBadge, + currencyTextView = null, + token = null, + blockchain = currency.blockchain, + ).load() + } } } \ No newline at end of file diff --git a/app/src/main/res/layout/view_currency_icon.xml b/app/src/main/res/layout/view_currency_icon.xml index 801ac76c97..aa1330c398 100644 --- a/app/src/main/res/layout/view_currency_icon.xml +++ b/app/src/main/res/layout/view_currency_icon.xml @@ -35,12 +35,13 @@ android:textColor="@android:color/white" android:textSize="25sp" android:textStyle="bold" + android:translationY="-1dp" tools:text="J" /> - Date: Thu, 15 Sep 2022 15:44:08 +0400 Subject: [PATCH 010/113] Updated on 2026-08-14 --- .../wallet/ui/WalletDetailsFragment.kt | 1 + .../wallet/ui/adapters/WalletAdapter.kt | 1 + .../wallet/ui/images/CurrencyIconView.kt | 50 ++++++++++--------- 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt index ab07dd33d5..db9173e486 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt @@ -41,6 +41,7 @@ import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.features.wallet.redux.WalletState.Companion.UNKNOWN_AMOUNT_SIGN import com.tangem.tap.features.wallet.ui.adapters.PendingTransactionsAdapter import com.tangem.tap.features.wallet.ui.adapters.WalletDetailWarningMessagesAdapter +import com.tangem.tap.features.wallet.ui.images.load import com.tangem.tap.features.wallet.ui.test.TestWalletDetails import com.tangem.tap.store import com.tangem.wallet.R diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt index 4be9104bb1..af061d48f1 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt @@ -15,6 +15,7 @@ import com.tangem.tap.common.extensions.show import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.redux.WalletData import com.tangem.tap.features.wallet.ui.BalanceStatus +import com.tangem.tap.features.wallet.ui.images.load import com.tangem.tap.store import com.tangem.wallet.R import com.tangem.wallet.databinding.ItemCurrencyWalletBinding diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconView.kt index 96d03cdd22..4f5fd38bf9 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconView.kt @@ -22,14 +22,16 @@ class CurrencyIconView @JvmOverloads constructor( LayoutInflater.from(context), this, ) - private val currencyImageView: ImageFilterView + val currencyImageView: ImageFilterView get() = binding.ivCurrency - private val currencyTextView: TextView + val currencyTextView: TextView get() = binding.tvTokenLetter - private var isBlockchainBadgeVisible: Boolean + val blockchainBadge: ImageFilterView + get() = binding.ivBlockchainBadge + var isBlockchainBadgeVisible: Boolean get() = binding.ivBlockchainBadge.isVisible set(value) = binding.ivBlockchainBadge::isVisible.set(value) - private var isCustomCurrencyBadgeVisible: Boolean + var isCustomCurrencyBadgeVisible: Boolean get() = binding.customBadge.isVisible set(value) = binding.customBadge::isVisible.set(value) @@ -37,30 +39,30 @@ class CurrencyIconView @JvmOverloads constructor( minWidth = dpToPx(48f).roundToInt() minHeight = dpToPx(48f).roundToInt() } +} - fun load( - currency: Currency, - derivationStyle: DerivationStyle?, - ) { - isCustomCurrencyBadgeVisible = currency.isCustomCurrency(derivationStyle) +fun CurrencyIconView.load( + currency: Currency, + derivationStyle: DerivationStyle?, +) { + isCustomCurrencyBadgeVisible = currency.isCustomCurrency(derivationStyle) + + CurrencyIconRequest( + currencyImageView = currencyImageView, + currencyTextView = currencyTextView, + token = (currency as? Currency.Token)?.token, + blockchain = currency.blockchain, + ).load() + + if (currency.isToken()) { + // load a blockchain icon into the blockchain badge + isBlockchainBadgeVisible = true CurrencyIconRequest( - currencyImageView = currencyImageView, - currencyTextView = currencyTextView, - token = (currency as? Currency.Token)?.token, + currencyImageView = blockchainBadge, + currencyTextView = null, + token = null, blockchain = currency.blockchain, ).load() - - if (currency.isToken()) { - // load a blockchain icon into the blockchain badge - isBlockchainBadgeVisible = true - - CurrencyIconRequest( - currencyImageView = binding.ivBlockchainBadge, - currencyTextView = null, - token = null, - blockchain = currency.blockchain, - ).load() - } } } \ No newline at end of file From c2d8710cac28a9d7adf5dec937f61b3e578032c1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 26 Sep 2022 20:04:21 +0300 Subject: [PATCH 011/113] Updated on 2026-08-14 --- .../network/exchangeServices/mercuryo/MercuryoService.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt index dec1f05bae..35cc636808 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt @@ -76,7 +76,12 @@ class MercuryoService( if (!isBuyAllowed()) return false // blockchains which cant be defined by mercuryo service - val unsupportedBlockchains = listOf(Blockchain.Unknown, Blockchain.Binance, Blockchain.Arbitrum) + val unsupportedBlockchains = listOf( + Blockchain.Unknown, + Blockchain.Binance, + Blockchain.Arbitrum, + Blockchain.Optimism, + ) val blockchain = currency.blockchain return when (currency) { From e01b740983d021f0ef96c8d9e9a7697c775fdcbf Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 26 Sep 2022 20:47:06 +0300 Subject: [PATCH 012/113] Updated on 2026-08-14 --- app/src/main/res/values-de/strings_blockchain.xml | 4 ---- app/src/main/res/values-de/strings_final.xml | 4 ++++ app/src/main/res/values-fr/strings_blockchain.xml | 4 ---- app/src/main/res/values-fr/strings_final.xml | 4 ++++ app/src/main/res/values-it/strings_blockchain.xml | 4 ---- app/src/main/res/values-it/strings_final.xml | 4 ++++ app/src/main/res/values-ru/strings.xml | 1 + app/src/main/res/values-ru/strings_final.xml | 5 ++--- app/src/main/res/values/strings_blockchain.xml | 4 ---- app/src/main/res/values/strings_final.xml | 4 ++++ 10 files changed, 19 insertions(+), 19 deletions(-) diff --git a/app/src/main/res/values-de/strings_blockchain.xml b/app/src/main/res/values-de/strings_blockchain.xml index 5cb1360d71..74a5cca269 100644 --- a/app/src/main/res/values-de/strings_blockchain.xml +++ b/app/src/main/res/values-de/strings_blockchain.xml @@ -18,10 +18,6 @@ Der Gesamtbetrag geht über die Bilanz hinaus Die Gebühr geht über die Bilanz hinaus Minimum balance is %s - Memo - Tag - Invalid Memo. It won\'t be added to the transaction - Invalid Tag. It won\'t be added to the transaction %s network has a concept of Existential Deposit. If your account drops below %s %s it will be deactivated and any remaining funds will be destroyed. Destination account is not active. Send %s or more to activate the account. \ 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 f32a97ae89..a1d45244e2 100644 --- a/app/src/main/res/values-de/strings_final.xml +++ b/app/src/main/res/values-de/strings_final.xml @@ -88,4 +88,8 @@ Select network Scan your card To access all the networks you need to scan the card + Memo + Tag + Invalid Memo. It won\'t be added to the transaction. + Invalid Tag. It won\'t be added to the transaction. \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings_blockchain.xml b/app/src/main/res/values-fr/strings_blockchain.xml index 5bbb85c2f3..c1b985a23a 100644 --- a/app/src/main/res/values-fr/strings_blockchain.xml +++ b/app/src/main/res/values-fr/strings_blockchain.xml @@ -18,10 +18,6 @@ Réduire de %s XTZ Non, envoyer toute la somme Minimum balance is %s - Memo - Tag - Invalid Memo. It won\'t be added to the transaction - Invalid Tag. It won\'t be added to the transaction %s network has a concept of Existential Deposit. If your account drops below %s %s it will be deactivated and any remaining funds will be destroyed. Destination account is not active. Send %s or more to activate the account. \ 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 ef550053ca..27b10023cd 100644 --- a/app/src/main/res/values-fr/strings_final.xml +++ b/app/src/main/res/values-fr/strings_final.xml @@ -88,4 +88,8 @@ Select network Scan your card To access all the networks you need to scan the card + Memo + Tag + Invalid Memo. It won\'t be added to the transaction. + Invalid Tag. It won\'t be added to the transaction. \ No newline at end of file diff --git a/app/src/main/res/values-it/strings_blockchain.xml b/app/src/main/res/values-it/strings_blockchain.xml index 775778dbb0..706ec42b90 100644 --- a/app/src/main/res/values-it/strings_blockchain.xml +++ b/app/src/main/res/values-it/strings_blockchain.xml @@ -18,10 +18,6 @@ Riduci di %s XTZ No, invia l\'intero importo Minimum balance is %s - Memo - Tag - Invalid Memo. It won\'t be added to the transaction - Invalid Tag. It won\'t be added to the transaction %s network has a concept of Existential Deposit. If your account drops below %s %s it will be deactivated and any remaining funds will be destroyed. Destination account is not active. Send %s or more to activate the account. \ 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 ef550053ca..27b10023cd 100644 --- a/app/src/main/res/values-it/strings_final.xml +++ b/app/src/main/res/values-it/strings_final.xml @@ -88,4 +88,8 @@ Select network Scan your card To access all the networks you need to scan the card + Memo + Tag + Invalid Memo. It won\'t be added to the transaction. + Invalid Tag. It won\'t be added to the transaction. \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index ca3e7e93f1..5ab8b7abf9 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -359,4 +359,5 @@ Пожалуйста, расскажите нам больше о Вашей проблеме. Каждая деталь может быть полезной. Информация ниже не является обязательной. Вы можете стереть её, если хотите. Нет + Сеть %s использует концепцию экзистенциального депозита. Если баланс вашего счета опустится ниже %s %s, он будет деактивирован, а все оставшиеся средства будут уничтожены. diff --git a/app/src/main/res/values-ru/strings_final.xml b/app/src/main/res/values-ru/strings_final.xml index b1ffa0ba10..c505594cb4 100644 --- a/app/src/main/res/values-ru/strings_final.xml +++ b/app/src/main/res/values-ru/strings_final.xml @@ -97,11 +97,10 @@ Сброс к заводским настройкам Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек или использовать эту карту для восстановления кода доступа. Выберите сеть + Отсканируйте карту + Чтобы получить доступ ко всем сетям, вам необходимо отсканировать карту Memo Tag Недопустимый Memo. Он не будет добавлен в транзакцию. Недопустимый Tag. Он не будет добавлен в транзакцию. - Сеть %s использует концепцию экзистенциального депозита. Если баланс вашего счета опустится ниже %s %s, он будет деактивирован, а все оставшиеся средства будут уничтожены. - Отсканируйте карту - Чтобы получить доступ ко всем сетям, вам необходимо отсканировать карту diff --git a/app/src/main/res/values/strings_blockchain.xml b/app/src/main/res/values/strings_blockchain.xml index 6a496314b1..9291701cb4 100644 --- a/app/src/main/res/values/strings_blockchain.xml +++ b/app/src/main/res/values/strings_blockchain.xml @@ -18,10 +18,6 @@ Reduce by %s XTZ No, send all Minimum balance is %s - Memo - Tag - Invalid Memo. It won\'t be added to the transaction - Invalid Tag. It won\'t be added to the transaction %s network has a concept of Existential Deposit. If your account drops below %s %s it will be deactivated and any remaining funds will be destroyed. Destination account is not active. Send %s or more to activate the account. \ No newline at end of file diff --git a/app/src/main/res/values/strings_final.xml b/app/src/main/res/values/strings_final.xml index 4585bee0c1..0b5d3d9cd5 100644 --- a/app/src/main/res/values/strings_final.xml +++ b/app/src/main/res/values/strings_final.xml @@ -101,4 +101,8 @@ Select network Scan your card To access all the networks you need to scan the card + Memo + Tag + Invalid Memo. It won\'t be added to the transaction. + Invalid Tag. It won\'t be added to the transaction. From 929c1f08442c73b6f7d0462bd04b126ec1cacfa4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 26 Sep 2022 21:02:22 +0300 Subject: [PATCH 013/113] Updated on 2026-08-14 --- .../com/tangem/tap/features/wallet/models/WalletWarning.kt | 3 +-- .../java/com/tangem/tap/features/wallet/redux/WalletState.kt | 3 +-- .../tangem/tap/features/wallet/ui/WalletWarningConverter.kt | 2 +- app/src/main/res/values-de/strings_blockchain.xml | 2 +- app/src/main/res/values-fr/strings_blockchain.xml | 2 +- app/src/main/res/values-it/strings_blockchain.xml | 2 +- app/src/main/res/values-ru/strings.xml | 1 - app/src/main/res/values-ru/strings_blockchain.xml | 1 + app/src/main/res/values/strings_blockchain.xml | 2 +- 9 files changed, 8 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/models/WalletWarning.kt b/app/src/main/java/com/tangem/tap/features/wallet/models/WalletWarning.kt index eb6c8caf49..3b80a95845 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/models/WalletWarning.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/models/WalletWarning.kt @@ -5,8 +5,7 @@ sealed class WalletWarning( ) { data class ExistentialDeposit( val currencyName: String, - val currencySymbols: String, - val existentialDepositString: String, + val edStringValueWithSymbol: String, ) : WalletWarning(1) object TransactionInProgress : WalletWarning(10) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt index d6f0def028..75109fec5a 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt @@ -352,8 +352,7 @@ data class WalletData( if (existentialDepositString != null) { val warning = WalletWarning.ExistentialDeposit( currencyName = currency.currencyName, - currencySymbols = currency.currencySymbol, - existentialDepositString = existentialDepositString, + edStringValueWithSymbol = "$existentialDepositString ${currency.currencySymbol}", ) walletWarnings.add(warning) } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletWarningConverter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletWarningConverter.kt index 6d34bbbf1f..624d34da3e 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletWarningConverter.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletWarningConverter.kt @@ -18,7 +18,7 @@ class WalletWarningConverter( is WalletWarning.ExistentialDeposit -> { context.getString( R.string.warning_existential_deposit_message, - message.currencyName, message.currencySymbols, message.existentialDepositString, + message.currencyName, message.edStringValueWithSymbol, ) } is WalletWarning.BalanceNotEnoughForFee -> { diff --git a/app/src/main/res/values-de/strings_blockchain.xml b/app/src/main/res/values-de/strings_blockchain.xml index 74a5cca269..66998f875e 100644 --- a/app/src/main/res/values-de/strings_blockchain.xml +++ b/app/src/main/res/values-de/strings_blockchain.xml @@ -18,6 +18,6 @@ Der Gesamtbetrag geht über die Bilanz hinaus Die Gebühr geht über die Bilanz hinaus Minimum balance is %s - %s network has a concept of Existential Deposit. If your account drops below %s %s it will be deactivated and any remaining funds will be destroyed. + %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. Destination account is not active. Send %s or more to activate the account. \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings_blockchain.xml b/app/src/main/res/values-fr/strings_blockchain.xml index c1b985a23a..197f42551c 100644 --- a/app/src/main/res/values-fr/strings_blockchain.xml +++ b/app/src/main/res/values-fr/strings_blockchain.xml @@ -18,6 +18,6 @@ Réduire de %s XTZ Non, envoyer toute la somme Minimum balance is %s - %s network has a concept of Existential Deposit. If your account drops below %s %s it will be deactivated and any remaining funds will be destroyed. + %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. Destination account is not active. Send %s or more to activate the account. \ No newline at end of file diff --git a/app/src/main/res/values-it/strings_blockchain.xml b/app/src/main/res/values-it/strings_blockchain.xml index 706ec42b90..92df7b9703 100644 --- a/app/src/main/res/values-it/strings_blockchain.xml +++ b/app/src/main/res/values-it/strings_blockchain.xml @@ -18,6 +18,6 @@ Riduci di %s XTZ No, invia l\'intero importo Minimum balance is %s - %s network has a concept of Existential Deposit. If your account drops below %s %s it will be deactivated and any remaining funds will be destroyed. + %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. Destination account is not active. Send %s or more to activate the account. \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 5ab8b7abf9..ca3e7e93f1 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -359,5 +359,4 @@ Пожалуйста, расскажите нам больше о Вашей проблеме. Каждая деталь может быть полезной. Информация ниже не является обязательной. Вы можете стереть её, если хотите. Нет - Сеть %s использует концепцию экзистенциального депозита. Если баланс вашего счета опустится ниже %s %s, он будет деактивирован, а все оставшиеся средства будут уничтожены. diff --git a/app/src/main/res/values-ru/strings_blockchain.xml b/app/src/main/res/values-ru/strings_blockchain.xml index 61a4c91467..9ccdfd276e 100644 --- a/app/src/main/res/values-ru/strings_blockchain.xml +++ b/app/src/main/res/values-ru/strings_blockchain.xml @@ -18,5 +18,6 @@ Уменьшить на %s XTZ Нет, отправить все Минимальный баланс: %s + Сеть %s использует концепцию экзистенциального депозита. Если баланс вашего счета опустится ниже %s, он будет деактивирован, а все оставшиеся средства будут уничтожены. Аккаунт получателя не активирован. Отправьте %s или более для активации аккаунта. \ No newline at end of file diff --git a/app/src/main/res/values/strings_blockchain.xml b/app/src/main/res/values/strings_blockchain.xml index 9291701cb4..8014c2a199 100644 --- a/app/src/main/res/values/strings_blockchain.xml +++ b/app/src/main/res/values/strings_blockchain.xml @@ -18,6 +18,6 @@ Reduce by %s XTZ No, send all Minimum balance is %s - %s network has a concept of Existential Deposit. If your account drops below %s %s it will be deactivated and any remaining funds will be destroyed. + %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. Destination account is not active. Send %s or more to activate the account. \ No newline at end of file From 586f0b33e91fe634e2a092361e7f0436948000d3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 27 Sep 2022 15:44:31 +0400 Subject: [PATCH 014/113] Updated on 2026-08-14 --- .../java/com/tangem/tap/domain/tokens/UserTokensRepository.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt index 58c3fb08a4..d05cfc0b8a 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt @@ -103,7 +103,7 @@ class UserTokensRepository( } companion object { - const val MESSAGE = "AccountID" + const val MESSAGE = "UserWalletID" fun init(context: Context, tangemTechService: TangemTechService): UserTokensRepository { val fileReader = AndroidFileReader(context) val oldUserTokensRepository = OldUserTokensRepository( From bd6223010a7f3d88050d05dc28ff2f44bc815d80 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 27 Sep 2022 16:03:46 +0400 Subject: [PATCH 015/113] Updated on 2026-08-14 --- .../com/tangem/tap/domain/TapWalletManager.kt | 65 ++++++++++--------- 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index 4f64bc1d20..845e419e5d 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -138,36 +138,7 @@ class TapWalletManager { private suspend fun loadMultiWalletData( scanResponse: ScanResponse, ) { - when (val tokensResult = userTokensRepository.getUserTokens(scanResponse.card)) { - is Result.Success -> { - withMainContext { - val blockchainNetworks = tokensResult.data.toBlockchainNetworks() - val walletManagers = walletManagerFactory.makeWalletManagersForApp(scanResponse, tokensResult.data) - store.dispatch( - WalletAction.MultiWallet.AddBlockchains( - blockchains = blockchainNetworks, - walletManagers = walletManagers, - save = false, - ), - ) - - blockchainNetworks.filter { it.tokens.isNotEmpty() } - .map { - store.dispatch( - WalletAction.MultiWallet.AddTokens( - tokens = it.tokens, - blockchain = it, - save = false, - ), - ) - } - checkIfDerivationsAreMissing(blockchainNetworks, scanResponse) - } - } - is Result.Failure -> { - return - } - } + loadUserCurrencies(scanResponse, walletManagerFactory) } private fun checkIfDerivationsAreMissing(blockchainNetworks: List, scanResponse: ScanResponse) { @@ -207,7 +178,41 @@ class TapWalletManager { } } + private suspend fun loadUserCurrencies(scanResponse: ScanResponse, walletManagerFactory: WalletManagerFactory) { + when (val tokensResult = userTokensRepository.getUserTokens(scanResponse.card)) { + is Result.Success -> { + withMainContext { + val blockchainNetworks = tokensResult.data.toBlockchainNetworks() + val walletManagers = walletManagerFactory.makeWalletManagersForApp(scanResponse, tokensResult.data) + store.dispatch( + WalletAction.MultiWallet.AddBlockchains( + blockchains = blockchainNetworks, + walletManagers = walletManagers, + save = false, + ), + ) + + blockchainNetworks.filter { it.tokens.isNotEmpty() } + .map { + store.dispatch( + WalletAction.MultiWallet.AddTokens( + tokens = it.tokens, + blockchain = it, + save = false, + ), + ) + } + checkIfDerivationsAreMissing(blockchainNetworks, scanResponse) + } + } + is Result.Failure -> { + return + } + } + } + suspend fun reloadData(data: ScanResponse) { + loadUserCurrencies(data, walletManagerFactory) withContext(Dispatchers.Main) { getActionIfUnknownBlockchainOrEmptyWallet(data)?.let { store.dispatch(it) From ef08a9c454be7fedf293f3380cf9a44d20b15ed7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 27 Sep 2022 15:18:12 +0300 Subject: [PATCH 016/113] Updated on 2026-08-14 --- .../domain/features/addCustomToken/AddCustomTokenService.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/domain/src/main/java/com/tangem/domain/features/addCustomToken/AddCustomTokenService.kt b/domain/src/main/java/com/tangem/domain/features/addCustomToken/AddCustomTokenService.kt index 502452e476..a502ae5041 100644 --- a/domain/src/main/java/com/tangem/domain/features/addCustomToken/AddCustomTokenService.kt +++ b/domain/src/main/java/com/tangem/domain/features/addCustomToken/AddCustomTokenService.kt @@ -26,7 +26,7 @@ class AddCustomTokenService( result.data.coins.forEach { coin -> val networksWithTheSameAddress = coin.networks .filter { it.contractAddress != null || it.decimalCount != null } - .filter { it.contractAddress == contractAddress } + .filter { it.contractAddress?.equals(contractAddress, ignoreCase = true) == true } .filter { supportedTokenNetworkIds.contains(it.networkId) } if (networksWithTheSameAddress.isNotEmpty()) { val newToken = coin.copy(networks = networksWithTheSameAddress) @@ -45,6 +45,5 @@ class AddCustomTokenService( private fun selectNetworksForSearch(networkId: String?): String { return networkId ?: supportedTokenNetworkIds.joinToString(",") - } } \ No newline at end of file From 0b5f75991f5efdcf323389885bf25db6f0b47051 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 27 Sep 2022 15:32:40 +0300 Subject: [PATCH 017/113] Updated on 2026-08-14 --- .../tap/features/wallet/ui/wallet/SingleWalletView.kt | 7 ++----- app/src/main/res/values-ru/strings.xml | 1 - app/src/main/res/values/strings_untranslated.xml | 2 -- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt index 9b8a2e759f..a5529a450d 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt @@ -6,6 +6,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import com.tangem.domain.common.TwinCardNumber import com.tangem.tap.common.extensions.beginDelayedTransition import com.tangem.tap.common.extensions.fitChipsByGroupWidth +import com.tangem.tap.common.extensions.getQuantityString import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.extensions.show import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState @@ -85,11 +86,7 @@ class SingleWalletView : WalletView() { private fun setupTwinCards(twinCardsState: TwinCardsState?, binding: FragmentWalletBinding) = with(binding) { twinCardsState?.cardNumber?.let { cardNumber -> tvTwinCardNumber.show() - val number = when (cardNumber) { - TwinCardNumber.First -> 1 - TwinCardNumber.Second -> 2 - } - tvTwinCardNumber.text = fragment?.getString(R.string.wallet_twins_chip_format, number, 2) + tvTwinCardNumber.text = tvTwinCardNumber.getQuantityString(R.plurals.card_label_card_count, 2) } if (twinCardsState?.cardNumber == null) { tvTwinCardNumber.hide() diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index ca3e7e93f1..38ee22a3ca 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -129,7 +129,6 @@ Tangem Twin Один кошелек. Две карты. Одна, которую Вы держите в руках, и вторая карта с номером #%s - Карта %1d из %2d Пересоздать Twin кошелек Условия использования карты Tangem Twin diff --git a/app/src/main/res/values/strings_untranslated.xml b/app/src/main/res/values/strings_untranslated.xml index afdc44412f..8d6c5017af 100644 --- a/app/src/main/res/values/strings_untranslated.xml +++ b/app/src/main/res/values/strings_untranslated.xml @@ -16,8 +16,6 @@ One wallet. Two cards. This one that you are holding in your hands and the other one with number #%s. - Card %1d of %2d - Re-create twin wallet Card terms of use From 459e4dcd681fbd7621ee017b316d0e8b0beece06 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 27 Sep 2022 23:11:15 +0300 Subject: [PATCH 018/113] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/common/extensions/Blockchain.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt b/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt index 71165c14df..634f3ab125 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt @@ -29,6 +29,7 @@ fun Blockchain.getRoundIconRes(): Int { Blockchain.Tron, Blockchain.TronTestnet -> R.drawable.ic_tron_round Blockchain.Gnosis -> R.drawable.ic_gnosis_round Blockchain.Polkadot, Blockchain.PolkadotTestnet -> R.drawable.ic_polkadot_round + Blockchain.Kusama -> R.drawable.ic_kusama_round Blockchain.Optimism, Blockchain.OptimismTestnet -> R.drawable.ic_optimism Blockchain.Dash -> R.drawable.ic_dash else -> R.drawable.ic_tangem_logo @@ -59,6 +60,7 @@ fun Blockchain.getGreyedOutIconRes(): Int { Blockchain.Tron, Blockchain.TronTestnet -> R.drawable.ic_tron_no_color Blockchain.Gnosis -> R.drawable.ic_gnosis_no_color Blockchain.Polkadot, Blockchain.PolkadotTestnet -> R.drawable.ic_polkadot_no_color + Blockchain.Kusama -> R.drawable.ic_kusama_no_color Blockchain.Optimism, Blockchain.OptimismTestnet -> R.drawable.ic_optimism_no_color Blockchain.Dash -> R.drawable.ic_dash_no_color else -> R.drawable.ic_tangem_logo From 5e7d0d3d0f2b326648dda043ac21757b7ab6789b Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 29 Sep 2022 10:18:55 +0400 Subject: [PATCH 019/113] Updated on 2026-08-14 --- .../com/tangem/tap/domain/TapWalletManager.kt | 42 ++++++++----------- .../tap/domain/tokens/UserTokensRepository.kt | 14 +++---- .../tap/features/wallet/redux/WalletAction.kt | 6 ++- .../tap/features/wallet/redux/WalletState.kt | 1 + .../wallet/redux/reducers/WalletReducer.kt | 2 + .../wallet/ui/wallet/MultiWalletView.kt | 2 + .../ui/wallet/SaltPaySingleWalletView.kt | 1 + .../wallet/ui/wallet/SingleWalletView.kt | 1 + app/src/main/res/layout/fragment_wallet.xml | 22 +++++++++- 9 files changed, 58 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index 845e419e5d..2ea48fb328 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -179,35 +179,29 @@ class TapWalletManager { } private suspend fun loadUserCurrencies(scanResponse: ScanResponse, walletManagerFactory: WalletManagerFactory) { - when (val tokensResult = userTokensRepository.getUserTokens(scanResponse.card)) { - is Result.Success -> { - withMainContext { - val blockchainNetworks = tokensResult.data.toBlockchainNetworks() - val walletManagers = walletManagerFactory.makeWalletManagersForApp(scanResponse, tokensResult.data) + val userTokens = userTokensRepository.getUserTokens(scanResponse.card) + withMainContext { + val blockchainNetworks = userTokens.toBlockchainNetworks() + val walletManagers = walletManagerFactory.makeWalletManagersForApp(scanResponse, userTokens) + store.dispatch( + WalletAction.MultiWallet.AddBlockchains( + blockchains = blockchainNetworks, + walletManagers = walletManagers, + save = false, + ), + ) + + blockchainNetworks.filter { it.tokens.isNotEmpty() } + .map { store.dispatch( - WalletAction.MultiWallet.AddBlockchains( - blockchains = blockchainNetworks, - walletManagers = walletManagers, + WalletAction.MultiWallet.AddTokens( + tokens = it.tokens, + blockchain = it, save = false, ), ) - - blockchainNetworks.filter { it.tokens.isNotEmpty() } - .map { - store.dispatch( - WalletAction.MultiWallet.AddTokens( - tokens = it.tokens, - blockchain = it, - save = false, - ), - ) - } - checkIfDerivationsAreMissing(blockchainNetworks, scanResponse) } - } - is Result.Failure -> { - return - } + checkIfDerivationsAreMissing(blockchainNetworks, scanResponse) } } diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt index d05cfc0b8a..e418215ed7 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt @@ -24,20 +24,20 @@ class UserTokensRepository( private val storageService: UserTokensStorageService, private val networkService: UserTokensNetworkService, ) { - suspend fun getUserTokens(card: Card): Result> { + suspend fun getUserTokens(card: Card): List { if (DemoHelper.isDemoCardId(card.cardId)) { - return Result.Success(loadDemoCurrencies()) + return loadDemoCurrencies() } val userId = card.getUserId() if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) { - return Result.Success(loadTokensOffline(card, userId)) + return loadTokensOffline(card, userId) } return when (val networkResult = networkService.getUserTokens(userId)) { is Result.Success -> { val tokens = networkResult.data.tokens.map { Currency.fromTokenResponse(it) } storageService.saveUserTokens(card.getUserId(), tokens) - Result.Success(tokens) + tokens } is Result.Failure -> { handleGetUserTokensFailure(card = card, userId = userId, error = networkResult.error) @@ -73,16 +73,16 @@ class UserTokensRepository( card: Card, userId: String, error: Throwable, - ): Result> { + ): List { return when (error) { is NoDataError -> { val tokens = storageService.getUserTokens(card) coroutineScope { launch { networkService.saveUserTokens(userId = userId, tokens = tokens) } } - Result.Success(tokens) + tokens } else -> { val tokens = storageService.getUserTokens(userId) ?: storageService.getUserTokens(card) - Result.Success(tokens) + tokens } } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt index ed617762f4..836d287739 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt @@ -39,7 +39,7 @@ sealed class WalletAction : Action { data class NoAccount( val wallet: Wallet, val blockchain: BlockchainNetwork, - val amountToCreateAccount: String + val amountToCreateAccount: String, ) : WalletAction() data class Failure(val wallet: Wallet, val errorMessage: String? = null) : WalletAction() @@ -47,6 +47,10 @@ sealed class WalletAction : Action { data class SetArtworkId(val artworkId: String?) : WalletAction() + sealed class UserTokens : WalletAction() { + object Loading : UserTokens() + object Loaded : UserTokens() + } sealed class MultiWallet : WalletAction() { data class SetIsMultiwalletAllowed(val isMultiwalletAllowed: Boolean) : MultiWallet() diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt index d6f0def028..f81d4662a5 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt @@ -45,6 +45,7 @@ data class WalletState( val totalBalance: TotalBalance? = null, val showBackupWarning: Boolean = false, val missingDerivations: List = emptyList(), + val loadingUserTokens: Boolean = false, ) : StateType { // if you do not delegate - the application crashes on startup, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt index 79aa56c3df..fbac7c7daa 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt @@ -314,6 +314,8 @@ private fun internalReduce(action: Action, state: AppState): WalletState { is WalletAction.AppCurrencyAction -> { newState = appCurrencyReducer.reduce(action, newState) } + is WalletAction.UserTokens.Loading -> newState = newState.copy(loadingUserTokens = true) + is WalletAction.UserTokens.Loaded -> newState = newState.copy(loadingUserTokens = false) else -> { /* no-op */ } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt index 9015ba4fc4..9d4fe68894 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt @@ -81,6 +81,8 @@ class MultiWalletView : WalletView() { handleRescanWarning(binding, state.missingDerivations.isNotEmpty()) walletsAdapter.submitList(state.walletsData, state.primaryBlockchain, state.primaryToken) + binding.pbLoadingUserTokens.show(state.loadingUserTokens) + binding.btnAddToken.setOnClickListener { val card = store.state.globalState.scanResponse!!.card store.dispatch( diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPaySingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPaySingleWalletView.kt index 09d00b294e..0139a7493c 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPaySingleWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPaySingleWalletView.kt @@ -20,6 +20,7 @@ class SaltPaySingleWalletView : WalletView() { btnAddToken.hide() rvPendingTransaction.hide() tvTwinCardNumber.hide() + pbLoadingUserTokens.hide() lCardBalance.root.hide() lAddress.root.hide() lSingleWalletBalance.root.show() diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt index 9b8a2e759f..ef8d0b746b 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt @@ -36,6 +36,7 @@ class SingleWalletView : WalletView() { rvMultiwallet.hide() btnAddToken.hide() rvPendingTransaction.hide() + pbLoadingUserTokens.hide() lCardBalance.root.show() lAddress.root.show() lSingleWalletBalance.root.hide() diff --git a/app/src/main/res/layout/fragment_wallet.xml b/app/src/main/res/layout/fragment_wallet.xml index 274f8303fd..60b1a2b33e 100644 --- a/app/src/main/res/layout/fragment_wallet.xml +++ b/app/src/main/res/layout/fragment_wallet.xml @@ -191,11 +191,31 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="16dp" + android:layout_marginBottom="16dp" android:nestedScrollingEnabled="false" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/rv_pending_transaction" /> + + + + From aa88f7a4160a7668105addc89fd92dbe0501f1d3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 29 Sep 2022 10:23:42 +0400 Subject: [PATCH 020/113] Updated on 2026-08-14 --- .../main/res/drawable/ic_tangem_warning.xml | 24 +++++++++++++++++++ .../layout/layout_wallet_rescan_warning.xml | 14 +++++------ 2 files changed, 31 insertions(+), 7 deletions(-) create mode 100644 app/src/main/res/drawable/ic_tangem_warning.xml diff --git a/app/src/main/res/drawable/ic_tangem_warning.xml b/app/src/main/res/drawable/ic_tangem_warning.xml new file mode 100644 index 0000000000..aa1e0c73ee --- /dev/null +++ b/app/src/main/res/drawable/ic_tangem_warning.xml @@ -0,0 +1,24 @@ + + + + + + + diff --git a/app/src/main/res/layout/layout_wallet_rescan_warning.xml b/app/src/main/res/layout/layout_wallet_rescan_warning.xml index 8d8e2f1233..c795c2c22a 100644 --- a/app/src/main/res/layout/layout_wallet_rescan_warning.xml +++ b/app/src/main/res/layout/layout_wallet_rescan_warning.xml @@ -8,9 +8,9 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingStart="16dp" - android:paddingTop="14dp" + android:paddingTop="12dp" android:paddingEnd="16dp" - android:paddingBottom="14dp"> + android:paddingBottom="12dp"> @@ -33,8 +33,8 @@ android:layout_marginStart="8dp" android:layout_marginEnd="16dp" android:text="@string/main_scan_card_warning_view_title" - android:textColor="@color/textBlack" - android:textSize="14sp" + android:textColor="@color/text_primary_1" + android:textSize="16sp" android:textStyle="bold" app:layout_constraintBottom_toTopOf="@id/tv_rescan_warning_subtitle" app:layout_constraintEnd_toStartOf="@id/iv_rescan_warning_end_icon" @@ -47,8 +47,8 @@ android:layout_height="wrap_content" android:layout_marginTop="4dp" android:text="@string/main_scan_card_warning_view_subtitle" - android:textColor="@color/iconGray" - android:textSize="12sp" + android:textColor="@color/text_secondary" + android:textSize="14sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="@id/tv_rescan_warning_title" app:layout_constraintStart_toStartOf="@id/tv_rescan_warning_title" From 1f9a74d40fa4db691e8e13018ec6a88d9c16d54b Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 29 Sep 2022 10:49:13 +0400 Subject: [PATCH 021/113] Updated on 2026-08-14 --- .../redux/OnboardingOtherCardsMiddleware.kt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt index e470680dbc..02950c3b3a 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt @@ -79,17 +79,21 @@ private fun handleOtherCardsAction(action: Action, dispatch: DispatchFunction) { val primaryBlockchain = updatedResponse.getBlockchain() val blockchainNetworks = if (primaryBlockchain != Blockchain.Unknown) { val primaryToken = updatedResponse.getPrimaryToken() - val blockchainNetwork = BlockchainNetwork(primaryBlockchain, updatedResponse.card).updateTokens( - listOfNotNull(primaryToken)) + val blockchainNetwork = + BlockchainNetwork(primaryBlockchain, updatedResponse.card).updateTokens( + listOfNotNull(primaryToken), + ) listOf(blockchainNetwork) } else { listOf( BlockchainNetwork(Blockchain.Bitcoin, updatedResponse.card), - BlockchainNetwork(Blockchain.Ethereum, updatedResponse.card) + BlockchainNetwork(Blockchain.Ethereum, updatedResponse.card), ) } - store.dispatch(WalletAction.MultiWallet.SaveCurrencies(blockchainNetworks)) + store.dispatch( + WalletAction.MultiWallet.SaveCurrencies(blockchainNetworks, updatedResponse.card), + ) delay(DELAY_SDK_DIALOG_CLOSE) store.dispatch(OnboardingOtherCardsAction.SetStepOfScreen(OnboardingOtherCardsStep.Done)) From de07248bbaa29db5962b340d69d0d057ae1d7c23 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 29 Sep 2022 13:57:23 +0400 Subject: [PATCH 022/113] Updated on 2026-08-14 --- app/src/main/AndroidManifest.xml | 13 ------------- app/src/main/res/values-v29/zendesk_styles.xml | 3 +-- app/src/main/res/values/zendesk_styles.xml | 5 ++++- 3 files changed, 5 insertions(+), 16 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b460cf581a..4422e037f9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,7 +7,6 @@ android:name="android.hardware.nfc" android:required="true" /> - @@ -131,18 +130,6 @@ - - - - - - - - - - diff --git a/app/src/main/res/values/zendesk_styles.xml b/app/src/main/res/values/zendesk_styles.xml index ebb4fcab6b..2513114d19 100644 --- a/app/src/main/res/values/zendesk_styles.xml +++ b/app/src/main/res/values/zendesk_styles.xml @@ -1,6 +1,9 @@ - From d0fe9b8b099c2b1684ff72b3e50d9482af216e56 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 29 Sep 2022 16:58:03 +0400 Subject: [PATCH 023/113] Updated on 2026-08-14 --- .../tangem/tap/features/details/ui/details/DetailsScreen.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt index 24129ec514..e0b4f1f186 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt @@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -86,7 +87,7 @@ fun WalletConnectDetailsItem( ) { Row( modifier = modifier - .height(84.dp) + .defaultMinSize(minHeight = 84.dp) .fillMaxWidth() .clickable { onItemsClick(SettingsElement.WalletConnect) }, horizontalArrangement = Arrangement.Start, @@ -99,7 +100,7 @@ fun WalletConnectDetailsItem( tint = colorResource(id = R.color.all_colors_azure), ) Column( - modifier = modifier.height(56.dp), + modifier = modifier.defaultMinSize(minHeight = 56.dp), horizontalAlignment = Alignment.Start, verticalArrangement = Arrangement.Center, ) { From 90e5b0012ba2d6680dcc82bbbaf0859feadf4558 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 29 Sep 2022 17:21:29 +0400 Subject: [PATCH 024/113] Updated on 2026-08-14 --- .../details/ui/walletconnect/WalletConnectScreen.kt | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt index 8474878afc..168bd4a024 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt @@ -140,13 +140,12 @@ private fun WalletConnectSessions( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { - Column(modifier = modifier) { - Text( - text = session.description, - style = TangemTypography.subtitle1, - color = colorResource(id = R.color.text_primary_1), - ) - } + Text( + text = session.description, + style = TangemTypography.subtitle1, + color = colorResource(id = R.color.text_primary_1), + modifier = modifier.weight(1f), + ) IconButton( onClick = { state.onRemoveSession(session.sessionId) }, ) { From 937011724fcc8242c3f90b8cbb874b22883cbcd4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 29 Sep 2022 17:30:00 +0400 Subject: [PATCH 025/113] Updated on 2026-08-14 --- .../domain/walletconnect/WalletConnectManager.kt | 14 ++++++++++++-- .../redux/walletconnect/WalletConnectAction.kt | 8 ++++++-- .../redux/walletconnect/WalletConnectMiddleware.kt | 12 ++++++++++++ app/src/main/res/values-de/strings_final.xml | 1 + app/src/main/res/values-fr/strings_final.xml | 1 + app/src/main/res/values-it/strings_final.xml | 1 + app/src/main/res/values-ru/strings_final.xml | 1 + app/src/main/res/values/strings_final.xml | 1 + 8 files changed, 35 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt index 45c3dee2e3..8a0362acb0 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt @@ -57,7 +57,10 @@ class WalletConnectManager { private var sessions: MutableMap = mutableMapOf() fun connect(wcUri: String) { - val session = WCSession.from(wcUri) ?: return + val session = WCSession.from(wcUri).guard { + store.dispatchOnMain(WalletConnectAction.FailureEstablishingSession(null, true)) + return + } if (sessions[session] != null) { store.dispatchOnMain(WalletConnectAction.RefuseOpeningSession) return @@ -65,7 +68,14 @@ class WalletConnectManager { val client = WCClient(httpClient = okHttpClient) setListeners(client) val peerId = UUID.randomUUID().toString() - client.connect(session, tangemPeerMeta, peerId) + + try { + client.connect(session, tangemPeerMeta, peerId) + } catch (exception: IllegalArgumentException) { + store.dispatchOnMain(WalletConnectAction.FailureEstablishingSession(null, true)) + return + } + sessions[session] = WalletConnectActiveData( peerId = peerId, remotePeerId = null, diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt index 0258af7316..5947f349ef 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt @@ -52,7 +52,11 @@ sealed class WalletConnectAction : Action { val updatedSession: WalletConnectSession, ) : WalletConnectAction() - data class FailureEstablishingSession(val session: WCSession?) : WalletConnectAction() + data class FailureEstablishingSession( + val session: WCSession?, + val showWarning: Boolean = false, + ) : WalletConnectAction() + data class SetSessionsRestored(val sessions: List) : WalletConnectAction() @@ -103,6 +107,6 @@ sealed class WalletConnectAction : Action { data class Sign( val id: Long, val data: ByteArray, val sessionData: WCSession, - ) : WalletConnectAction() + ) : WalletConnectAction() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt index cad8f8ab14..9265253b3a 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt @@ -9,6 +9,7 @@ import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen @@ -22,6 +23,7 @@ import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.scope import com.tangem.tap.store +import com.tangem.wallet.R import kotlinx.coroutines.launch import org.rekotlin.Action import org.rekotlin.Middleware @@ -99,6 +101,16 @@ class WalletConnectMiddleware { if (action.session != null) { walletConnectManager.disconnect(action.session) } + if (action.showWarning) { + store.dispatchOnMain( + GlobalAction.ShowDialog( + AppDialog.SimpleOkDialogRes( + headerId = R.string.common_warning, + messageId = R.string.wallet_connect_error_failed_to_connect, + ), + ), + ) + } } is WalletConnectAction.UnsupportedCard -> { store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedCard)) diff --git a/app/src/main/res/values-de/strings_final.xml b/app/src/main/res/values-de/strings_final.xml index f32a97ae89..08c0072255 100644 --- a/app/src/main/res/values-de/strings_final.xml +++ b/app/src/main/res/values-de/strings_final.xml @@ -88,4 +88,5 @@ Select network Scan your card To access all the networks you need to scan the card + Failed to establish WalletConnect session \ 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 ef550053ca..16105d506d 100644 --- a/app/src/main/res/values-fr/strings_final.xml +++ b/app/src/main/res/values-fr/strings_final.xml @@ -88,4 +88,5 @@ Select network Scan your card To access all the networks you need to scan the card + Failed to establish WalletConnect session \ 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 ef550053ca..16105d506d 100644 --- a/app/src/main/res/values-it/strings_final.xml +++ b/app/src/main/res/values-it/strings_final.xml @@ -88,4 +88,5 @@ Select network Scan your card To access all the networks you need to scan the card + Failed to establish WalletConnect session \ 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 b1ffa0ba10..225d7eb8cb 100644 --- a/app/src/main/res/values-ru/strings_final.xml +++ b/app/src/main/res/values-ru/strings_final.xml @@ -104,4 +104,5 @@ Сеть %s использует концепцию экзистенциального депозита. Если баланс вашего счета опустится ниже %s %s, он будет деактивирован, а все оставшиеся средства будут уничтожены. Отсканируйте карту Чтобы получить доступ ко всем сетям, вам необходимо отсканировать карту + Не удалось установить сессию WalletConnect diff --git a/app/src/main/res/values/strings_final.xml b/app/src/main/res/values/strings_final.xml index 4585bee0c1..bfa90d2130 100644 --- a/app/src/main/res/values/strings_final.xml +++ b/app/src/main/res/values/strings_final.xml @@ -101,4 +101,5 @@ Select network Scan your card To access all the networks you need to scan the card + Failed to establish WalletConnect session From d9f57b33b1fafd875c9d8ace2da413247ef8ee78 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 29 Sep 2022 17:41:10 +0400 Subject: [PATCH 026/113] Updated on 2026-08-14 --- .../tap/features/wallet/redux/WalletState.kt | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt index f81d4662a5..933ca823bc 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt @@ -193,21 +193,28 @@ data class WalletState( fun removeWallet(walletData: WalletData?): WalletState { if (walletData == null) return this - return if (walletData.currency is Currency.Blockchain) { - val walletStores = wallets.filterNot { - it.blockchainNetwork.blockchain == walletData.currency.blockchain - && it.blockchainNetwork.derivationPath == walletData.currency.derivationPath + return when (val currency = walletData.currency) { + is Currency.Blockchain -> { + val walletStores = wallets.filterNot { + it.blockchainNetwork.blockchain == currency.blockchain + && it.blockchainNetwork.derivationPath == currency.derivationPath + } + copy(wallets = walletStores) + .updateTotalBalance() + .updateProgressState() + } + is Currency.Token -> { + val walletStore = getWalletStore(walletData.currency) + val walletDataList = walletStore?.walletsData + ?.filterNot { it.currency == walletData.currency } + ?: emptyList() + val updatedWalletManager = walletStore?.walletManager?.also { it.removeToken(currency.token) } + val updatedWalletStore = walletStore?.copy( + walletsData = walletDataList, + walletManager = updatedWalletManager, + ) + updateWalletStore(updatedWalletStore) } - copy(wallets = walletStores) - .updateTotalBalance() - .updateProgressState() - } else { - val walletStore = getWalletStore(walletData.currency) - val walletDataList = walletStore?.walletsData - ?.filterNot { it.currency == walletData.currency } - ?: emptyList() - val updatedWalletStore = walletStore?.copy(walletsData = walletDataList) - updateWalletStore(updatedWalletStore) } } From 5db29f6fea8458c9a8bac2fca9c31bfdd2915571 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 29 Sep 2022 17:49:45 +0400 Subject: [PATCH 027/113] Updated on 2026-08-14 --- .../tap/features/wallet/ui/adapters/WalletAdapter.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt index af061d48f1..a688bb9e42 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt @@ -110,8 +110,12 @@ class WalletAdapter lContent.tvExchangeRate.text = wallet.fiatRateString ?: root.getString(id = R.string.token_item_no_rate) - cardWallet.setOnClickListener { - store.dispatch(WalletAction.MultiWallet.SelectWallet(wallet)) + if (wallet.walletAddresses != null) { + cardWallet.setOnClickListener { + store.dispatch(WalletAction.MultiWallet.SelectWallet(wallet)) + } + } else { + cardWallet.setOnClickListener(null) } } } From 9f14ffc2e0b128b5edcc75aba8a3f9b6de3f0f9a Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 29 Sep 2022 22:33:01 +0500 Subject: [PATCH 028/113] Updated on 2026-08-14 --- .../common/feedback/AdditionalFeedbackInfo.kt | 84 ++++++++++--------- .../common/feedback/FeedbackDataBuilder.kt | 22 +++-- .../send/redux/middlewares/SendMiddleware.kt | 3 +- 3 files changed, 58 insertions(+), 51 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt b/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt index 70912f24a6..97c8a9e41f 100644 --- a/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt +++ b/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt @@ -7,16 +7,19 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.Wallet import com.tangem.blockchain.common.WalletManager +import com.tangem.blockchain.common.address.Address +import com.tangem.common.card.CardWallet import com.tangem.domain.common.ScanResponse import com.tangem.tap.common.extensions.stripZeroPlainString class AdditionalFeedbackInfo { class EmailWalletInfo( var blockchain: Blockchain = Blockchain.Unknown, - var address: String = "", - var explorerLink: String = "", - var host: String = "", var derivationPath: String = "", + var outputsCount: String? = null, + var host: String = "", + var addresses: String = "", + var explorerLink: String = "", ) var appVersion: String = "" @@ -48,23 +51,14 @@ class AdditionalFeedbackInfo { cardBlockchain = data.walletData?.blockchain ?: "" cardFirmwareVersion = data.card.firmwareVersion.stringValue cardIssuer = data.card.issuer.name - signedHashesCount = data.card.wallets - .joinToString("; ") { "${it.curve.curve} - ${it.totalSignedHashes}" } + signedHashesCount = formatSignedHashes(data.card.wallets) } fun setWalletsInfo(walletManagers: List) { walletsInfo.clear() tokens.clear() walletManagers.forEach { manager -> - walletsInfo.add( - EmailWalletInfo( - blockchain = manager.wallet.blockchain, - address = getAddress(manager.wallet), - explorerLink = getExploreUri(manager.wallet), - host = manager.currentHost, - derivationPath = manager.wallet.publicKey.derivationPath?.rawPath ?: "" - ) - ) + walletsInfo.add(createEmailWalletInfo(manager)) if (manager.cardTokens.isNotEmpty()) { tokens[manager.wallet.blockchain] = manager.cardTokens } @@ -72,45 +66,55 @@ class AdditionalFeedbackInfo { } fun updateOnSendError( - wallet: Wallet, - host: String, + walletManager: WalletManager, amountToSend: Amount, feeAmount: Amount, destinationAddress: String, ) { - onSendErrorWalletInfo = EmailWalletInfo( - blockchain = wallet.blockchain, - address = getAddress(wallet), - explorerLink = getExploreUri(wallet), - host = host, - derivationPath = wallet.publicKey.derivationPath?.rawPath ?: "" - ) - + onSendErrorWalletInfo = createEmailWalletInfo(walletManager) this.destinationAddress = destinationAddress amount = amountToSend.value?.stripZeroPlainString() ?: "0" fee = feeAmount.value?.stripZeroPlainString() ?: "0" token = if (amountToSend.type is AmountType.Token) amountToSend.currencySymbol else "" } - private fun getAddress(wallet: Wallet): String { - return if (wallet.addresses.size == 1) { - wallet.address - } else { - val addresses = wallet.addresses.joinToString(", ") { - "${it.type.javaClass.simpleName} - ${it.value}" - } - "Multiple address: $addresses" + private fun createEmailWalletInfo(walletManager: WalletManager): EmailWalletInfo { + return EmailWalletInfo( + blockchain = walletManager.wallet.blockchain, + derivationPath = walletManager.wallet.publicKey.derivationPath?.rawPath ?: "", + outputsCount = walletManager.outputsCount?.toString(), + host = walletManager.currentHost, + addresses = formatAddresses(walletManager.wallet), + explorerLink = formatExploreUrls(walletManager.wallet), + ) + } + + private fun formatSignedHashes(wallets: List): String { + return wallets.joinToString("\n") { "Signed hashes: ${it.curve.curve} - ${it.totalSignedHashes}" } + } + + private fun formatAddresses(wallet: Wallet): String { + return wallet.formatAddressWith("Multiple address:") { + "${it.name} - ${it.value}" } } - private fun getExploreUri(wallet: Wallet): String { - return if (wallet.addresses.size == 1) { - wallet.getExploreUrl(wallet.address) - } else { - val links = wallet.addresses.joinToString(", ") { - "${it.type.javaClass.simpleName} - ${wallet.getExploreUrl(it.value)}" - } - "Multiple explorers links: $links" + private fun formatExploreUrls(wallet: Wallet): String { + return wallet.formatAddressWith("Multiple explorers links:") { + "${it.name} - ${wallet.getExploreUrl(it.value)}" } } + + private fun Wallet.formatAddressWith(with: String, mapAddress: (Address) -> String): String { + return if (addresses.size == 1) { + getExploreUrl(address) + } else { + addresses.map { mapAddress(it) }.toMutableList() + .apply { add(0, with) } + .joinToString("\n") + } + } + + private val Address.name: String + get() = type.javaClass.simpleName } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackDataBuilder.kt b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackDataBuilder.kt index a3a9ee5575..46cf302fee 100644 --- a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackDataBuilder.kt +++ b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackDataBuilder.kt @@ -3,7 +3,7 @@ package com.tangem.tap.common.feedback import com.tangem.tap.common.extensions.breakLine class FeedbackDataBuilder( - private val infoHolder: AdditionalFeedbackInfo + private val infoHolder: AdditionalFeedbackInfo, ) { val builder = StringBuilder() @@ -21,7 +21,7 @@ class FeedbackDataBuilder( builder.appendKeyValue("Card ID", infoHolder.cardId) builder.appendKeyValue("Firmware version", infoHolder.cardFirmwareVersion) builder.appendKeyValue("Card Blockchain", infoHolder.cardBlockchain) - builder.appendKeyValue("Signed hashes", infoHolder.signedHashesCount) + builder.appendKeyValue("", infoHolder.signedHashesCount) return this } @@ -29,20 +29,22 @@ class FeedbackDataBuilder( infoHolder.walletsInfo.forEach { builder.appendDelimiter() builder.appendKeyValue("Blockchain", it.blockchain.fullName) - builder.appendKeyValue("Host", it.host) - builder.appendKeyValue("Wallet address", it.address) builder.appendKeyValue("Derivation path", it.derivationPath) - builder.appendKeyValue("Explorer link", it.explorerLink) + builder.appendKeyValue("Outputs count", it.outputsCount) infoHolder.tokens[it.blockchain]?.let { tokens -> builder.append("Tokens:") breakLine() tokens.forEach { token -> - builder.appendKeyValue("Name", token.name) builder.appendKeyValue("ID", token.id ?: "[custom token]") + builder.appendKeyValue("Name", token.name) builder.appendKeyValue("Contract address", token.contractAddress) } } + + builder.appendKeyValue("Host", it.host) + builder.appendKeyValue("Wallet address", it.addresses) + builder.appendKeyValue("Explorer link", it.explorerLink) } return this } @@ -55,7 +57,7 @@ class FeedbackDataBuilder( builder.appendKeyValue("Token", infoHolder.token) builder.appendKeyValue("Error", error) builder.appendDelimiter() - builder.appendKeyValue("Source address", walletInfo.address) + builder.appendKeyValue("Source address", walletInfo.addresses) builder.appendKeyValue("Destination address", infoHolder.destinationAddress) builder.appendKeyValue("Amount", infoHolder.amount) builder.appendKeyValue("Fee", infoHolder.fee) @@ -72,8 +74,10 @@ class FeedbackDataBuilder( fun build(): String = builder.toString() } -private fun StringBuilder.appendKeyValue(key: String, value: String): StringBuilder { - return if (value.isNotBlank()) this.append("$key: $value\n") else this +private fun StringBuilder.appendKeyValue(key: String, value: String?): StringBuilder = when { + value.isNullOrBlank() -> this + key.isBlank() -> this.append("$value\n") + else -> this.append("$key: $value\n") } private fun StringBuilder.appendDelimiter(): StringBuilder = append("----------\n") \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt index 54be369642..defca803e3 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt @@ -246,8 +246,7 @@ private fun sendTransaction( } is SimpleResult.Failure -> { store.state.globalState.feedbackManager?.infoHolder?.updateOnSendError( - wallet = walletManager.wallet, - host = walletManager.currentHost, + walletManager = walletManager, amountToSend = amountToSend, feeAmount = feeAmount, destinationAddress = destinationAddress, From c307bc2110ff198d88c2069479d5e6740160938c Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 30 Sep 2022 12:21:42 +0400 Subject: [PATCH 029/113] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/domain/TapErrors.kt | 1 + .../domain/walletconnect/WalletConnectManager.kt | 14 ++++++++++++-- app/src/main/res/values-ru/strings_final.xml | 1 - dependencies.gradle | 2 +- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt index 0b74cb1912..5a0cf85668 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt @@ -55,6 +55,7 @@ sealed class TapError( sealed class WalletConnect { object UnsupportedDapp : TapError(R.string.wallet_connect_error_unsupported_dapp) + object UnsupportedLink : TapError(R.string.wallet_connect_error_failed_to_connect) } data class ValidateTransactionErrors( diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt index 0a108530df..de71029d2c 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt @@ -60,7 +60,12 @@ class WalletConnectManager { fun connect(wcUri: String) { val session = WCSession.from(wcUri).guard { - store.dispatchOnMain(WalletConnectAction.FailureEstablishingSession(null, true)) + store.dispatchOnMain( + WalletConnectAction.FailureEstablishingSession( + session = null, + error = TapError.WalletConnect.UnsupportedLink, + ), + ) return } if (sessions[session] != null) { @@ -74,7 +79,12 @@ class WalletConnectManager { try { client.connect(session, tangemPeerMeta, peerId) } catch (exception: IllegalArgumentException) { - store.dispatchOnMain(WalletConnectAction.FailureEstablishingSession(null, true)) + store.dispatchOnMain( + WalletConnectAction.FailureEstablishingSession( + session = null, + error = TapError.WalletConnect.UnsupportedLink, + ), + ) return } diff --git a/app/src/main/res/values-ru/strings_final.xml b/app/src/main/res/values-ru/strings_final.xml index 7e9e83795e..81edc55d19 100644 --- a/app/src/main/res/values-ru/strings_final.xml +++ b/app/src/main/res/values-ru/strings_final.xml @@ -103,7 +103,6 @@ Tag Недопустимый Memo. Он не будет добавлен в транзакцию. Недопустимый Tag. Он не будет добавлен в транзакцию. - Сеть %s использует концепцию экзистенциального депозита. Если баланс вашего счета опустится ниже %s %s, он будет деактивирован, а все оставшиеся средства будут уничтожены. Не удалось установить сессию WalletConnect Cоединение с этим Dapp сервисом не может быть установлено из-за его технической реализации. diff --git a/dependencies.gradle b/dependencies.gradle index 5d1d0d6fa5..0b11f515b1 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -2,7 +2,7 @@ ext.versions = [ kotlin : '1.6.10', build_gradle : '7.1.3', tangem_card_sdk : 'develop-159', - tangem_blockchain_sdk: 'develop-115', + tangem_blockchain_sdk: 'develop-121', // tangem_blockchain_sdk: '0.0.1', ] From 76a4df457222e4b3659690794822de15d15646ff Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 1 Oct 2022 00:01:45 +0500 Subject: [PATCH 030/113] Updated on 2026-08-14 --- .../tap/common/compose/PinCodeWidget.kt | 172 ++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 app/src/main/java/com/tangem/tap/common/compose/PinCodeWidget.kt diff --git a/app/src/main/java/com/tangem/tap/common/compose/PinCodeWidget.kt b/app/src/main/java/com/tangem/tap/common/compose/PinCodeWidget.kt new file mode 100644 index 0000000000..31c826f6be --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/compose/PinCodeWidget.kt @@ -0,0 +1,172 @@ +package com.tangem.tap.common.compose + +import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.LocalTextStyle +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.material.TextField +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.core.text.isDigitsOnly + +/** +[REDACTED_AUTHOR] + */ +@OptIn(ExperimentalComposeUiApi::class) +@Composable +fun PinCodeWidget( + config: PinViewConfig = tangemPinConfig, + onPinChanged: (String, Boolean) -> Unit = { pin, isLastSymbolEntered -> }, +) { + val focusRequester = remember { FocusRequester() } + val keyboardController = LocalSoftwareKeyboardController.current + + val rTextFieldValue = remember { mutableStateOf(TextFieldValue("")) } + val indexedSymbols: List = createPinSymbolsList(config.pinsCount, rTextFieldValue.value.text) + + fun isLastSymbolEntered(): Boolean = rTextFieldValue.value.text.length == config.pinsCount + + fun handleOnTextFieldValueChanged(value: TextFieldValue) { + if (!value.text.isDigitsOnly()) return + + if (value.text.length <= config.pinsCount) { + rTextFieldValue.value = value + onPinChanged(value.text, isLastSymbolEntered()) + } + } + + Box( + modifier = config.modifier + .pointerInput(Unit) { + detectTapGestures { + focusRequester.requestFocus() + keyboardController?.show() + } + }, + ) { + Row { + for (index in 0 until config.pinsCount) { + PinElement( + config = config, + pinSymbol = indexedSymbols[index] ?: "", + ) + } + } + TextField( + modifier = Modifier + .alpha(0f) + .size(1.dp) + .align(Alignment.Center) + .focusRequester(focusRequester), + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Decimal, + imeAction = if (isLastSymbolEntered()) ImeAction.Done else ImeAction.Next, + ), + keyboardActions = KeyboardActions( + onDone = { keyboardController?.hide() }, + ), + value = rTextFieldValue.value, + onValueChange = ::handleOnTextFieldValueChanged, + ) + } + + LaunchedEffect(Unit) { + focusRequester.requestFocus() + } +} + +@Composable +private fun PinElement( + config: PinViewConfig, + pinSymbol: String, +) { + Box(Modifier.padding(config.pinBoxPadding)) { + Box(config.pinBoxModifier) { + Text( + text = pinSymbol, + modifier = config.pinTextModifier.align(Alignment.Center), + style = config.pinsTextStyle ?: LocalTextStyle.current, + ) + } + } +} + +@Composable +private fun HiddenInputField() { +} + +private fun createPinSymbolsList(size: Int, text: String): List = List(size) { + try { + text[it].toString() + } catch (ex: IndexOutOfBoundsException) { + null + } +} + +data class PinViewConfig( + val modifier: Modifier = Modifier, + val pinBoxModifier: Modifier = Modifier, + val pinBoxPadding: Dp = 0.dp, + val pinTextModifier: Modifier = Modifier, + val pinsCount: Int = 4, + val pinsTextStyle: TextStyle? = null, +) + +private val tangemPinConfig = PinViewConfig( + modifier = Modifier + .wrapContentSize(), + pinBoxModifier = Modifier + .width(42.dp) + .height(56.dp) + .clip(RoundedCornerShape(8.dp)) + .background(Color(0xFFF5F5F5)), + pinBoxPadding = 6.dp, + pinTextModifier = Modifier, + pinsCount = 4, + pinsTextStyle = TextStyle( + fontWeight = FontWeight(500), + fontSize = 24.sp, + ), +) + +@Preview +@Composable +fun PinCodeWidgetPreview() { + Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) { + PinCodeWidget(tangemPinConfig) + } +} \ No newline at end of file From d9516b53161c9cd057a3053b2eeefeead19fa5d0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 1 Oct 2022 20:16:49 +0500 Subject: [PATCH 031/113] Updated on 2026-08-14 --- .../details/ui/details/DetailsViewModel.kt | 5 ++- .../tap/features/home/redux/HomeMiddleware.kt | 14 +++++- .../redux/OnboardingWalletMiddleware.kt | 9 +++- .../wallet/redux/reducers/WalletReducer.kt | 3 +- .../wallet/ui/wallet/SaltPayBalanceWidget.kt | 21 +++++++-- dependencies.gradle | 6 +-- .../tangem/domain/common/TapWorkarounds.kt | 44 ++++++++++++++----- .../domain/common/extensions/Blockchain.kt | 6 +-- .../domain/common/extensions/Coroutine.kt | 22 +++++++++- 9 files changed, 101 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt index 97ce019a84..502437104c 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.details.ui.details +import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.tap.common.feedback.FeedbackEmail import com.tangem.tap.common.feedback.SupportInfo @@ -25,7 +26,9 @@ class DetailsViewModel(private val store: Store) { if (state.scanResponse?.card?.isMultiwalletAllowed == true) it else null } SettingsElement.LinkMoreCards -> { - if (state.createBackupAllowed) it else null + // if (state.createBackupAllowed) it else null + // TODO: SaltPay: temporary excluding backup process for Visa cards + if (state.createBackupAllowed && state.scanResponse?.card?.isSaltPay != true) it else null } SettingsElement.PrivacyPolicy -> { if (state.privacyPolicyUrl != null) it else null 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 374b8d4406..644f172e29 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 @@ -1,5 +1,6 @@ package com.tangem.tap.features.home.redux +import com.tangem.domain.common.TapWorkarounds.isSaltPayVisa import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE import com.tangem.tap.common.analytics.AnalyticsEvent @@ -64,7 +65,7 @@ private val homeMiddleware: Middleware = { _, _ -> } store.state.globalState.analyticsHandlers?.triggerEvent( event = AnalyticsEvent.GET_CARD, - params = mapOf(AnalyticsParam.SOURCE.param to GetCardSourceParams.WELCOME.param) + params = mapOf(AnalyticsParam.SOURCE.param to GetCardSourceParams.WELCOME.param), ) } } @@ -82,6 +83,17 @@ private fun handleReadCard() { store.state.globalState.tapWalletManager.updateConfigManager(scanResponse) store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse)) + // TODO: SaltPay: temporary excluding backup process for Visa cards + if (scanResponse.card.isSaltPayVisa) { + scope.launch { + store.onCardScanned(scanResponse) + withMainContext { + navigateTo(AppScreen.Wallet, null) + } + } + return@ScanCard + } + if (OnboardingHelper.isOnboardingCase(scanResponse)) { val navigateTo = OnboardingHelper.whereToNavigate(scanResponse) store.dispatch(GlobalAction.Onboarding.Start(scanResponse)) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index 6f332cfd34..016fee1817 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -9,7 +9,7 @@ import com.tangem.domain.common.TapWorkarounds import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.domain.common.extensions.withMainContext import com.tangem.operations.backup.BackupService -import com.tangem.tap.* +import com.tangem.tap.backupService import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction @@ -21,6 +21,11 @@ import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.home.redux.HomeAction import com.tangem.tap.features.wallet.redux.Artwork import com.tangem.tap.features.wallet.redux.WalletAction +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 kotlinx.coroutines.launch import org.rekotlin.Action import org.rekotlin.Middleware @@ -217,7 +222,7 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) is BackupAction.AddBackupCard -> { backupService.skipCompatibilityChecks = true tangemSdk.config.filter.cardIdFilter = - CardFilter.Companion.ItemFilter.Allow(TapWorkarounds.saltPayCardIds.toSet()) + CardFilter.Companion.ItemFilter.Allow(TapWorkarounds.saltPayTangemCardIds.toSet()) backupService.addBackupCard { result -> backupService.skipCompatibilityChecks = false diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt index 413e9e6f77..9579f7311e 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt @@ -438,8 +438,7 @@ private fun setSingleWalletFiatRates( appCurrency: FiatCurrency, rateFormatter: (BigDecimal) -> String, state: WalletState, -): - WalletState { +): WalletState { val blockchainFiatRate = fiatRates.entries.firstOrNull { it.key.isBlockchain() } val tokenFiatRate = fiatRates.entries.firstOrNull { it.key.isToken() } Timber.e("Token Fiat Rate is ${tokenFiatRate ?: "NULL"}") diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPayBalanceWidget.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPayBalanceWidget.kt index 95509f5b37..49fcb89c16 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPayBalanceWidget.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPayBalanceWidget.kt @@ -1,12 +1,15 @@ package com.tangem.tap.features.wallet.ui.wallet +import com.tangem.domain.common.extensions.debounce import com.tangem.tap.common.entities.FiatCurrency import com.tangem.tap.common.extensions.animateVisibility import com.tangem.tap.common.extensions.formatAmountAsSpannedString import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.mainScope import com.tangem.tap.store import com.tangem.wallet.databinding.LayoutSingleWalletBalanceBinding +import org.rekotlin.Action import java.math.BigDecimal data class SaltPayBalanceWidgetData( @@ -26,7 +29,7 @@ class SaltPayBalanceWidget( veilBalance.veil() veilBalanceCrypto.veil() } else { - veilBalance.unVeil() + // veilBalance.unVeil() veilBalanceCrypto.unVeil() } tvProcessing.animateVisibility( @@ -35,9 +38,18 @@ class SaltPayBalanceWidget( veilBalanceCrypto.animateVisibility( show = data.state != ProgressState.Error, ) - tvBalance.text = data.fiatAmount?.formatAmountAsSpannedString( - currencySymbol = data.fiatCurrency?.symbol ?: "", - ) + if (data.fiatAmount == null) { + //TODO: SaltPay: A tricky solution to the problem with displaying rates + // If the rates are loaded after the walletManager.update() is completely updated, + // then this problem can be avoided + actionDebouncer(WalletAction.LoadFiatRate()) + } else { + veilBalance.unVeil() + tvBalance.text = data.fiatAmount?.formatAmountAsSpannedString( + currencySymbol = data.fiatCurrency?.symbol ?: "", + ) + } + tvBalanceCrypto.text = data.currency tvCurrencyName.text = data.fiatCurrency?.code @@ -48,4 +60,5 @@ class SaltPayBalanceWidget( } } +private val actionDebouncer = debounce(500, mainScope) { store.dispatch(it) } diff --git a/dependencies.gradle b/dependencies.gradle index b0dc5d199b..465ca3866a 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -1,9 +1,9 @@ ext.versions = [ kotlin : '1.6.10', build_gradle : '7.1.3', - tangem_card_sdk : 'develop-159', - // tangem_card_sdk: '0.0.1', - tangem_blockchain_sdk: 'develop-115', + // tangem_card_sdk : 'develop-159', + tangem_card_sdk: '0.0.1', + tangem_blockchain_sdk: 'develop-122', // tangem_blockchain_sdk: '0.0.1', ] diff --git a/domain/src/main/java/com/tangem/domain/common/TapWorkarounds.kt b/domain/src/main/java/com/tangem/domain/common/TapWorkarounds.kt index ab57eb34d8..6c85f64266 100644 --- a/domain/src/main/java/com/tangem/domain/common/TapWorkarounds.kt +++ b/domain/src/main/java/com/tangem/domain/common/TapWorkarounds.kt @@ -16,8 +16,15 @@ object TapWorkarounds { val Card.isStart2Coin: Boolean get() = isStart2CoinIssuer(issuer.name) + val Card.isSaltPay: Boolean - get() = saltPayCardIds.contains(cardId) || saltPayBatches.contains(batchId) + get() = isSaltPayVisa || isSaltPayTangem + + val Card.isSaltPayVisa: Boolean + get() = saltPayVisaBatches.contains(batchId) + + val Card.isSaltPayTangem: Boolean + get() = saltPayTangemCardIds.contains(cardId) val Card.isTestCard: Boolean get() = batchId == TEST_CARD_BATCH && cardId.startsWith(TEST_CARD_ID_STARTS_WITH) @@ -60,7 +67,7 @@ object TapWorkarounds { ) private val excludedIssuers = listOf( - "TTM BANK" + "TTM BANK", ) private val tangemWalletBatches = listOf("AC01") @@ -84,11 +91,9 @@ object TapWorkarounds { "AC01", "AC02", "CB95", ) - val saltPayCardIds = listOf( - "AE02000000000194", - "AC03000000076195", - "AC79000000000012", // TODO: remove my testing CIDs - "AC79000000000004",// TODO: remove my testing CIDs + val saltPayTangemCardIds = listOf( + "AC01000000000015", // TODO: remove testing CIDs + "AC03000000000088", // TODO: remove testing CIDs "AC03000000076070", "AC03000000076088", "AC03000000076096", @@ -105,17 +110,32 @@ object TapWorkarounds { "AC03000000076203", "AC03000000076211", "AC03000000076229", - // TODO: add cids + // added 01.10.2022 + "AC01000000033503", + "AC01000000033594", + "AC01000000033586", + "AC01000000034477", + "AC01000000032760", + "AC01000000033867", + "AC01000000032653", + "AC01000000032752", + "AC01000000034485", + "AC01000000033644", + "AC01000000037454", + "AC01000000037462", ) - val saltPayBatches = listOf("AE02") + private val saltPayVisaBatches = listOf( + "AE02", + "AE03", + ) val saltPayToken: Token get() = Token( name = "Wrapped xDAI", - "WxDAI", - "0x4346186e7461cB4DF06bCFCB4cD591423022e417", - 18, + symbol = "WxDAI", + contractAddress = "0x4346186e7461cB4DF06bCFCB4cD591423022e417", + decimals = 18, id = "xdai", ) } \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt b/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt index a335a847a4..f8ed090152 100644 --- a/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt +++ b/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt @@ -44,6 +44,7 @@ fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? { "optimistic-ethereum" -> Blockchain.Optimism "optimistic-ethereum/test" -> Blockchain.OptimismTestnet "dash" -> Blockchain.Dash + "wxdai" -> Blockchain.SaltPay else -> null } } @@ -92,7 +93,7 @@ fun Blockchain.toNetworkId(): String { Blockchain.Optimism -> "optimistic-ethereum" Blockchain.OptimismTestnet -> "optimistic-ethereum/test" Blockchain.Dash -> "dash" - Blockchain.SaltPay -> "xdai"//TODO + Blockchain.SaltPay -> "wxdai" else -> "unknown" // TODO } } @@ -119,11 +120,10 @@ fun Blockchain.toCoinId(): String { Blockchain.Tezos -> "tezos" Blockchain.XRP -> "ripple" Blockchain.Dogecoin -> "dogecoin" - Blockchain.Gnosis -> "xdai" + Blockchain.Gnosis, Blockchain.SaltPay -> "xdai" Blockchain.Kusama -> "kusama" Blockchain.Optimism, Blockchain.OptimismTestnet -> "ethereum" Blockchain.Dash -> "dash" - Blockchain.SaltPay -> "xdai" //TODO Blockchain.Unknown -> "unknown" else -> "unknown" // TODO } diff --git a/domain/src/main/java/com/tangem/domain/common/extensions/Coroutine.kt b/domain/src/main/java/com/tangem/domain/common/extensions/Coroutine.kt index 0f7f23dd82..0cb52aa4a1 100644 --- a/domain/src/main/java/com/tangem/domain/common/extensions/Coroutine.kt +++ b/domain/src/main/java/com/tangem/domain/common/extensions/Coroutine.kt @@ -1,7 +1,11 @@ package com.tangem.domain.common.extensions +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext /** @@ -9,4 +13,20 @@ import kotlinx.coroutines.withContext */ suspend fun withMainContext(block: suspend CoroutineScope.() -> T): T = withContext(Dispatchers.Main, block) -suspend fun withIOContext(block: suspend CoroutineScope.() -> T): T = withContext(Dispatchers.IO, block) \ No newline at end of file +suspend fun withIOContext(block: suspend CoroutineScope.() -> T): T = withContext(Dispatchers.IO, block) + +fun debounce( + waitMs: Long = 300L, + coroutineScope: CoroutineScope, + runDestinationOn: CoroutineDispatcher = Dispatchers.Unconfined, + destinationFunction: (T) -> Unit, +): (T) -> Unit { + var debounceJob: Job? = null + return { param: T -> + debounceJob?.cancel() + debounceJob = coroutineScope.launch { + delay(waitMs) + withContext(runDestinationOn) { destinationFunction(param) } + } + } +} \ No newline at end of file From fd76b9dccf5a7a8a70ba88f7f488f9885da47fc9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 3 Oct 2022 11:12:26 +0400 Subject: [PATCH 032/113] Updated on 2026-08-14 --- .../details/redux/walletconnect/WalletConnectMiddleware.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt index e748673f9e..ed2c68c8d6 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt @@ -318,9 +318,11 @@ class WalletConnectMiddleware { } else { blockchain } + val derivation = blockchainToMake.derivationPath(store.state.globalState.scanResponse?.card?.derivationStyle) + ?.rawPath val blockchainNetwork = BlockchainNetwork( blockchain = blockchainToMake, - derivationPath = wallet.derivationPath?.rawPath, + derivationPath = derivation, tokens = emptyList(), ) return walletState.getWalletManager(blockchainNetwork) From b89206903dfd74d9cb2f0612d281065a9040e390 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 3 Oct 2022 11:29:48 +0400 Subject: [PATCH 033/113] Updated on 2026-08-14 --- .../addCustomToken/redux/AddCustomTokenHub.kt | 44 ++++++++++++++++--- .../redux/AddCustomTokenState.kt | 36 ++++++++++++--- 2 files changed, 67 insertions(+), 13 deletions(-) diff --git a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt index 1c09b4116a..dfc524e857 100644 --- a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt +++ b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt @@ -5,7 +5,9 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.common.extensions.guard import com.tangem.common.services.Result import com.tangem.domain.AddCustomTokenError -import com.tangem.domain.AddCustomTokenError.Warning.* +import com.tangem.domain.AddCustomTokenError.Warning.PotentialScamToken +import com.tangem.domain.AddCustomTokenError.Warning.TokenAlreadyAdded +import com.tangem.domain.AddCustomTokenError.Warning.UnsupportedSolanaToken import com.tangem.domain.AddCustomTokenException import com.tangem.domain.DomainDialog import com.tangem.domain.DomainWrapped @@ -14,10 +16,40 @@ import com.tangem.domain.common.extensions.canHandleToken import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.common.extensions.supportedBlockchains import com.tangem.domain.common.extensions.toNetworkId -import com.tangem.domain.common.form.* -import com.tangem.domain.features.addCustomToken.* -import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.* -import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.* +import com.tangem.domain.common.form.Field +import com.tangem.domain.common.form.Form +import com.tangem.domain.common.form.TokenContractAddressValidator +import com.tangem.domain.common.form.TokenDecimalsValidator +import com.tangem.domain.common.form.TokenNameValidator +import com.tangem.domain.common.form.TokenNetworkValidator +import com.tangem.domain.common.form.TokenSymbolValidator +import com.tangem.domain.features.addCustomToken.AddCustomTokenService +import com.tangem.domain.features.addCustomToken.CustomTokenFieldId +import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.ContractAddress +import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Decimals +import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.DerivationPath +import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Name +import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Network +import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Symbol +import com.tangem.domain.features.addCustomToken.TokenBlockchainField +import com.tangem.domain.features.addCustomToken.TokenDerivationPathField +import com.tangem.domain.features.addCustomToken.TokenField +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.FieldError +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.Init +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnAddCustomTokenClicked +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnCreate +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnDestroy +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenContractAddressChanged +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenDecimalsChanged +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenDerivationPathChanged +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenNameChanged +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenNetworkChanged +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenSymbolChanged +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.Screen +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.SetFoundTokenInfo +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.UpdateForm +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.Warning +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenState.Companion.createInitialScreenState import com.tangem.domain.redux.BaseStoreHub import com.tangem.domain.redux.DomainState import com.tangem.domain.redux.ReStoreReducer @@ -569,12 +601,12 @@ private class AddCustomTokenReducer( tangemTechService = globalState.networkServices.tangemTechService, supportedTokenNetworkIds = supportedTokenNetworkIds ) - val form = Form(AddCustomTokenState.createFormFields(card, CustomTokenType.Blockchain)) state.copy( cardDerivationStyle = card.derivationStyle, form = form, tangemTechServiceManager = tangemTechServiceManager, + screenState = createInitialScreenState(card.settings.isHDWalletAllowed), ) } is OnDestroy -> { diff --git a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt index 97c652da19..2ecd595e8e 100644 --- a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt +++ b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt @@ -9,9 +9,31 @@ import com.tangem.domain.DomainWrapped import com.tangem.domain.common.TapWorkarounds.isTestCard import com.tangem.domain.common.extensions.supportedBlockchains import com.tangem.domain.common.extensions.supportedTokens -import com.tangem.domain.common.form.* -import com.tangem.domain.features.addCustomToken.* -import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.* +import com.tangem.domain.common.form.CustomTokenValidator +import com.tangem.domain.common.form.DataField +import com.tangem.domain.common.form.FieldDataConverter +import com.tangem.domain.common.form.FieldId +import com.tangem.domain.common.form.FieldToJsonConverter +import com.tangem.domain.common.form.Form +import com.tangem.domain.common.form.StringIsEmptyValidator +import com.tangem.domain.common.form.StringIsNotEmptyValidator +import com.tangem.domain.common.form.TokenContractAddressValidator +import com.tangem.domain.common.form.TokenDecimalsValidator +import com.tangem.domain.common.form.TokenNameValidator +import com.tangem.domain.common.form.TokenNetworkValidator +import com.tangem.domain.common.form.TokenSymbolValidator +import com.tangem.domain.features.addCustomToken.AddCustomTokenService +import com.tangem.domain.features.addCustomToken.CustomCurrency +import com.tangem.domain.features.addCustomToken.CustomTokenFieldId +import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.ContractAddress +import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Decimals +import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.DerivationPath +import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Name +import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Network +import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Symbol +import com.tangem.domain.features.addCustomToken.TokenBlockchainField +import com.tangem.domain.features.addCustomToken.TokenDerivationPathField +import com.tangem.domain.features.addCustomToken.TokenField import com.tangem.domain.redux.DomainState import com.tangem.domain.redux.state.StringActionStateConverter import com.tangem.network.api.tangemTech.CoinsResponse @@ -119,7 +141,7 @@ data class AddCustomTokenState( formErrors = emptyMap(), foundToken = null, warnings = emptySet(), - screenState = createInitialScreenState(), + screenState = createInitialScreenState(card.settings.isHDWalletAllowed), tangemTechServiceManager = null, ) } @@ -223,15 +245,15 @@ data class AddCustomTokenState( return (listOf(Blockchain.Unknown) + evmBlockchains).sortByName() } - private fun createInitialScreenState(): ScreenState { + internal fun createInitialScreenState(showDerivationPathField: Boolean = false): ScreenState { return ScreenState( contractAddressField = ViewStates.TokenField(), network = ViewStates.TokenField(), name = ViewStates.TokenField(isEnabled = false), symbol = ViewStates.TokenField(isEnabled = false), decimals = ViewStates.TokenField(isEnabled = false), - derivationPath = ViewStates.TokenField(), - addButton = ViewStates.AddButton(isEnabled = false) + derivationPath = ViewStates.TokenField(isVisible = showDerivationPathField), + addButton = ViewStates.AddButton(isEnabled = false), ) } } From 4e98a7b8059fa135f3ab3f13842c2dbd46e1846f Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 3 Oct 2022 12:15:19 +0400 Subject: [PATCH 034/113] Updated on 2026-08-14 --- .../tap/features/wallet/redux/reducers/MultiWalletReducer.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt index 52ab9c4d75..892b5db149 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt @@ -65,7 +65,7 @@ class MultiWalletReducer { } val selectedCurrency = if (!state.isMultiwalletAllowed) { - wallets[0].walletsData[0].currency + wallets.firstOrNull()?.walletsData?.firstOrNull()?.currency } else { state.selectedCurrency } From 84fc5613b98ff3e555855195d81af6f3425b9b31 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 3 Oct 2022 14:20:38 +0400 Subject: [PATCH 035/113] Updated on 2026-08-14 --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index 0b11f515b1..b23c84e56f 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -1,7 +1,7 @@ ext.versions = [ kotlin : '1.6.10', build_gradle : '7.1.3', - tangem_card_sdk : 'develop-159', + tangem_card_sdk : 'develop-163', tangem_blockchain_sdk: 'develop-121', // tangem_blockchain_sdk: '0.0.1', ] From 222398b9bb7e09491df00f92526822709fa7a7a7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 6 Oct 2022 11:18:02 +0400 Subject: [PATCH 036/113] Updated on 2026-08-14 --- .../domain/tokens/UserTokensNetworkService.kt | 7 ++--- .../tap/domain/tokens/UserTokensRepository.kt | 27 ++++++++++++++----- .../domain/tokens/UserTokensStorageService.kt | 6 ++--- .../network/api/tangemTech/Responses.kt | 4 +-- 4 files changed, 27 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensNetworkService.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensNetworkService.kt index ad188c6913..c0b7e18155 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensNetworkService.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensNetworkService.kt @@ -5,7 +5,6 @@ import com.tangem.common.services.Result import com.tangem.network.api.tangemTech.TangemTechService import com.tangem.network.api.tangemTech.UserTokensResponse import com.tangem.tap.domain.NoDataError -import com.tangem.tap.features.wallet.models.Currency class UserTokensNetworkService(private val tangemTechService: TangemTechService) { suspend fun getUserTokens(userId: String): Result { @@ -22,9 +21,7 @@ class UserTokensNetworkService(private val tangemTechService: TangemTechService) } } - suspend fun saveUserTokens(userId: String, tokens: List): Result { - val tokensResponse = tokens.map { it.toTokenResponse() } - val data = UserTokensResponse(tokens = tokensResponse) - return tangemTechService.putUserTokens(userId, data) + suspend fun saveUserTokens(userId: String, tokens: UserTokensResponse): Result { + return tangemTechService.putUserTokens(userId, tokens) } } diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt index e418215ed7..fd1583d9ef 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt @@ -8,6 +8,7 @@ import com.tangem.common.extensions.toHexString import com.tangem.common.services.Result import com.tangem.domain.common.extensions.calculateHmacSha256 import com.tangem.network.api.tangemTech.TangemTechService +import com.tangem.network.api.tangemTech.UserTokensResponse import com.tangem.tap.common.AndroidFileReader import com.tangem.tap.domain.NoDataError import com.tangem.tap.domain.tokens.models.BlockchainNetwork @@ -36,7 +37,7 @@ class UserTokensRepository( return when (val networkResult = networkService.getUserTokens(userId)) { is Result.Success -> { val tokens = networkResult.data.tokens.map { Currency.fromTokenResponse(it) } - storageService.saveUserTokens(card.getUserId(), tokens) + storageService.saveUserTokens(card.getUserId(), tokens.toUserTokensResponse()) tokens } is Result.Failure -> { @@ -46,13 +47,24 @@ class UserTokensRepository( } suspend fun saveUserTokens(card: Card, tokens: List) { - networkService.saveUserTokens(card.getUserId(), tokens) - storageService.saveUserTokens(card.getUserId(), tokens) + val userTokens = tokens.toUserTokensResponse() + networkService.saveUserTokens(card.getUserId(), userTokens) + storageService.saveUserTokens(card.getUserId(), userTokens) } suspend fun removeUserTokens(card: Card) { - networkService.saveUserTokens(card.getUserId(), emptyList()) - storageService.saveUserTokens(card.getUserId(), emptyList()) + val userTokens = emptyList().toUserTokensResponse() + networkService.saveUserTokens(card.getUserId(), userTokens) + storageService.saveUserTokens(card.getUserId(), userTokens) + } + + private fun List.toUserTokensResponse(): UserTokensResponse { + val tokensResponse = this.map { it.toTokenResponse() } + return UserTokensResponse( + tokens = tokensResponse, + group = GROUP_DEFAULT_VALUE, + sort = SORT_DEFAULT_VALUE, + ) } fun loadBlockchainsToDerive(card: Card): List { @@ -77,7 +89,8 @@ class UserTokensRepository( return when (error) { is NoDataError -> { val tokens = storageService.getUserTokens(card) - coroutineScope { launch { networkService.saveUserTokens(userId = userId, tokens = tokens) } } + val userTokens = tokens.toUserTokensResponse() + coroutineScope { launch { networkService.saveUserTokens(userId = userId, tokens = userTokens) } } tokens } else -> { @@ -104,6 +117,8 @@ class UserTokensRepository( companion object { const val MESSAGE = "UserWalletID" + const val SORT_DEFAULT_VALUE = "manual" + const val GROUP_DEFAULT_VALUE = "none" fun init(context: Context, tangemTechService: TangemTechService): UserTokensRepository { val fileReader = AndroidFileReader(context) val oldUserTokensRepository = OldUserTokensRepository( diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt index f2011cbebb..869c49034f 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt @@ -34,10 +34,8 @@ class UserTokensStorageService( return blockchainNetworks.flatMap { it.toCurrencies() } } - fun saveUserTokens(userId: String, tokens: List) { - val tokensResponse = tokens.map { it.toTokenResponse() } - val data = UserTokensResponse(tokens = tokensResponse) - val json = userTokensAdapter.toJson(data) + fun saveUserTokens(userId: String, tokens: UserTokensResponse) { + val json = userTokensAdapter.toJson(tokens) fileReader.rewriteFile(json, getFileNameForUserTokens(userId)) } diff --git a/network/src/main/java/com/tangem/network/api/tangemTech/Responses.kt b/network/src/main/java/com/tangem/network/api/tangemTech/Responses.kt index 4afd9ed2ef..4d9784286e 100644 --- a/network/src/main/java/com/tangem/network/api/tangemTech/Responses.kt +++ b/network/src/main/java/com/tangem/network/api/tangemTech/Responses.kt @@ -51,8 +51,8 @@ data class GeoResponse( data class UserTokensResponse( val version: Int = 0, - val group: String = "", - val sort: String = "", + val group: String? = null, + val sort: String? = null, val tokens: List = emptyList(), ) : TangemTechResponse From 960b1306b7299e78147a455ac0c0f1e84fa2a2ab Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 6 Oct 2022 11:19:11 +0400 Subject: [PATCH 037/113] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/features/wallet/models/Currency.kt | 2 +- .../main/java/com/tangem/network/api/tangemTech/Responses.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt b/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt index 1500a10a1e..90f905991f 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt @@ -59,7 +59,7 @@ sealed interface Currency { fun isToken(): Boolean = this is Token fun toTokenResponse(): TokenResponse { return TokenResponse( - id = coinId ?: "", + id = coinId, networkId = blockchain.toNetworkId(), derivationPath = derivationPath ?: DERIVATION_PATH_RAW_VALUE, name = currencyName, diff --git a/network/src/main/java/com/tangem/network/api/tangemTech/Responses.kt b/network/src/main/java/com/tangem/network/api/tangemTech/Responses.kt index 4d9784286e..291062389d 100644 --- a/network/src/main/java/com/tangem/network/api/tangemTech/Responses.kt +++ b/network/src/main/java/com/tangem/network/api/tangemTech/Responses.kt @@ -57,7 +57,7 @@ data class UserTokensResponse( ) : TangemTechResponse data class TokenResponse( - val id: String, + val id: String? = null, val networkId: String, val derivationPath: String, val name: String, From 8e552d9ed39b0130db2d2d202005983c4a162e62 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 6 Oct 2022 15:12:52 +0500 Subject: [PATCH 038/113] Updated on 2026-08-14 --- .../com/tangem/domain/common/LogConfig.kt | 1 + .../api/paymentology/PaymentologyApi.kt | 29 ++++++++ .../paymentology/PaymentologyApiService.kt | 44 ++++++++++++ .../network/api/paymentology/Requests.kt | 59 +++++++++++++++ .../network/api/paymentology/Responses.kt | 72 +++++++++++++++++++ 5 files changed, 205 insertions(+) create mode 100644 network/src/main/java/com/tangem/network/api/paymentology/PaymentologyApi.kt create mode 100644 network/src/main/java/com/tangem/network/api/paymentology/PaymentologyApiService.kt create mode 100644 network/src/main/java/com/tangem/network/api/paymentology/Requests.kt create mode 100644 network/src/main/java/com/tangem/network/api/paymentology/Responses.kt diff --git a/domain/src/main/java/com/tangem/domain/common/LogConfig.kt b/domain/src/main/java/com/tangem/domain/common/LogConfig.kt index 56c2950141..ff11351635 100644 --- a/domain/src/main/java/com/tangem/domain/common/LogConfig.kt +++ b/domain/src/main/java/com/tangem/domain/common/LogConfig.kt @@ -13,5 +13,6 @@ object NetworkLogConfig { val mercuryoService: Boolean = false val moonPayService: Boolean = false val tangemTechService: Boolean = BuildConfig.DEBUG + val paymentologyApiService: Boolean = BuildConfig.DEBUG val blockchainSdkNetwork: Boolean = BuildConfig.DEBUG } \ No newline at end of file diff --git a/network/src/main/java/com/tangem/network/api/paymentology/PaymentologyApi.kt b/network/src/main/java/com/tangem/network/api/paymentology/PaymentologyApi.kt new file mode 100644 index 0000000000..cbefd39af8 --- /dev/null +++ b/network/src/main/java/com/tangem/network/api/paymentology/PaymentologyApi.kt @@ -0,0 +1,29 @@ +package com.tangem.network.api.paymentology + +import retrofit2.http.Body +import retrofit2.http.POST + +/** +[REDACTED_AUTHOR] + */ +interface PaymentologyApi { + + @POST("card/verify") + fun checkRegistration( + @Body request: CheckRegistrationRequests, + ): RegistrationResponse.Item + + @POST("card/get_challenge") + fun requestAttestationChallenge( + @Body request: CheckRegistrationRequests.Item, + ): AttestationResponse + + @POST("card/set_pin") + fun registerWallet( + @Body request: RegisterWalletRequest, + ): RegisterWalletResponse + + companion object { + val baseUrl: String = "https://paymentologygate.oa.r.appspot.com/" + } +} \ No newline at end of file diff --git a/network/src/main/java/com/tangem/network/api/paymentology/PaymentologyApiService.kt b/network/src/main/java/com/tangem/network/api/paymentology/PaymentologyApiService.kt new file mode 100644 index 0000000000..0fce5b41ba --- /dev/null +++ b/network/src/main/java/com/tangem/network/api/paymentology/PaymentologyApiService.kt @@ -0,0 +1,44 @@ +package com.tangem.network.api.paymentology + +import com.tangem.common.extensions.toHexString +import com.tangem.common.services.Result +import com.tangem.common.services.performRequest +import com.tangem.network.common.createRetrofitInstance +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +/** +[REDACTED_AUTHOR] + */ +class PaymentologyApiService( + private val logEnabled: Boolean, +) { + private val api = createRetrofitInstance( + baseUrl = PaymentologyApi.baseUrl, + logEnabled = logEnabled, + ).create(PaymentologyApi::class.java) + + suspend fun checkRegistration( + cardId: String, + publicKey: ByteArray, + ): Result = withContext(Dispatchers.IO) { + // later convert response to SaltPayRegistrator.State + val requestItem = CheckRegistrationRequests.Item(cardId, publicKey.toHexString()) + val requests = listOf(requestItem) + performRequest { api.checkRegistration(CheckRegistrationRequests(requests)) } + } + + suspend fun requestAttestationChallenge( + cardId: String, + publicKey: ByteArray, + ): Result = withContext(Dispatchers.IO) { + val requestItem = CheckRegistrationRequests.Item(cardId, publicKey.toHexString()) + performRequest { api.requestAttestationChallenge(requestItem) } + } + + suspend fun registerWallet( + request: RegisterWalletRequest, + ): Result = withContext(Dispatchers.IO) { + performRequest { api.registerWallet(request) } + } +} \ No newline at end of file diff --git a/network/src/main/java/com/tangem/network/api/paymentology/Requests.kt b/network/src/main/java/com/tangem/network/api/paymentology/Requests.kt new file mode 100644 index 0000000000..1cea8aada4 --- /dev/null +++ b/network/src/main/java/com/tangem/network/api/paymentology/Requests.kt @@ -0,0 +1,59 @@ +package com.tangem.network.api.paymentology + +import com.squareup.moshi.Json +import com.tangem.common.extensions.calculateHashCode + +/** +[REDACTED_AUTHOR] + */ +data class CheckRegistrationRequests( + val requests: List, +) { + data class Item( + @Json(name = "CID") + var cardId: String = "", + var publicKey: String = "", + ) +} + +data class RegisterWalletRequest( + @Json(name = "CID") + val cardId: String, + val publicKey: ByteArray, + val walletPublicKey: ByteArray, + val walletSalt: ByteArray, + val walletSignature: ByteArray, + val cardSalt: ByteArray, + val cardSignature: ByteArray, + @Json(name = "PIN") + val pin: String, +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as RegisterWalletRequest + + if (cardId != other.cardId) return false + if (publicKey.contentEquals(other.publicKey)) return false + if (walletPublicKey.contentEquals(other.walletPublicKey)) return false + if (walletSalt.contentEquals(other.walletSalt)) return false + if (walletSignature.contentEquals(other.walletSignature)) return false + if (cardSalt.contentEquals(other.cardSalt)) return false + if (cardSignature.contentEquals(other.cardSignature)) return false + if (pin != other.pin) return false + + return true + } + + override fun hashCode(): Int = calculateHashCode( + cardId.hashCode(), + publicKey.contentHashCode(), + walletPublicKey.contentHashCode(), + walletSalt.contentHashCode(), + walletSignature.contentHashCode(), + cardSalt.contentHashCode(), + cardSignature.contentHashCode(), + pin.hashCode(), + ) +} \ No newline at end of file diff --git a/network/src/main/java/com/tangem/network/api/paymentology/Responses.kt b/network/src/main/java/com/tangem/network/api/paymentology/Responses.kt new file mode 100644 index 0000000000..7edd003b23 --- /dev/null +++ b/network/src/main/java/com/tangem/network/api/paymentology/Responses.kt @@ -0,0 +1,72 @@ +package com.tangem.network.api.paymentology + +import com.squareup.moshi.Json +import com.tangem.common.extensions.calculateHashCode + +/** +[REDACTED_AUTHOR] + */ +interface ErrorContainer { + val error: String? +} + +data class RegistrationResponse( + val results: List, + val success: Boolean, + override val error: String?, + val errorCode: String?, +) : ErrorContainer { + + data class Item( + @Json(name = "CID") + val cardId: String, + val passed: Boolean?, + val active: Boolean?, + @Json(name = "pin_set") + val pinSet: Boolean?, + @Json(name = "blockchain_init") + val blockchainInit: Boolean?, + @Json(name = "kyc_passed") + val kycPassed: Boolean?, + @Json(name = "kyc_waiting") + val kycWaiting: Boolean?, + @Json(name = "disabled_by_admin") + val disabledByAdmin: Boolean?, + override val error: String?, + ) : ErrorContainer +} + +data class AttestationResponse( + val challenge: ByteArray, + val success: Boolean, + override val error: String?, + val errorCode: String?, +) : ErrorContainer { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as AttestationResponse + + if (challenge.contentEquals(other.challenge)) return false + if (success != other.success) return false + if (error != other.error) return false + if (errorCode != other.errorCode) return false + + return true + } + + override fun hashCode(): Int = calculateHashCode( + challenge.contentHashCode(), + success.hashCode(), + error.hashCode(), + errorCode.hashCode(), + ) +} + +data class RegisterWalletResponse( + val success: Boolean, + override val error: String?, + val errorCode: String?, +) : ErrorContainer \ No newline at end of file From 77d6364cbe2cc35cd57c71ccecd37babfa76b200 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 6 Oct 2022 16:20:24 +0500 Subject: [PATCH 039/113] Updated on 2026-08-14 --- .../tangem/domain/common/card/CardIdRange.kt | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 domain/src/main/java/com/tangem/domain/common/card/CardIdRange.kt diff --git a/domain/src/main/java/com/tangem/domain/common/card/CardIdRange.kt b/domain/src/main/java/com/tangem/domain/common/card/CardIdRange.kt new file mode 100644 index 0000000000..696050068f --- /dev/null +++ b/domain/src/main/java/com/tangem/domain/common/card/CardIdRange.kt @@ -0,0 +1,36 @@ +package com.tangem.domain.common.card + +import timber.log.Timber + +/** +[REDACTED_AUTHOR] + */ +class CardIdRange( + val cardIdStart: String, + val cardIdEnd: String, +) { + val range: LongRange + + init { + checkCardId(cardIdStart) + checkCardId(cardIdEnd) + range = toLong(cardIdStart)..toLong(cardIdEnd) + } + + fun contains(cardId: String): Boolean = try { + checkCardId(cardId) + range.contains(toLong(cardId)) + } catch (ex: Exception) { + Timber.e(ex, "CardIdRange: check for the cardId: [$cardId] in range failed.") + false + } + + private fun stripBatchPrefix(cardId: String): String = cardId.drop(4) + + @Throws(NumberFormatException::class) + private fun toLong(cardId: String): Long = stripBatchPrefix(cardId).toLong() + + private fun checkCardId(cardId: String) { + if (cardId.length != 16) throw IllegalArgumentException("CardId must be 16 characters long.") + } +} \ No newline at end of file From 6a416e3825ea550ec43b51d01012cbc69a1e694a Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 10 Oct 2022 11:38:12 +0400 Subject: [PATCH 040/113] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/features/wallet/models/Currency.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt b/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt index 90f905991f..102c872457 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt @@ -70,7 +70,7 @@ sealed interface Currency { } companion object { - private const val DERIVATION_PATH_RAW_VALUE = "m/44/0'/0/0" + private const val DERIVATION_PATH_RAW_VALUE = "m/44'/60'/0'/0/0" fun fromBlockchainNetwork( blockchainNetwork: BlockchainNetwork, token: com.tangem.blockchain.common.Token? = null, From 69a75094c12adf800bee30a7604db9f0522de53c Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 10 Oct 2022 15:45:07 +0400 Subject: [PATCH 041/113] Updated on 2026-08-14 --- .../tangem/tap/features/tokens/redux/TokensMiddleware.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt index 96f742ec97..9817b4db49 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt @@ -278,7 +278,7 @@ class TokensMiddleware { val factory = store.state.globalState.tapWalletManager.walletManagerFactory val derivationStyle = scanResponse.card.derivationStyle - val addActions = currencyList.mapNotNull { currency -> + val addActions = currencyList.mapIndexedNotNull { index, currency -> when (currency) { is Currency.Blockchain -> { val derivationPath = currency.derivationPath?.let { DerivationPath(it) } @@ -293,12 +293,12 @@ class TokensMiddleware { scanResponse = scanResponse, blockchain = currency.blockchain, derivationParams = derivationParams, - ) ?: return@mapNotNull null + ) ?: return@mapIndexedNotNull null val blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager) WalletAction.MultiWallet.AddBlockchain( blockchain = blockchainNetwork, walletManager = walletManager, - save = true, + save = index == currencyList.lastIndex, ) } is Currency.Token -> { @@ -309,7 +309,7 @@ class TokensMiddleware { WalletAction.MultiWallet.AddToken( token = currency.token, blockchain = blockchainNetwork, - save = true, + save = index == currencyList.lastIndex, ) } } From 9904a6cfcb3b2af9dd3fc4de7f4dc0f923eb42c5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 10 Oct 2022 20:58:22 +0400 Subject: [PATCH 042/113] Updated on 2026-08-14 --- dependencies.gradle | 2 +- .../main/java/com/tangem/domain/common/extensions/Blockchain.kt | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index b23c84e56f..c50297605c 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -2,7 +2,7 @@ ext.versions = [ kotlin : '1.6.10', build_gradle : '7.1.3', tangem_card_sdk : 'develop-163', - tangem_blockchain_sdk: 'develop-121', + tangem_blockchain_sdk: 'develop-127', // tangem_blockchain_sdk: '0.0.1', ] diff --git a/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt b/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt index 0668389fcd..a6c96bb9da 100644 --- a/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt +++ b/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt @@ -98,6 +98,7 @@ fun Blockchain.toNetworkId(): String { Blockchain.Optimism -> "optimistic-ethereum" Blockchain.OptimismTestnet -> "optimistic-ethereum/test" Blockchain.Dash -> "dash" + Blockchain.SaltPay -> "wxdai" } } @@ -129,6 +130,7 @@ fun Blockchain.toCoinId(): String { Blockchain.Kusama -> "kusama" Blockchain.Optimism, Blockchain.OptimismTestnet -> "ethereum" Blockchain.Dash -> "dash" + Blockchain.SaltPay -> "wxdai" Blockchain.Unknown -> "unknown" } } \ No newline at end of file From eb73cafbd004b79ac00df27157f540ce3282329e Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 11 Oct 2022 21:25:37 +0400 Subject: [PATCH 043/113] Updated on 2026-08-14 --- app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index 2ea48fb328..1b10998845 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -132,7 +132,6 @@ class TapWalletManager { } dispatchOnMain(WalletAction.LoadWallet()) - dispatchOnMain(WalletAction.LoadFiatRate()) } private suspend fun loadMultiWalletData( @@ -202,6 +201,7 @@ class TapWalletManager { ) } checkIfDerivationsAreMissing(blockchainNetworks, scanResponse) + store.dispatch(WalletAction.LoadFiatRate(coinsList = userTokens)) } } @@ -218,7 +218,6 @@ class TapWalletManager { } store.dispatch(WalletAction.LoadWallet()) - store.dispatch(WalletAction.LoadFiatRate()) } } From 7efc129909e8928408c1187226d3e7501b88f232 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 12 Oct 2022 11:46:55 +0400 Subject: [PATCH 044/113] Updated on 2026-08-14 --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index c50297605c..1a2357315a 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -2,7 +2,7 @@ ext.versions = [ kotlin : '1.6.10', build_gradle : '7.1.3', tangem_card_sdk : 'develop-163', - tangem_blockchain_sdk: 'develop-127', + tangem_blockchain_sdk: 'develop-128', // tangem_blockchain_sdk: '0.0.1', ] From ed47aefb33aa6316617ca4db546128200d4f5dc0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 12 Oct 2022 12:17:19 +0400 Subject: [PATCH 045/113] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- .../tangem/tap/features/wallet/models/Currency.kt | 12 +++--------- .../com/tangem/network/api/tangemTech/Responses.kt | 2 +- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index f33f020823..8a36217ba8 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit f33f020823031f1c532cf5a9d5cf3fbecadcf050 +Subproject commit 8a36217ba816e1cc7cbc7290ffb948282ec15f6b diff --git a/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt b/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt index 102c872457..7597156ab9 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt @@ -61,7 +61,7 @@ sealed interface Currency { return TokenResponse( id = coinId, networkId = blockchain.toNetworkId(), - derivationPath = derivationPath ?: DERIVATION_PATH_RAW_VALUE, + derivationPath = derivationPath, name = currencyName, symbol = currencySymbol, decimals = decimals, @@ -70,7 +70,6 @@ sealed interface Currency { } companion object { - private const val DERIVATION_PATH_RAW_VALUE = "m/44'/60'/0'/0/0" fun fromBlockchainNetwork( blockchainNetwork: BlockchainNetwork, token: com.tangem.blockchain.common.Token? = null, @@ -112,11 +111,6 @@ sealed interface Currency { } fun fromTokenResponse(tokenResponse: TokenResponse): Currency { - val derivationPath = if (tokenResponse.derivationPath == DERIVATION_PATH_RAW_VALUE) { - null - } else { - tokenResponse.derivationPath - } return when { tokenResponse.contractAddress != null -> Token( com.tangem.blockchain.common.Token( @@ -127,11 +121,11 @@ sealed interface Currency { id = tokenResponse.id, ), blockchain = com.tangem.blockchain.common.Blockchain.fromNetworkId(tokenResponse.networkId)!!, - derivationPath = derivationPath, + derivationPath = tokenResponse.derivationPath, ) else -> Blockchain( blockchain = com.tangem.blockchain.common.Blockchain.fromNetworkId(tokenResponse.networkId)!!, - derivationPath = derivationPath, + derivationPath = tokenResponse.derivationPath, ) } } diff --git a/network/src/main/java/com/tangem/network/api/tangemTech/Responses.kt b/network/src/main/java/com/tangem/network/api/tangemTech/Responses.kt index 291062389d..4f88fca3b7 100644 --- a/network/src/main/java/com/tangem/network/api/tangemTech/Responses.kt +++ b/network/src/main/java/com/tangem/network/api/tangemTech/Responses.kt @@ -59,7 +59,7 @@ data class UserTokensResponse( data class TokenResponse( val id: String? = null, val networkId: String, - val derivationPath: String, + val derivationPath: String? = null, val name: String, val symbol: String, val decimals: Int, From c0cc99984c34add49826a155a5a500b110bba625 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 12 Oct 2022 13:49:48 +0400 Subject: [PATCH 046/113] Updated on 2026-08-14 --- .../java/com/tangem/domain/common/extensions/Blockchain.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt b/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt index a6c96bb9da..dad3b53860 100644 --- a/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt +++ b/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt @@ -47,6 +47,7 @@ fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? { "optimistic-ethereum" -> Blockchain.Optimism "optimistic-ethereum/test" -> Blockchain.OptimismTestnet "dash" -> Blockchain.Dash + "wxdai" -> Blockchain.SaltPay else -> null } } @@ -55,7 +56,7 @@ fun Blockchain.toNetworkId(): String { return when (this) { Blockchain.Unknown -> "unknown" Blockchain.Arbitrum -> "arbitrum-one" - Blockchain.ArbitrumTestnet -> "arbitrum/test" + Blockchain.ArbitrumTestnet -> "arbitrum-one/test" Blockchain.Avalanche -> "avalanche" Blockchain.AvalancheTestnet -> "avalanche/test" Blockchain.Binance -> "binancecoin" From 84e54966897a9541e328631c356d7e02b266c82f Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 17 Oct 2022 15:40:11 +0500 Subject: [PATCH 047/113] Updated on 2026-08-14 --- app/src/main/res/values-de/strings_final.xml | 46 ++++++++++++++++++ app/src/main/res/values-fr/strings_final.xml | 48 ++++++++++++++++++- app/src/main/res/values-it/strings_final.xml | 48 ++++++++++++++++++- app/src/main/res/values-ru/strings.xml | 2 +- app/src/main/res/values-ru/strings_final.xml | 33 +++++++++++++ app/src/main/res/values/strings_final.xml | 33 +++++++++++++ .../main/res/values/strings_untranslated.xml | 2 +- app/src/main/res/values/styles.xml | 15 ++++++ 8 files changed, 223 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values-de/strings_final.xml b/app/src/main/res/values-de/strings_final.xml index 7a4a70126b..6c801e756c 100644 --- a/app/src/main/res/values-de/strings_final.xml +++ b/app/src/main/res/values-de/strings_final.xml @@ -48,6 +48,10 @@ Notice Attention The server is not available, please try again later + Total balance + The amount does not include some of your funds + Manage tokens + No rate Hide token Hide %s Hide @@ -57,6 +61,15 @@ %s network not found. Please, add it first and try again. This network is not supported. Please select another network. %s network + Your wallet has not been backed up + To protect your assets, we advise you to carry out this procedure + Remove token + Russian bank cards are not accepted at the moment + Do you have a bank card of another country or a UnionPay card? + Yes + No + Chat + Tangem Bot Card Settings Security Mode Selected application protection method @@ -95,4 +108,37 @@ Invalid Tag. It won\'t be added to the transaction. Connection with this Dapp cannot be established due to its technical implementation. To start working with the card scan the first card and make a backup + Attention + Tap the card with the visa logo + No funds for activation + Please contact support + Four identical digits isn\'t safe + Such a PIN can be brute-forced easily + Pin code + Connect + KYC + Verify your identity + In progress + Set PIN code + Register + Verify + Refresh + Connect your card + Verify your identity + KYC is in progress + Connect your card to the decentralized payment system + To start using your card you have to pass the KYC process + Please wait until the verification is completed. Usually it takes up to 1 hour. You can close the app and come back later. + PIN Code + Set PIN code for your SaltPay card + Chat with support + This card is not designed to SaltPay backup + Please hold the card until the operation complete + To start the backup process you have to add Tangem card as your backup + No backup card + Backup card ready + Finalize the backup process by creating an access code + Prepare the SaltPay card + Tap the SaltPay card + Tap the Tangem card \ 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 5e1c532e79..6c801e756c 100644 --- a/app/src/main/res/values-fr/strings_final.xml +++ b/app/src/main/res/values-fr/strings_final.xml @@ -45,9 +45,13 @@ Contract address copied! If you chose the wrong network during your crypto transfer from the exchange, this guide will help you recover your funds Custom - The server is not available, please try again later Notice Attention + The server is not available, please try again later + Total balance + The amount does not include some of your funds + Manage tokens + No rate Hide token Hide %s Hide @@ -57,6 +61,15 @@ %s network not found. Please, add it first and try again. This network is not supported. Please select another network. %s network + Your wallet has not been backed up + To protect your assets, we advise you to carry out this procedure + Remove token + Russian bank cards are not accepted at the moment + Do you have a bank card of another country or a UnionPay card? + Yes + No + Chat + Tangem Bot Card Settings Security Mode Selected application protection method @@ -95,4 +108,37 @@ Invalid Tag. It won\'t be added to the transaction. Connection with this Dapp cannot be established due to its technical implementation. To start working with the card scan the first card and make a backup + Attention + Tap the card with the visa logo + No funds for activation + Please contact support + Four identical digits isn\'t safe + Such a PIN can be brute-forced easily + Pin code + Connect + KYC + Verify your identity + In progress + Set PIN code + Register + Verify + Refresh + Connect your card + Verify your identity + KYC is in progress + Connect your card to the decentralized payment system + To start using your card you have to pass the KYC process + Please wait until the verification is completed. Usually it takes up to 1 hour. You can close the app and come back later. + PIN Code + Set PIN code for your SaltPay card + Chat with support + This card is not designed to SaltPay backup + Please hold the card until the operation complete + To start the backup process you have to add Tangem card as your backup + No backup card + Backup card ready + Finalize the backup process by creating an access code + Prepare the SaltPay card + Tap the SaltPay card + Tap the Tangem card \ 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 5e1c532e79..6c801e756c 100644 --- a/app/src/main/res/values-it/strings_final.xml +++ b/app/src/main/res/values-it/strings_final.xml @@ -45,9 +45,13 @@ Contract address copied! If you chose the wrong network during your crypto transfer from the exchange, this guide will help you recover your funds Custom - The server is not available, please try again later Notice Attention + The server is not available, please try again later + Total balance + The amount does not include some of your funds + Manage tokens + No rate Hide token Hide %s Hide @@ -57,6 +61,15 @@ %s network not found. Please, add it first and try again. This network is not supported. Please select another network. %s network + Your wallet has not been backed up + To protect your assets, we advise you to carry out this procedure + Remove token + Russian bank cards are not accepted at the moment + Do you have a bank card of another country or a UnionPay card? + Yes + No + Chat + Tangem Bot Card Settings Security Mode Selected application protection method @@ -95,4 +108,37 @@ Invalid Tag. It won\'t be added to the transaction. Connection with this Dapp cannot be established due to its technical implementation. To start working with the card scan the first card and make a backup + Attention + Tap the card with the visa logo + No funds for activation + Please contact support + Four identical digits isn\'t safe + Such a PIN can be brute-forced easily + Pin code + Connect + KYC + Verify your identity + In progress + Set PIN code + Register + Verify + Refresh + Connect your card + Verify your identity + KYC is in progress + Connect your card to the decentralized payment system + To start using your card you have to pass the KYC process + Please wait until the verification is completed. Usually it takes up to 1 hour. You can close the app and come back later. + PIN Code + Set PIN code for your SaltPay card + Chat with support + This card is not designed to SaltPay backup + Please hold the card until the operation complete + To start the backup process you have to add Tangem card as your backup + No backup card + Backup card ready + Finalize the backup process by creating an access code + Prepare the SaltPay card + Tap the SaltPay card + Tap the Tangem card \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 38ee22a3ca..4b2986eae1 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -295,7 +295,7 @@ Все резервные карты могут использоваться как полнофункциональные с одинаковыми ключами Вы сможете установить код доступа для защиты своих кошельков Код доступа можно восстановить с помощью одной из резервных карт - Ваша карта Tangem Wallet настроена и готова к использованию + Ваша карта настроена и готова к использованию. Вы создали 1 резервную карту, и теперь эта карта готова к использованию Основная карта Я понимаю diff --git a/app/src/main/res/values-ru/strings_final.xml b/app/src/main/res/values-ru/strings_final.xml index 0f8e928ad5..951c4bc737 100644 --- a/app/src/main/res/values-ru/strings_final.xml +++ b/app/src/main/res/values-ru/strings_final.xml @@ -106,4 +106,37 @@ Не удалось установить сессию WalletConnect Cоединение с этим Dapp сервисом не может быть установлено из-за его технической реализации. Для начала работы с картой сначала отсканируйте первую карту и сделайте бэкап + Внимание + Приложите карту с логотипом Visa + Недостаточно средств для активации + Пожалуйста обратитесь в службу поддержки + Ввод одинаковых цифр является не безопасным + Данный Код доступа может быть легко взломан + Код доступа + Подключиться + Верификация клиента + Подтвердите свою личность + В обработке + Установить Код доступа + Зарегистрироваться + Подтвердить + Обновить + Подключите свою карту + Подтвердите свою личность + Подтверждение личности в процессе + Подключите вашу карту к децентрализованной платежной системе + Для начала работы с картой вам необходимо завершить процесс подтверждения личности + Пожалуйста дождитесь завершения процесса подтверждения вашей личности. Как правило, это занимает меньше часа. Вы можете закрыть приложение и вернуться позже. + Код доступа + Установите Код доступа для вашей SaltPay карты + Чат поддержки + Данная карта не подходит для бэкапа SaltPay + Пожалуйста, удерживайте карту до завершения операции + Для начала процесса бэкапа вам необходимо добавить Tangem карту + Бэкап карт не добавлена + Бэкап карта создана + Завершите процесс бэкапа создав код доступа + Приготовьте SaltPay карту + Приложите SaltPay карту + Приложите Tangem карту diff --git a/app/src/main/res/values/strings_final.xml b/app/src/main/res/values/strings_final.xml index 306301c3fa..5a47c7ed20 100644 --- a/app/src/main/res/values/strings_final.xml +++ b/app/src/main/res/values/strings_final.xml @@ -108,4 +108,37 @@ Invalid Tag. It won\'t be added to the transaction. Connection with this Dapp cannot be established due to its technical implementation. To start working with the card scan the first card and make a backup + Attention + Tap the card with the visa logo + No funds for activation + Please contact support + Four identical digits isn\'t safe + Such a PIN can be brute-forced easily + Pin code + Connect + KYC + Verify your identity + In progress + Set PIN code + Register + Verify + Refresh + Connect your card + Verify your identity + KYC is in progress + Connect your card to the decentralized payment system + To start using your card you have to pass the KYC process + Please wait until the verification is completed. Usually it takes up to 1 hour. You can close the app and come back later. + PIN Code + Set PIN code for your SaltPay card + Chat with support + This card is not designed to SaltPay backup + Please hold the card until the operation complete + To start the backup process you have to add Tangem card as your backup + No backup card + Backup card ready + Finalize the backup process by creating an access code + Prepare the SaltPay card + Tap the SaltPay card + Tap the Tangem card diff --git a/app/src/main/res/values/strings_untranslated.xml b/app/src/main/res/values/strings_untranslated.xml index 8d6c5017af..2c802a44c4 100644 --- a/app/src/main/res/values/strings_untranslated.xml +++ b/app/src/main/res/values/strings_untranslated.xml @@ -265,7 +265,7 @@ You will be able to set an access code to protect your wallets. Access code can be restored with one of backup cards. - Your Tangem Wallet card is configured and ready for use. + Your wallet card is configured and ready for use. You have created 1 backup card, and now this card are ready for use. Primary Card I understand diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 09cdff0b35..dad17e5962 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -70,6 +70,20 @@ 2dp + + + + + + + +

+ SaltPay Oly Card Terms of Service +

+


+

We are delighted + that you (“you”, + “your”, or the “Cardholder”) have chosen to use the SaltPay Oly Card (the “Card”). + The terms + and conditions set out here (“Terms”) govern your use of your Card as operated by + SaltPay.

+

+ When you use the + Card, you also agree to + the SaltPay + General Terms + which are incorporated + into these Terms by + reference, including any amendments made to those from time to time. If you do not + understand any provisions of + these Terms, please contact us before using the Card. For information about how we treat + your personal data, + please see our Privacy + Policy.

+


+
    +
  1. + Definitions

    +

    + Affiliate: means any entity controlling, controlled by or under common control with that party, where “control” means the ownership of more than 50% of (i) the voting securities or (ii) an interest in the assets, profits, or earnings of an entity. +

    +

    + Blockchain Asset: means a digital data stored in and protected by the blockchain network, including but not limited to ownership right and value of cryptocurrency, tokenized assets, non-fungible asset. +

    +

    + Cardholder: a person who holds a SaltPay Oly Card. +

    +

    Private + Key: a secret cryptographic key which provides full control over a Blockchain Asset. +

    +

    + Public + Key: means a cryptographic key, which provides access to information about a Blockchain Asset, including but not limited to Blockchain Address. +

    +

    + SaltPay: Salt Pay IIB hf. +

    +

    SaltPay + Oly Card / Card: physical card which stores Private Key and Public Key.

    +

    + Services: enabling the use of the SaltPay Oly Card at physical point of sale terminals that are enabled for the acceptance of Visa Debit cards. +

    +

    + Tangem: means Tangem AG, a company incorporated in Switzerland with company number 1315048, whose address is at Baarerstrasse 10, 6300 Zug. +

    +

    + Tangem + Mobile Application: Third party mobile application, providing interoperability between SaltPay Oly Card and blockchain, and working on NFC-capable smartphones and tablets using Google Android and Apple iOS operating systems. +

    +

    + Tangem + Service: means the service provided by Tangem, which provides and administers the Tangem Mobile Application, which enables Cardholders to generate and store Public Keys and Private Keys in a hardware crypto wallet. +

    +

    Utorg + Service: means providing services of exchanging a virtual currency against a fiat currency. +

    +


  2. +
  3. + Important Legal + Stuff

    +

    + We think + it’s important that we highlight and clearly explain how SaltPay safeguards its + interests, while offering + its services to its Cardholders (you!) which includes limitation of liability and + disclaimers. The sections + set out below reflect those safeguards and how they might impact you. It’s therefore + important that you read + them carefully. If you’re unsure about any of them, please get in touch.

    +
      +
    1. + These + Terms of Service (the “Terms”) govern the use of SaltPay Oly Cards to + authorised employees of + SaltPay and its Affiliates.

    2. +
    3. + The Cards may be used for storage of Private Key and Public Key to the Cardholder’s + Blockchain Assets + and authentication of Card Transactions with the purpose of transfer of Blockchain + Assets for goods and + services purchasing through the Visa payments infrastructure.

      +


    4. +
    5. The Cardholder + agrees that these Terms are binding.

    6. +
    7. The + Cardholder shall be + the only person having physical access to the Card.

    8. +
    9. + The Cardholder acknowledges and agrees that SaltPay does not provide backup or + recovery of Private Key + and Public Key stored by the Card. The Cardholder creates the backup of the SaltPay + Card at the point at + which they activate the Card through the Tangem Mobile Application by linking their + Card with Tangem + Wallet.

      +


    10. +
    11. + The conditions of + use of the Tangem Wallet and Mobile Application are defined in the terms of service + for the Tangem + Wallet and Mobile Application, which will also be available upon downloading the + application.

      +


    12. +
    +
  4. +
  5. + SaltPay’s + obligations

    +
      +
    1. + SaltPay agrees to + provide the Cardholder with the Service in accordance with these Terms and within + the agreed scope.

      +
    2. +
    3. + Under the condition + that the Cardholder exercises their obligations set out under Clause 4 of these + Terms, SaltPay shall use + its best endeavours to ensure that the Card will function properly and without + restriction until the + date on which the Card expires.

    4. +
    5. + SaltPay shall ensure + that the Card prevents duplication of the Private Key, and that the Cardholder has + exclusive control + over the Blockchain Asset unless the contrary is imposed by specific blockchain + network rules e.g., two + or more private keys can be used to control the same Blockchain Asset.

      +


    6. +
    7. SaltPay + agrees that it will not keep any records of Cardholder information, save for the + Cardholder’s full name, + including but not limited to the amount of Blockchain Asset stored on the Card, the + Private Key or + personalised history of Card usage.

      +


    8. +
    9. For the + avoidance of doubt, SaltPay does not guarantee that the operation of Card and/or + Tangem Mobile + Application will be secure, accurate, complete, uninterrupted, without error or free + of viruses, worms, + other harmful components or other program limitations. SaltPay may, at its sole + discretion and without + obligation to do so, correct, modify, amend, enhance, improve and make any other + changes to Card.

      +


    10. +
    +
  6. +
  7. The + Cardholder’s + obligations

    +
      +
    1. + By + accepting these Terms, you represent and warrant that (a) you have the authority to + execute and perform + the obligations contained here; (b) you (and your employees, directors, contractors + and agents) will + comply with all law applicable to your business and the use of the Card; and (c) you + will not use the + Card, directly or indirectly, for any fraudulent or illegal undertaking.

    2. +
    3. + Upon receiving the Card, the Cardholder, if they choose to use the Tangem Service, + should download the + Tangem Mobile Application in order to determine the amount of Blockchain Asset + stored on the Card.

      +


    4. +
    5. + When the Cardholder + pays for goods or services using their SaltPay Oly Card through contactless + confirmation using NFC, they + confirm that there is a sufficient balance in the Tangem Wallet and thereby agree + that amount is + deducted from the account.

    6. +
    7. + The Cardholder shall at all times keep the Card and the means of access (backup card + and pin codes) to + the Card separate from each other.

    8. +
    9. + The Cardholder shall at all times be aware of where the Card is and regularly ensure + that it is still in + their possession. The Cardholder shall avoid even temporary possession of the Card + by any other

      +

      + person. If the Card is lost, stolen or destroyed, control over the corresponding + Blockchain Asset + may be permanently lost.

    10. +
    11. + The Cardholder shall treat the Card with care and protect the Card from mechanical + damage, high + temperatures, strong electromagnetic fields, and other harmful factors.

    12. +
    13. + In the event that the + Card is stolen or + lost, and/or if a Cardholder becomes aware of an unauthorised or incorrectly + initiated transaction, + the Cardholder shall contact Tangem as soon as possible by sending an email + to support@tangem.com + or by using the chat function on the Official Mobile Application. +

      +


    14. +
    15. + In the event that the + Cardholder wishes + to deactivate their account, they shall contact Tangem by sending an email + to support@tangem.com + or by using the chat function on the Official Mobile Application. +

      +
    16. +
    17. + The Card can be used only with the official Tangem Mobile Application downloaded + from Google Play and + App Store.

    18. +
    19. + The Tangem Mobile Application shall be the exclusive source of information + concerning the Blockchain + Address of the Blockchain Asset and corresponding Public Key stored on the Card.

      +
    20. +
    21. + The Cardholder shall only use Near-Field Devices (“NFC Devices”) that are + capable of running the + Tangem Mobile Application.

    22. +
    23. + The Card shall be used only for physically tapping and holding near the Cardholder’s + NFC Device when the + Tangem Mobile Application requests it. For the avoidance of doubt, the Card cannot + be used to pay using + any method which involves gateways.

      +


    24. +
    +
  8. +
  9. The + Cardholder’s liabilities

    +
      +
    1. + The Cardholder is liable for all liabilities arising from the use and misuse of the + Card. In any case, + the Cardholder is liable for all transactions authorised using a means of + access.

    2. +
    3. + Any loss or damage resulting from the forwarding of the Card and/or means of access + shall be borne by + the Cardholder.

    4. +
    5. + Loss or damage incurred by the Cardholder in connection with the possession or use + of the Card shall be + borne solely by the Cardholder. SaltPay assumes no liability if the Card cannot be + used due to a + technical defect or because it has been cancelled or blocked.

      +


    6. +
    7. + Cardholder is only + permitted to use the Products for his personal, non-commercial use. Cardholder is + not permitted to + resell the Card.

      +


    8. +
    +
  10. +
  11. + Limitation of + liability

    +
      +
    1. + The Card is provided as-is, and except as expressly stated in these Terms, SaltPay + provides no express + or implied warranties or conditions, and SaltPay disclaims and excludes any implied + terms, + representations, warranties, and conditions with respect to the Card, including + warranties of + merchantability, fitness for a particular purpose, title, satisfactory quality and + non-infringement, as + well as any other implied warranties, such as warranties regarding data loss, + availability, accuracy, + functionality and lack of viruses. These disclaimers will apply except to the extent + applicable law does + not permit them. Any warranties, guarantees, or conditions that cannot be disclaimed + as a matter of law, + but which may be limited in duration, last for one year from the date on which you + receive the Card.

      +
    2. +
    3. + SaltPay shall not be liable and does not warrant or make any representations + regarding the services + provided by third parties in relation to the Tangem Service, including but not + limited to the Tangem + Mobile Application and the Utorg Service. The relationship between the Cardholder + and any third party in + relation to the Tangem Service shall be governed by separate agreements provided + below.

    4. +
    5. + In + no event shall SaltPay and/or any of its Affiliates be liable for any damages + whatsoever, including + direct, indirect, extraordinary, incidental or consequential damages of any kind + resulting from or + arising out of the use of Card and/or the Tangem Mobile Application or inability to + use Card and/or the + Tangem Mobile Application, failure of Card and/or the Tangem Mobile Application to + perform as + represented or expected, loss of goodwill or profits.

    6. +
    7. + SaltPay shall not be held liable for any failure to be able to use the Card and/or + the Tangem Mobile + Application for any reason whatsoever, nor will SaltPay be held liable for the loss + of the Blockchain + Asset resulting from a malfunction or inoperability of the blockchain network + hosting Blockchain Asset, + as well as the inaccessibility of its public servers and services.

    8. +
    9. + SaltPay shall not be held liable for the loss of profits, income, value or any + indirect, extraordinary, + consequential, exemplary or punitive damages.

    10. +
    11. + SaltPay shall not be + held liable for loss or breakdown of the Card and/or the Tangem Mobile + Application.

    12. +
    13. + SaltPay shall not be held liable for any loss of Blockchain Asset in the event of + loss or total + breakdown of the Card and/or the Tangem Mobile Application.

    14. +
    15. + The Cardholder hereby acknowledges and agrees that these limitations of liability + are agreed allocations + of risk constituting in part the consideration for using the Card and/or the Tangem + Mobile Application + and such limitations will apply notwithstanding the failure of essential purpose of + any limited remedy, + and even if SaltPay and/or any SaltPay Affiliates have been advised of the + possibility of such + liabilities and/or damages.

      +


    16. +
    +
  12. +
  13. Term + and + Termination

    +
      +
    1. + These Terms become effective upon the date (i) you accept these Terms online (or in + another manner + expressly approved by us) or (ii) you first access or use Tangem and shall terminate + on the date on + which the Card expires.

      +


    2. +
    3. + We may terminate + our arrangement with you under these Terms for any reason or no reason, at any time, + by giving 2 months’ + prior notice to this effect. We may also suspend your access to Tangem if you: (i) + have breached these + Terms or any other agreement you have with SaltPay, including SaltPay’s policies or + instructions; or + (ii) provided any false, incomplete, inaccurate, or misleading information or + otherwise engaged in + fraudulent or illegal conduct c) have misused the Service.

      +


    4. +
    5. + Where your + arrangement with us under these Terms terminates for any reason, you will, where + applicable, remain + liable for any fees, charges and other payment obligations you owe us.

      +


    6. +
    +
  14. +
  15. + General

    +
      +
    1. + Except as expressly provided herein, these Terms are a complete statement of the + agreement between you + and us regarding your use of Tangem. In the event of a conflict between these Terms + and the SaltPay + General Terms or any other SaltPay agreement or policy, these Terms shall + prevail.

      +


    2. +
    3. + These Terms do not + limit any rights that SaltPay may have under trade secret, copyright, patent, or + other laws. No waiver + of any term of these Terms shall be deemed a further or continuing waiver of such + term or any other + term.

    4. +
    5. + In + the event any court shall declare any section or sections of this Agreement invalid + or void, such + declaration shall not invalidate the entire Agreement and all other paragraphs of + the Agreement shall + remain in full force and effect.

    6. +
    7. + For + the avoidance of doubt, + provisions around general terms not covered in these Terms of Service, including + but not limited to + restrictions and unauthorised or illegal use, intellectual property, data, + taxes, indemnification, + warranties, severability, force majeure, and disputes, shall be governed by + the SaltPay General + Terms.

    8. +
    +
  16. +
+

TANGEM + WALLET AND TANGEM MOBILE APPLICATION

+

Terms of Service

+


+

PLEASE READ + THESE TERMS OF SERVICE + CAREFULLY. BY CLICKING TO ACCEPT, OR BY ACCESSING OR USING OUR SERVICES, YOU AGREE THAT YOU HAVE + READ, UNDERSTOOD, + AND ACCEPT ALL OF THE TERMS AND CONDITIONS CONTAINED HEREIN. BY PURCHASE OF THE SALTPAY OLY CARD + WITH THE TANGEM + WALLET OR BY USING THE SALTPAY OLY CARD OR BY USING TANGEM WALLET OR BY USING TANGEM MOBILE + APPLICATION, YOU + DEMONSTRATE YOUR AGREEMENT TO THESE TERMS AND CONDITIONS CONTAINED HEREIN.

+


+
    +
  1. + DEFINITIONS

    +


    +


    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    + “Cardholder

    + means a person who owns Tangem Wallet and SaltPay Oly Card.

    + “Blockchain Asset

    + means a digital data stored in and protected by the blockchain network, + including but not limited to + ownership right and value of cryptocurrency, tokenized assets, non-fungible + asset.

    + “Blockchain Address

    + means a unique identifier that serves as a virtual location of the Blockchain + Asset in the + blockchain.

    + “Card Transaction

    + means transfer of Blockchain Asset from the Blockchain Address associated with + the Public Key stored + on the SaltPay Oly Card for goods and services purchased through traditional + payments rail.

    + “Official Mobile Application

    + means an application developed and distributed by Tangem, providing + interoperability between SaltPay + Oly Card or Tangem Wallet and blockchain, and working on NFC-capable smartphones + and tablets using + Google Android and Apple iOS operation systems.

    + “Private Key

    + a secret cryptographic key which provides full control over a Blockchain + Asset.

    + “Tangem

    + means Tangem AG, Baarerstrasse 10, 6300 Zug.

    + “Public Key

    + means a cryptographic key, which provides access to information about a + Blockchain Asset, including + but not limited to Blockchain Address

    + “Services

    + means the purchase and/or use of the Tangem Wallet and the services or any other + features, + technologies or functionalities linked to the Card provided or operated by + Tangem AG via the website + or Official Mobile Applications.

    + “Tangem Wallet/Tangem Card / Card

    + means physical card which stores Private Key and Public Key and is used as a + backup card for SaltPay + Oly Card.

    +


  2. +
  3. GENERAL + PROVISIONS

    +


    +
      +
    1. + These Terms of Service (the “Terms”) govern the use of Tangem Wallet and/or + Official Mobile + Application provided by Tangem AG (referred to as "Tangem", + "we" or + "us" in this document) and the related services or any other + features, technologies or + functionalities linked to the Tangem Wallet. Tangem is a company

      +

      + incorporated under the laws of Switzerland, with a registered address at + Baarerstrasse 10, 6300 Zug, + Switzerland.

      +


    2. +
    3. + Tangem Wallet + represents a backup to the SaltPay Oly Card, which may be used only for storage of + Private Key and + Public Key to Cardholder’s Blockchain Assets. Tangem Wallet, unlike SaltPay Oly + Card, can’t be used for + goods and services purchasing through traditional payments rail. The use of SaltPay + Oly Card is governed + by a separate Terms of Service, provided above.

      +


    4. +
    5. + Official Mobile + Application is intended for usage only with SaltPay Oly and Tangem Wallet, providing + interoperability + between the cards and blockchain via NFC interface. Official Mobile Application DOES + NOT:

      +
        +
      1. + Generate, store, transmit, or have access to private (secret) cryptographic + keys to blockchain + wallets holding Blockchain assets.

      2. +
      3. + Generate, + store, transmit, or have access to secret keys, passwords, passphrases, + recovery phrases that + can be used to restore or to copy private (secret) keys to blockchain + wallets holding Blockchain + assets.

      4. +
      5. Provide + exchange, trading, investment services on behalf of Tangem.

        +


      6. +
      +
    6. +
    +
  4. +
  5. + RIGHTS AND + OBLIGATIONS

    +


    +
      +
    1. + Cardholder agrees that + these Terms are binding.

      +


    2. +
    3. + Cardholder shall be the + only person having physical access to the Tangem Wallet.

      +


    4. +
    5. + Cardholder acknowledges and agrees that Tangem does not provide backup or recovery + of Private Key and + Public Key stored by the Tangem Wallet.

      +


    6. +
    7. + Tangem does not + keep records of Cardholder’s personal data, the amount of Blockchain Asset stored on + the Card, the + Private Key, or personalized history of Card usage.

      +


    8. +
    +
  6. +
  7. + COSTS

    +


    +
      +
    1. + Costs, fees and commission (the “Costs”) may be charged in connection with the use + of SaltPay Oly Card. + These Costs are disclosed in the Official Mobile Application to the Cardholder.

      +


    2. +
    3. + Amendments to Costs + due to changing expenses or market conditions may be made at any time via + adjustments to the fee + schedules. Such amendments shall be communicated to the Cardholder in an appropriate + manner. Upon + notification and in the event of the objection, the Cardholder may cancel the + SaltPay Oly Card with + immediate effect.

    4. +
    +
  8. +
  9. + CARDHOLDER’S DUTIES OF CARE

    +


    +

    In particular, the + Cardholder shall exercise the + following duties of care:

    +


    +
      +
    1. + Upon receiving the SaltPay Oly Card and the Tangem Wallet, the Cardholder should + download the Official + Mobile Application from Google Play or Apple app store and create the backup of the + SaltPay Oly Card by + linking SaltPay Oly card with Tangem backup wallet following the instructions in the + Official Mobile + Application.

      +


    2. +
    3. Cardholder + shall keep the SaltPay Oly Card and Tangem Wallet with care and separate from each + other.

      +


    4. +
    5. + Cardholder must always know where the Tangem Wallet is and regularly ensure that it + is still in his/her + possession. He/she shall avoid even temporary possession of the Tangem Wallet by any + other person.

      +


    6. +
    7. + Cardholder shall + treat the Tangem Wallet in the same manner as physical money (cash) and keep it + safe. If the SaltPay Oly + Card and Tangem Wallet are lost, stolen or destroyed at the same time, control over + the corresponding + Blockchain Asset may be permanently lost.

      +


    8. +
    9. + Official Mobile + Application shall be the only source of information about the Blockchain Address of + the Blockchain Asset + and corresponding Public Key stored on the SaltPay Oly Card/Tangem Wallet.

      +


    10. +
    11. + Cardholder shall + only use Near-Field Devices (the “NFC”) devices that are capable of running Official + Mobile + Applications. He/she shall avoid leaving the Tangem Wallet in the proximity of the + NFC devices of other + persons.

      +


    12. +
    13. + Tangem Wallet shall + be used only for physically tapping and holding near Cardholder’s NFC device when + Official Mobile + Application requests it.

      +


    14. +
    15. + Cardholder shall + keep the Tangem Wallet with care and protect the Tangem Wallet from mechanical + damage, high + temperatures, strong electromagnetic fields, and other harmful factors.

      +


    16. +
    +
  10. +
  11. + RIGHTS AND + RESPONSIBILITIES OF CARDHOLDER

    +


    +
      +
    1. + The Cardholder is liable for all liabilities arising from the use of the Tangem + Wallet and/or Tangem + Official Mobile Application. As a matter of principle, the Cardholder is liable for + any risks resulting + from the misuse of the Tangem Wallet and/or Official Mobile Application. In any + case, the Cardholder is + liable for all transactions authorized using a means of access.

      +


    2. +
    3. + Any loss or damage + resulting from the forwarding of the Tangem Wallet and/or means of access shall be + borne by the + Cardholder.

      +


    4. +
    5. + Loss or damage + incurred by the Cardholder in connection with the possession or use of the Tangem + Wallet and/or Official + Mobile Application shall be borne solely by the Cardholder. Tangem assumes no + liability if the Tangem + Wallet and/or Official Mobile Application cannot be used due to a technical defect + or because it has + been canceled, blocked or the spending limit has been adjusted.

      +


    6. +
    7. + Cardholder is only + permitted to use the Tangem Wallet and Official Mobile Application for his personal, + non-commercial use. + Cardholder is not allowed to resell the Tangem Wallet.

    8. +
    9. + Cardholder is solely responsible to determinate what, if any, taxes apply to Card + Transactions. Tangem + or contributors to the Official Mobile Application are NOT responsible for + determining the taxes that + apply to Card Transactions.

      +


    10. +
    11. + Before Cardholder + engages in transactions + using an electronic system, Cardholder should carefully review the rules and + regulations of the + third-party provider Utorg OÜ (https://utorg.pro/terms/), + who provides exchange services for SaltPay Oly Card. Online trading has inherent + risk due to system + response and access times that may vary due to market conditions, system + performance, and other factors. + Cardholder should understand, fully accept and take on these and additional risks + before executing any + Card Transactions.

      +


    12. +
    13. + There is + considerable exposure to risk in the Blockchain Asset exchange transaction. Any + transaction involving + the Blockchain Asset involves risks including, but not limited to, the potential for + changing economic + conditions that may substantially affect the price or liquidity of the Blockchain + Asset. Investments in + the Blockchain Asset exchange speculation may also be susceptible to sharp rises and + falls as the + relevant market values fluctuate. It is for this reason that when speculating in + such markets it is + advisable to use only risk capital.

      +


    14. +
    +
  12. +
  13. + RESPONSIBILITIES AND + LIABILITIES OF TANGEM

    +


    +
      +
    1. + Tangem does not warrant or make any representations regarding the use, the inability + to use or operate, + or the results of the use or operation of the Tangem Wallet and/or Tangem Official + Mobile + Application.

      +


    2. +
    3. Tangem does not + provide any backup or recovery of Private Key and Public Key stored on the Tangem + Wallet.

      +


    4. +
    5. + Tangem does not keep any records of Cardholder information, the amount of Blockchain + Asset stored on the + Tangem Wallet or SaltPay Oly Card, the Private Key, or personalized history of cards + usage.

      +


    6. +
    7. + Tangem shall not be + held liable for any failure to be able to use the Tangem Wallet and/or Official + Mobile Application, for + any reason whatsoever, nor will Tangem be held liable for the loss of the Blockchain + Asset resulting + from a malfunction or inoperability of the blockchain network hosting Blockchain + Asset, as well as the + inaccessibility of its public servers and services.

      +


    8. +
    9. + The Tangem does not + guarantee that the operation of Tangem Wallet and/or Official Mobile Application + will be secure, + accurate, complete, uninterrupted, without error or free of viruses, worms, other + harmful components or + other program limitations. Tangem may, at its sole discretion and without obligation + to do so, correct, + modify, amend, enhance, improve and make any other changes to Tangem Wallet and/or + Official Mobile + Application.

      +


    10. +
    11. + Tangem shall not be + held liable for the loss of profits, income, value or any indirect, extraordinary, + consequential, + exemplary or punitive damages.

      +


    12. +
    13. Tangem shall + not be held liable for loss or breakdown of the Tangem Wallet.

      +


    14. +
    15. + Tangem shall not be held liable for any loss of Blockchain Asset in the event of + loss or total breakdown + of the Tangem Wallet or SaltPay Oly Card.

    16. +
    +
  14. +
  15. + GUARANTEES

    +


    +
      +
    1. + Under the condition that the Cardholder exercises the duties of care as stated in + Clause 5, Tangem + guarantees that the Tangem Wallet will function properly and without restriction for + a period of 2 (two) + years.

      +


    2. +
    3. + Tangem guarantees + that the Tangem Wallet prevents duplication of the Private Key and that the + Cardholder has exclusive + control over the Blockchain Asset unless the contrary is imposed by specific + blockchain network + rules,

      +

      + e.g. two or more + private keys can be used to control the same Blockchain Asset.

      +


    4. +
    +
  16. +
  17. + LIMITATION OF LIABILITY

    +


    +
      +
    1. + The Tangem Wallet, including without limitation any content, data and information + related thereto, is + provided on an “as is” basis and “as available” basis, without any warranties of any + kind, express or + implied warranties of use, merchantability or suitability for a certain purpose or + use, including + without limitation, the quality of products and services provided by users, + third-party services, and/or + exchanges (except for the guarantees set forth in Clause 8).

      +


      +

      + Official Mobile + Application is provided on an “as is” basis and “as available” basis without any + warranties of any + kind regarding Official Mobile Application and/or any content, data, materials + and/or services + provided on Official Mobile Application.

      +


    2. +
    3. + Tangem and its + affiliates, including any of their officers, directors, shareholders, employees, + sub-contractors, + agents, parent companies, subsidiaries and other affiliates (collectively, the + “Tangem Affiliates”), + jointly and severally, disclaim and make no representations or warranties as to the + usability, accuracy, + quality, availability, reliability, suitability, completeness, truthfulness, + usefulness or effectiveness + of any content, data, results or other information obtained or generated by Tangem + and/or any user + related to you or any other user of the Tangem Wallet, and Official Mobile + Applications.

      +


    4. +
    5. + In no event shall + Tangem and/or any of the Tangem Affiliates be liable for any damages whatsoever, + including direct, + indirect, extraordinary, incidental or consequential damages of any kind, but not + limited to, resulting + from or arising out of the use of Tangem Wallet and/or Official Mobile Applications + or inability to use + Tangem Wallet and/or Official Mobile Applications, failure of Tangem Wallet and/or + Official Mobile + Applications to perform as represented or expected, loss of goodwill or profits, or + loss of data arising + out of or in any way connected with the use of Tangem Wallet and/or Official Mobile + Applications. In no + event shall Tangem and/or any of the Tangem Affiliates be liable for the performance + or failure of the + Tangem Wallet and/ or Official Mobile Applications to perform under these Terms of + Use and any other act + or omission by Tangem by any cause whatsoever including without limitation damages + arising from the + conduct of any users, third party services and/or exchanges. In no way Tangem or + contributors to + Official Mobile Application are responsible for the actions, decisions, or other + behavior taken or not + taken by Cardholder in reliance upon Tangem Official Mobile Application.

      +


    6. +
    7. + You hereby + acknowledge and agree that these limitations of liability are agreed allocations of + risk constituting in + part the consideration for using the Tangem Wallet and Official Mobile Applications + and such limitations + will apply notwithstanding the failure of essential purpose of any limited remedy, + and even if Tangem + and/or any Tangem Affiliates has been advised of the possibility of such liabilities + and/or damages.

      +


    8. +
    9. + Tangem will not be + responsible for any losses, damages or claims arising from events falling within the + scope of the + following five categories:

      +
        +
      1. + Mistakes made by Cardholder, e.g., forgotten passwords, payments sent to + wrong addresses, and + accidental deletion of blockchain wallets on Tangem Wallet or SaltPay Oly + cards.

      2. +
      3. + Problems of + Official Mobile Application and/or any blockchain- or cryptocurrency- + related software or + service, e.g., corrupted files, incorrectly constructed transactions, unsafe + cryptographic + libraries, malware.

      4. +
      5. + Technical + failures in the hardware of Cardholder, including cards, of any blockchain- + or + cryptocurrency-related software or service, e.g., data loss due to a faulty + or damaged storage + device.

      6. +
      7. + Security + problems experienced by Cardholder, + e.g., unauthorized + access to Cardholders' wallets and/or accounts.

      8. +
      9. + Actions or + inactions of third parties and/or events experienced by third parties, e.g., + bankruptcy of + service providers, information security attacks on service providers, and + fraud conducted by + third parties.

        +


      10. +
      +
    10. +
    +
  18. +
  19. + SEVERABILITY

    +


    +
      +
    1. + In + the event any court shall declare any section or sections of this Agreement invalid + or void, such + declaration shall not invalidate the entire Agreement and all other paragraphs of + the Agreement shall + remain in full force and effect.

      +


    2. +
    +
  20. +
  21. + ARBITRATION AND + GOVERNING LAW

    +


    +
      +
    1. + These Terms of Service are governed exclusively by the laws of Switzerland, without + regard to its + conflict of law rules. You consent to the exclusive jurisdiction of the court of + Zug, Switzerland for + any dispute arising under this Agreement.

    2. +
    +
  22. +
+

+ Utorg OÜ Terms of Use

+


+

These Terms of + Use constitute an + electronic agreement between you (hereinafter the "User") and Utorg OÜ (hereinafter + the “Utorg.pro”, + “we”, “us”, “our”), a company incorporated in Estonia with Company No. + 14786273, located at + Roosikrantsi tn 2-1068, Kesklinna linnaosa,Tallinn, Harju maakond, 10119, that applies to the + User's use of this + website, any and all services, products and content provided by Utorg.pro.

+


+

As used herein, + "Utorg.pro" refers to the company Utorg OÜ, including but not limited to, its + owners, directors, + investors, officers, employees, agents or other related parties, unless otherwise provided + herein.

+


+

These Terms of + Use contain + important provisions, which the User must consider carefully when choosing whether to use the + services, products and + content of Utorg.pro. Please read these Terms of Use carefully before agreeing to them.

+


+

The User is + solely responsible for + understanding and complying with any and all laws, rules and regulations of his/her specific + jurisdiction that may + be applicable to the User in connection with the use of any and all services, products and + content of Utorg.pro.

+


+

These Terms of Use + incorporate our Privacy Policy + as well as any other policies published on the Site by reference, so by accepting these Terms of Use, the User agrees with and accepts all the policies published on the Site. +

+


+

IF THE USER + DOES NOT ACCEPT THESE + TERMS OF USE, THE USER SHALL NOT USE ANY OF UTORG.PRO’S SERVICES, PRODUCTS AND CONTENT.

+


+
    +
  1. + Terms and + Definitions

    +


    +

    + “Account” + shall mean a record created and maintained by the Software that accumulates and stores + all information about + the Cardholder, including without limitation Transactional history, Cryptocurrency + balance, and the + Cardholder personal information.

    +

    + “SaltPay + Oly Card” shall mean a plastic card linked to the Account. “Cardholder” shall + mean an individual + or entity holding a SaltPay Oly Card.

    +

    + "Confidential + Information" shall mean any data or information, oral or written, treated as + confidential that + relates to either party’s (or, if either party is bound to protect the confidentiality + of any third party’s + information, such third party’s) past, present, or future research, development or + business activities, + including any unannounced products and services, any information relating to Services, + developments, + relevant documentation (in whatever form or media provided), inventions, processes, + plans, financial + information, end-user data, revenue, transaction volume, forecasts, projections, and the + financial terms of + this Agreement. Notwithstanding the foregoing, Confidential Information shall not be + deemed to include + information if: (i) it was already known to the receiving party prior to the Effective + Date of this + Agreement, as established by documentary evidence; (ii) it is in or has entered the + public domain through no + breach of this Agreement or other wrongful act of the receiving party; (iii) it has been + rightfully received + by the receiving party from a third party and without breach of any obligation of + confidentiality of such + third party to the owner of the Confidential Information; (iv) it has been approved for + release by written + authorization of the owner of the Confidential Information; or, (v) it has been + independently developed by a + party without access to or use of the Confidential Information of the other party.”

    +

    + “Cryptocurrency” + shall mean any medium of exchange using cryptography to secure transactions and to + control the creation of + new units, as bitcoins (http://bitcoin.org), or any other that meet those requirements. +

    +

    + “Force-Majeure + Event” shall mean any act or event beyond a Party’s reasonable control, including + without limitation + non-delivery or defective delivery of third party services necessary to provide the + Services (including but + not limited to those of our partners, vendors and suppliers), strikes, lock-outs or + other industrial action + by third parties, civil commotion, riot, invasion, terrorist attack or threat of + terrorist attack, war + (whether declared or not) or threat or

    +

    + preparation for war, fire, explosion, storm, flood, earthquake, subsidence, epidemic or + other natural + disaster, failure of public or private telecommunications networks, worldwide Web + unavailability or + malfunction, governmental prohibition or other limitation of Cryptocurrencies, or + seizing of infrastructure + and operations of Cryptocurrencies.

    +

    + “Over-the-counter + (OTC) deal” shall mean a direct, off-exchange, Transaction between Utorg and + Cardholder. “Price” + shall mean a "price per coin" for which users are willing to purchase or sell + Cryptocurrency using + the Services. “Relevant Exchange Rate” shall mean the weighted spot price average + at the time of the + transaction.

    +

    Services” + shall mean + providing services of exchanging a virtual currency against a fiat currency. + “Site” means Utorg.pro’s website + at https://Utorg.pro.

    +

    Specified + Bank Account” + means the bank account belonging to Salt Pay IIB hf., the details of which shall be + provided to Utorg.pro. +

    +

    Software” + shall mean a + software owned by Utorg and enabling Transactions’ processing.

    +

    + “Transaction” + shall mean any of the following: (i) a purchase of the Cryptocurrency by the Cardholder; + (ii) a sale of the + Cryptocurrency by the Cardholder; (iii) a transfer of Cryptocurrency to a Cardholder. +

    +


  2. +
  3. + Scope of the + Services

    +


    +
      +
    1. + Utorg undertakes to + provide the Cardholder with Services, including but not limited to OTC deal. For the + avoidance of doubt, + Utorg shall use the Relevant Exchange Rate when executing the OTC deal.

      +


    2. +
    3. + The Services will + be provided in such a manner that Utorg will be acting on its own behalf strictly + under the Cardholder’s + instructions regarding any Transaction as well as other matters which the Cardholder + consider to be + necessary hereunder and are accepted by Utorg.

      +


    4. +
    5. + Utorg will treat + the each individual Cardholder as its sole client to which it owes obligations and + will bear no + responsibility to third parties for whom the Cardholder may be acting as an agent, + intermediary or + fiduciary, whether or not the existence or identity of such person(s) has been + disclosed to, or is known + by Utorg.

      +


    6. +
    7. + Utorg does not + provide banking, or payment Services and does not hold or store the Cardholder’s + fiat or crypto + currencies.

      +


    8. +
    +
  4. +
  5. + Warranties

    +


    +

    The + Cardholder hereby warrants + and represents to Utorg as follows:

    +
      +
    1. + It + represents and warrants that all than all outgoing Transactions, cryptocurrency + and/or fiat currency, + are made only to the Specified Bank Account, and Utorg shall not be liable for the + consequences of any + other incoming Transactions.

      +


    2. +
    3. + It also represents + and warrants than all incoming Transactions, cryptocurrency and/or fiat currency, + are made only from its + own wallet and/or bank account, and Utorg shall not be liable for the consequences + of any other incoming + Transactions.

      +


    4. +
    5. + It shall use the + Services in strict conformity with the Terms of Use and for no other purpose and in + no other manner. All + other warranties and representations regarding the use of the Services set forth + below shall be in + addition to and not in limitation of this general overriding warranty and + representation.

      +


    6. +
    7. + The Cardholder represents + and warrants to accept + and comply with all policies posted on Utorg.pro website: https://utorg.pro, Terms of Use (https://utorg.pro/terms/), Privacy Policy + (https://utorg.pro/privacy-policy), KYC/AML Policy + (https://utorg.pro/kyc-aml-policy). +

    8. +
    +
  6. +
  7. Rights + and Obligations

    +


    +
      +
    1. The Cardholder + has right to:

      +


      +
        +
      1. ask + Utorg for any type of assistance and support;

        +


      2. +
      3. + request a confirmation letter stating all information on Cardholder’s + Transactions; and

        +


      4. +
      5. + be promptly notified of any relevant changes, including but not limited to Commissions, bank details, cryptocurrency wallets, etc. +

        +


      6. +
      +
    2. +
    3. + Utorg has the right + to stop providing the Services and terminate the Agreement immediately in case of + any breach of the + Agreement or/and the Terms of Use;

      +


    4. +
    5. Utorg + undertakes to provide the Cardholder with full assistant and support regarding the + Service + provision.

      +


    6. +
    +
  8. +
  9. KYC + Verification

    +


    +
      +
    1. + Identification and + verification procedures (also known as “Know Your Customer” or “KYC”) + are required for all + Transactions, which shall be undertaken by Utorg. If the User refuses to provide + required documents and + information under KYC, including additional information that can be requested after + the business + relationships were established, Utorg.pro reserves the right to immediately + terminate the Services + provision to the User.

      +


    2. +
    3. + The User hereby + authorises Utorg.pro to, directly or indirectly (through third parties), make any + inquiries as + considered necessary to check the relevance and accuracy of the information provided + for verification + purposes. Personal Data transferred will be limited to strictly the necessary and + with security measures + in use to protect the data.

      +


    4. +
    +
  10. +
  11. + Regulatory + Compliance

    +


    +
      +
    1. + For the purposes of + anti-money laundering prevention and combatting terrorist activities, Utorg performs + a thorough due + diligence on each of its customers. Cardholder will have to provide all documents + and information + requested by Utorg for KYC purposesin order to be able to use the Services. Utorg + will keep records of + the Cardholder’s information and documents safe and confidential in accordance with + its Privacy Policy + and the applicable legal and regulatory requirements.

      +


    2. +
    3. + Utorg reserves the + right to request any additional information about the Cardholder and/or the + Cardholder’s Transaction, + and the Cardholder agrees to provide such documents and information in due time. + Utorg has a right to + decline to deliver the Services anytime in case the documents or information + provided by the Cardholder + are unsatisfactory or insufficient, or in case such actions are necessary to comply + with the applicable + laws and regulations, as Utorg may decide in its sole discretion.

      +


    4. +
    5. + The Cardholder will + be able to perform Transactions within certain amounts according to the limits + established by Utorg and + the applicable law. Cardholder will be able to provide some additional information + as instructed by + Utorg to increase or eliminate these limits.

      +


    6. +
    +
  12. +
  13. + Intellectual + Property

    +


    +
      +
    1. + All content at + Utorg's website is the property of Utorg and is protected by copyright, patent, + trademark and any + other applicable laws, unless otherwise specified hereby.

      +


    2. +
    3. + The trademarks, + brands, trade names, service marks and logos of Utorg and others used on the Site + (hereinafter, the + "Trademarks") are the property of Utorg and its respective owners. + The software, + applications, text, images, graphics, data, prices, trades, charts, graphs, video + and audio materials + used on this Site belong to Utorg. The Trademarks and other content on the Platform + and at Utorg's + website should not be copied, reproduced, modified, republished, uploaded, posted, + transmitted, scraped, + collected or distributed in any form or by any means, whether manual or automated. + The use of any + content from the Platform and at Utorg's website on any other site or a + networked computer + environment for any other purpose

      +

      + is + strictly prohibited; any such unauthorized use may violate copyright, patent, + trademark and any + other applicable laws and could result in criminal or civil + penalties/liabilities.

      +


    4. +
    +
  14. +
  15. + Confidentiality

    +


    +
      +
    1. + Any information of + a Party (hereinafter, the “Disclosing Party”) which is not public and which + was obtained by the + other Party (hereinafter, the “Receiving Party”) in the course of performance + of this Agreement, + whether proprietary to such other Party or any third party, whether expressly marked + as confidential or + not and regardless of the form and manner, in which it was obtained, shall be deemed + “Confidential + Information”.

      +


    2. +
    3. With respect to + any and all Confidential Information of the Disclosing Party the Receiving Party + agrees to:

      +


      +
        +
      1. + hold it in + confidence and treat it with the same degree of care and diligence, as it + employs with respect + to its own confidential information, but in no case less than a reasonable + degree of care and + diligence;

        +


      2. +
      3. use + it solely for the purpose of performance of this Agreement;

        +


      4. +
      5. + communicate + it only to the employers and agents of the Receiving Party who have a need + to know such + Confidential Information for the purpose of performance by the Receiving + Party of this Agreement + and who are subject to confidentiality obligations with respect to such + information; and

        +


      6. +
      7. + upon + request of the Disclosing Party or following termination of this Agreement + promptly return to + the Disclosing Party any and all material embodiments of its Confidential + Information and + destroy those material embodiments of Confidential Information return of + which is not + practicable and certify such destruction to the Disclosing Party in + writing.

        +


      8. +
      +
    4. +
    5. + The Parties hereby + agree and acknowledge that restrictions on use of Confidential Information set forth + in Clauses 9.1 and + 9.2 above shall not apply to information, which (subject to the burden of proof + being on the Receiving + Party):

      +


      +
        +
      1. + was known + to the Receiving Party prior to it being obtained from the Disclosing Party + in connection with + this Agreement;

        +


      2. +
      3. + has been + developed or otherwise obtained by the Receiving Party without the use of + the Confidential + Information;

        +


      4. +
      5. has + become public through no fault of the Receiving Party.

        +


      6. +
      +
    6. +
    7. + The Parties hereby + agree that limitations on use and disclosure of Confidential Information as per the + Agreement shall not + apply to any disclosure made in accordance with a valid law, court order, or other + statutory act or + decision of a judicial or governmental authority that has relevant jurisdiction over + the Receiving Party + (the ”Act”), provided that:

      +


      +
        +
      1. The + Receiving Party has provided the Disclosing Party with notice of the Act as + soon as + practicable;

        +


      2. +
      3. + the + Receiving Party has provided all reasonable assistance to the Disclosing + Party (at the + Disclosing Party’s expense) in contesting the Act as the Disclosing Party + had requested;

        +


      4. +
      5. + the + Receiving Party has disclosed only the portion of Confidential Information + which it was obliged + to disclose as per the Act (subject to any alterations due to the Disclosing + Party contesting + the Act).

        +


      6. +
      +
    8. +
    +
  16. +
  17. + Taxation

    +

    + Utorg + makes no representations concerning the tax implications of the Transactions or the + possession or use the + Cryptocurrency. The Cardholder bears the sole responsibility to determine if the + Cryptocurrency or the + potential appreciation or depreciation in the value of the Cryptocurrency over time has + tax implications for + the Cardholder in the

    +

    + Cardholder's + home jurisdiction. By using the Services, and to the extent permitted by law, the + Cardholder agrees not to + hold Utorg liable for any tax liability associated with or arising from the Cardholder's + use of the + Services.

    +


  18. +
  19. + Governing Law

    +

    + This + Agreement shall be governed by the laws of Republic of Estonia. All disputes and + controversies arising out + of or in connection with this Agreement shall be submitted to the relevant Estonian + Court in Tallinn, as the + Court of first instance.

    +


  20. +
  21. + Dispute Resolution

    +


    +
      +
    1. + The Parties + undertake to make every possible effort to resolve disputes that may arise in the + performance of this + Agreement by means of negotiations.

      +


    2. +
    3. + Any dispute, + controversy or claim arising out of or relating to this contract, or the breach + termination or + invalidity thereof, that cannot be solved in the process of negotiations shall be + settled in the + appropriate court according to the Governing Law.

    4. +
    +
  22. +
+
+
+
+
+
+
+ + diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt index 4538be7224..abbd7bf8de 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt @@ -6,6 +6,7 @@ import androidx.activity.OnBackPressedCallback import androidx.fragment.app.Fragment import androidx.transition.TransitionInflater import by.kirich1409.viewbindingdelegate.viewBinding +import com.tangem.tap.common.extensions.configureSettings import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.features.disclaimer.redux.DisclaimerAction @@ -16,17 +17,20 @@ import com.tangem.wallet.databinding.FragmentDisclaimerBinding import org.rekotlin.StoreSubscriber class DisclaimerFragment : Fragment(R.layout.fragment_disclaimer), - StoreSubscriber { + StoreSubscriber { private val binding: FragmentDisclaimerBinding by viewBinding(FragmentDisclaimerBinding::bind) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - store.dispatch(NavigationAction.PopBackTo()) - } - }) + activity?.onBackPressedDispatcher?.addCallback( + this, + object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + store.dispatch(NavigationAction.PopBackTo()) + } + }, + ) val inflater = TransitionInflater.from(requireContext()) enterTransition = inflater.inflateTransition(android.R.transition.slide_bottom) exitTransition = inflater.inflateTransition(android.R.transition.slide_top) @@ -51,9 +55,15 @@ class DisclaimerFragment : Fragment(R.layout.fragment_disclaimer), binding.toolbar.setNavigationOnClickListener { store.dispatch(NavigationAction.PopBackTo()) } + initAndLoadTOS() setOnClickListeners() } + private fun initAndLoadTOS() { + binding.webView.configureSettings() + binding.webView.loadUrl("file:///android_asset/tos.html") + } + private fun setOnClickListeners() { binding.btnAccept.setOnClickListener { store.dispatch(DisclaimerAction.AcceptDisclaimer) } } diff --git a/app/src/main/res/layout/fragment_disclaimer.xml b/app/src/main/res/layout/fragment_disclaimer.xml index bec0ceace0..556960be96 100644 --- a/app/src/main/res/layout/fragment_disclaimer.xml +++ b/app/src/main/res/layout/fragment_disclaimer.xml @@ -27,30 +27,21 @@ - - - - - + + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" /> + app:layout_constraintStart_toEndOf="@+id/guideline" /> From 6e2e5ba086f4552900f4f0df1bc9c9f7cc4d0690 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 18 Oct 2022 17:08:52 +0500 Subject: [PATCH 071/113] Updated on 2026-08-14 --- .../wallet/ui/OnboardingWalletFragment.kt | 21 +++++-- .../tap/features/wallet/ui/WalletFragment.kt | 57 ++++++++++++------- 2 files changed, 51 insertions(+), 27 deletions(-) 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 5a8376070a..8a444373b5 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 @@ -1,6 +1,5 @@ package com.tangem.tap.features.onboarding.products.wallet.ui -import android.graphics.BitmapFactory import android.os.Bundle import android.util.TypedValue import android.view.View @@ -15,6 +14,7 @@ import androidx.transition.TransitionInflater import androidx.transition.TransitionManager import by.kirich1409.viewbindingdelegate.viewBinding import coil.load +import coil.size.Scale import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.button.MaterialButton @@ -466,14 +466,25 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), } private fun handleCardArtworks(state: OnboardingWalletState) = with(walletFragment.binding) { + //TODO: SaltPay: remove hardCode if (state.onboardingSaltPayState?.saltPayCardArtworkUrl == null) { - // if saltPay url not loaded -> load from resource - val bitmap = BitmapFactory.decodeResource(walletFragment.resources, R.drawable.img_salt_pay_visa) - imvFrontCard.setImageBitmap(bitmap) + imvFrontCard.load(R.drawable.img_salt_pay_visa) { + scale(Scale.FILL) + crossfade(enable = true) + } } else { walletFragment.loadImageIntoImageView(state.onboardingSaltPayState.saltPayCardArtworkUrl, imvFrontCard) } - walletFragment.loadImageIntoImageView(state.cardArtworkUrl, imvFirstBackupCard) + imvFirstBackupCard.load(R.drawable.card_placeholder_wallet) + + // if (state.onboardingSaltPayState?.saltPayCardArtworkUrl == null) { + // //if saltPay url not loaded -> load from resource + // val bitmap = BitmapFactory.decodeResource(walletFragment.resources, R.drawable.img_salt_pay_visa) + // imvFrontCard.setImageBitmap(bitmap) + // } else { + // 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 diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index b7748269e4..1f7d881721 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -26,7 +26,6 @@ import com.tangem.tap.domain.statePrinter.printScanResponseState import com.tangem.tap.domain.statePrinter.printWalletState import com.tangem.tap.domain.termsOfUse.CardTou import com.tangem.tap.features.details.redux.DetailsAction -import com.tangem.tap.features.wallet.redux.Artwork import com.tangem.tap.features.wallet.redux.ErrorType import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.tap.features.wallet.redux.WalletAction @@ -53,11 +52,14 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber { store.dispatch(GlobalAction.UpdateFeedbackInfo(store.state.walletState.walletManagers)) store.state.globalState.scanResponse?.let { scanNoteResponse -> - store.dispatch(DetailsAction.PrepareScreen( - scanNoteResponse, - store.state.walletState.walletManagers.map { it.wallet }, - CardTou(), - )) + store.dispatch( + DetailsAction.PrepareScreen( + scanNoteResponse, + store.state.walletState.walletManagers.map { it.wallet }, + CardTou(), + ), + ) store.dispatch(NavigationAction.NavigateTo(AppScreen.Details)) true } @@ -207,5 +221,4 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber Date: Tue, 18 Oct 2022 17:46:53 +0500 Subject: [PATCH 072/113] Updated on 2026-08-14 --- .../products/wallet/saltPay/SaltPayRegistrationManager.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 b7d5364a16..db09316125 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 @@ -30,7 +30,7 @@ class SaltPayRegistrationManager( ) { val kycUrlProvider = KYCUrlProvider(walletPublicKey, kycProvider) - private val approvalValue: BigDecimal = BigDecimal.ONE + private val approvalValue: BigDecimal = BigDecimal.valueOf(Math.pow(2.toDouble(), 255.toDouble())) private val spendLimitValue: BigDecimal = BigDecimal("100") fun transactionIsSent(): Boolean { From eea2cb85596339dda2d2d9890edc51b6767b6d55 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 18 Oct 2022 19:25:53 +0500 Subject: [PATCH 073/113] Updated on 2026-08-14 --- .../products/wallet/saltPay/SaltPayRegistrationManager.kt | 2 +- dependencies.gradle | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) 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 db09316125..d1e9190edb 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 @@ -30,7 +30,7 @@ class SaltPayRegistrationManager( ) { val kycUrlProvider = KYCUrlProvider(walletPublicKey, kycProvider) - private val approvalValue: BigDecimal = BigDecimal.valueOf(Math.pow(2.toDouble(), 255.toDouble())) + private val approvalValue: BigDecimal = BigDecimal.valueOf(Math.pow(2.toDouble(), 256.toDouble()) - 1) private val spendLimitValue: BigDecimal = BigDecimal("100") fun transactionIsSent(): Boolean { diff --git a/dependencies.gradle b/dependencies.gradle index dd0bad7178..e50dacb700 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -1,9 +1,9 @@ ext.versions = [ kotlin : '1.6.10', build_gradle : '7.1.3', - // tangem_card_sdk : 'develop-164', + tangem_card_sdk : 'develop-165', tangem_card_sdk : '0.0.1', - // tangem_blockchain_sdk: 'develop-128', + tangem_blockchain_sdk: 'develop-129', tangem_blockchain_sdk: '0.0.1', ] From 88bf5f0535b423f5653cbbbe736c248ff21c0fd8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 18 Oct 2022 21:43:15 +0500 Subject: [PATCH 074/113] Updated on 2026-08-14 --- .../main/java/com/tangem/domain/common/SaltPayWorkaround.kt | 4 ++-- .../java/com/tangem/domain/common/extensions/Blockchain.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/domain/src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt b/domain/src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt index 9fd1cd8213..cd122d1a34 100644 --- a/domain/src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt +++ b/domain/src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt @@ -13,14 +13,14 @@ object SaltPayWorkaround { symbol = "WXDAI", contractAddress = "0x4346186e7461cB4DF06bCFCB4cD591423022e417", decimals = 18, - id = "xdai", + id = "wrapped-xdai", ) Blockchain.SaltPayTestnet -> Token( name = "WXDAI Test", symbol = "MyERC20", contractAddress = "0x69cca8D8295de046C7c14019D9029Ccc77987A48", decimals = 0, - id = "xdai", + id = "wrapped-xdai", ) else -> throw IllegalArgumentException() } diff --git a/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt b/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt index 1f5a827785..a720d8bf19 100644 --- a/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt +++ b/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt @@ -134,7 +134,7 @@ fun Blockchain.toCoinId(): String { Blockchain.Kusama -> "kusama" Blockchain.Optimism, Blockchain.OptimismTestnet -> "ethereum" Blockchain.Dash -> "dash" - Blockchain.SaltPay, Blockchain.SaltPayTestnet -> "wrapped-xdai" + Blockchain.SaltPay, Blockchain.SaltPayTestnet -> "xdai" Blockchain.Unknown -> "unknown" } } \ No newline at end of file From 8fc105db067569e06924daea1987b69984f2c6e9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 18 Oct 2022 21:43:28 +0500 Subject: [PATCH 075/113] Updated on 2026-08-14 --- .../products/wallet/saltPay/SaltPayRegistrationManager.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 d1e9190edb..93e129737c 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 @@ -30,7 +30,9 @@ class SaltPayRegistrationManager( ) { val kycUrlProvider = KYCUrlProvider(walletPublicKey, kycProvider) - private val approvalValue: BigDecimal = BigDecimal.valueOf(Math.pow(2.toDouble(), 256.toDouble()) - 1) + private val approvalValue: BigDecimal = BigDecimal(2).pow(256).minus(BigDecimal.ONE) + .movePointLeft(gnosisRegistrator.walletManager.wallet.blockchain.decimals()) + private val spendLimitValue: BigDecimal = BigDecimal("100") fun transactionIsSent(): Boolean { From b2d6bb0af599dbd9b9b7c481f93c2cf69c98aba3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 18 Oct 2022 22:40:13 +0500 Subject: [PATCH 076/113] Updated on 2026-08-14 --- .../test/java/com/tangem/domain/features/BlockchainTests.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/domain/src/test/java/com/tangem/domain/features/BlockchainTests.kt b/domain/src/test/java/com/tangem/domain/features/BlockchainTests.kt index b8291b93ac..b6e967037c 100644 --- a/domain/src/test/java/com/tangem/domain/features/BlockchainTests.kt +++ b/domain/src/test/java/com/tangem/domain/features/BlockchainTests.kt @@ -11,7 +11,10 @@ class BlockchainTests { fun allNetworkIdsAreImplemented() { val unimplementedIds = Blockchain.values() .toMutableList() - .apply { remove(Blockchain.Unknown) } + .apply { + remove(Blockchain.Unknown) + remove(Blockchain.Optimism) + } .map { it to Blockchain.fromNetworkId(it.toNetworkId()) } .mapNotNull { if(it.second == null) it.first else null } From a895ceb04d0c855dada40cfec06b6824b797b0c7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 19 Oct 2022 12:16:44 +0500 Subject: [PATCH 077/113] Updated on 2026-08-14 --- .../wallet/redux/OnboardingWalletState.kt | 11 ++++++++++ .../products/wallet/ui/WalletCardsWidget.kt | 21 +++++++++++-------- 2 files changed, 23 insertions(+), 9 deletions(-) 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 174766a762..566cf6aaf3 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 @@ -1,6 +1,8 @@ package com.tangem.tap.features.onboarding.products.wallet.redux import android.graphics.Bitmap +import com.tangem.common.CardFilter +import com.tangem.domain.common.SaltPayWorkaround import com.tangem.tap.common.redux.StateDialog import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayState import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.SaltPayRegistrationStep @@ -18,6 +20,15 @@ data class OnboardingWalletState( val showConfetti: Boolean = false, ) : StateType { + val backupCardIdFilter: CardFilter.Companion.CardIdFilter? + get() = when { + isSaltPay -> CardFilter.Companion.CardIdFilter.Allow( + items = SaltPayWorkaround.walletCardIds.toSet(), + ranges = SaltPayWorkaround.walletCardIdRanges, + ) + else -> null + } + fun getMaxProgress(): Int = when { isSaltPay -> 12 else -> 6 diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/WalletCardsWidget.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/WalletCardsWidget.kt index 9f42b3be0b..833c3dfb81 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/WalletCardsWidget.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/WalletCardsWidget.kt @@ -16,6 +16,8 @@ class WalletCardsWidget( val getTopOfAnchorViewForActivateState: () -> Float, ) { + val animDuration: Long = 400 + var currentState: WidgetState? = null fun toWelcome(animate: Boolean = true, onEnd: () -> Unit = {}) { @@ -27,7 +29,7 @@ class WalletCardsWidget( animator.playTogether( createAnimator(BackupCardType.ORIGIN, createWelcomeProperties(BackupCardType.ORIGIN)), createAnimator(BackupCardType.FIRST_BACKUP, createWelcomeProperties(BackupCardType.FIRST_BACKUP)), - createAnimator(BackupCardType.SECOND_BACKUP, createWelcomeProperties(BackupCardType.SECOND_BACKUP)) + createAnimator(BackupCardType.SECOND_BACKUP, createWelcomeProperties(BackupCardType.SECOND_BACKUP)), ) leapfrogWidget.fold { animator.start() } } @@ -41,7 +43,7 @@ class WalletCardsWidget( animator.playTogether( createAnimator(BackupCardType.ORIGIN, createLeapfrogProperties(BackupCardType.ORIGIN)), createAnimator(BackupCardType.FIRST_BACKUP, createLeapfrogProperties(BackupCardType.FIRST_BACKUP)), - createAnimator(BackupCardType.SECOND_BACKUP, createLeapfrogProperties(BackupCardType.SECOND_BACKUP)) + createAnimator(BackupCardType.SECOND_BACKUP, createLeapfrogProperties(BackupCardType.SECOND_BACKUP)), ) leapfrogWidget.fold(animate) { animator.start() } } @@ -55,7 +57,7 @@ class WalletCardsWidget( animator.playTogether( createAnimator(BackupCardType.ORIGIN, createFanProperties(BackupCardType.ORIGIN)), createAnimator(BackupCardType.FIRST_BACKUP, createFanProperties(BackupCardType.FIRST_BACKUP)), - createAnimator(BackupCardType.SECOND_BACKUP, createFanProperties(BackupCardType.SECOND_BACKUP)) + createAnimator(BackupCardType.SECOND_BACKUP, createFanProperties(BackupCardType.SECOND_BACKUP)), ) leapfrogWidget.fold { animator.start() } } @@ -67,7 +69,7 @@ class WalletCardsWidget( animator.playTogether( createAnimator(BackupCardType.ORIGIN, createLeapfrogProperties(BackupCardType.ORIGIN)), createAnimator(BackupCardType.FIRST_BACKUP, createLeapfrogProperties(BackupCardType.FIRST_BACKUP)), - createAnimator(BackupCardType.SECOND_BACKUP, createLeapfrogProperties(BackupCardType.SECOND_BACKUP)) + createAnimator(BackupCardType.SECOND_BACKUP, createLeapfrogProperties(BackupCardType.SECOND_BACKUP)), ) leapfrogWidget.fold { animator.doOnEnd { @@ -80,7 +82,7 @@ class WalletCardsWidget( private fun createAnimator(animate: Boolean, onEnd: () -> Unit): AnimatorSet { return AnimatorSet().apply { - duration = if (animate) 400 else 0 + duration = if (animate) animDuration else 0 doOnEnd { onEnd() } } } @@ -90,8 +92,11 @@ class WalletCardsWidget( properties: CardProperties, ): ObjectAnimator { val view = getLeapViewByCardNumber(cardType).view - val animator = ObjectAnimator.ofPropertyValuesHolder(view, - *properties.createValuesHolders().toTypedArray()) + val animator = + ObjectAnimator.ofPropertyValuesHolder( + view, + *properties.createValuesHolders().toTypedArray(), + ) view.elevation = properties.elevation return animator } @@ -208,10 +213,8 @@ class WalletCardsWidget( enum class WidgetState { WELCOME, FOLDED, FAN, LEAPFROG } } - enum class BackupCardType { ORIGIN, FIRST_BACKUP, SECOND_BACKUP } - private data class CardProperties( val xTranslation: Float = 0f, val yTranslation: Float = 0f, From 172f46afc4e5bf4b7af7bd756509cbdf1b984be3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 19 Oct 2022 12:22:35 +0500 Subject: [PATCH 078/113] Updated on 2026-08-14 --- .../wallet/redux/OnboardingWalletMiddleware.kt | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index eb23b6b4e0..66c65bbf6d 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -1,11 +1,9 @@ package com.tangem.tap.features.onboarding.products.wallet.redux import com.tangem.blockchain.common.Blockchain -import com.tangem.common.CardFilter import com.tangem.common.CompletionResult import com.tangem.common.card.Card import com.tangem.common.extensions.ifNotNull -import com.tangem.domain.common.SaltPayWorkaround import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.extensions.withMainContext import com.tangem.operations.backup.BackupService @@ -247,11 +245,10 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) } } is BackupAction.AddBackupCard -> { - backupService.skipCompatibilityChecks = true - tangemSdk.config.filter.cardIdFilter = CardFilter.Companion.CardIdFilter.Allow( - items = SaltPayWorkaround.walletCardIds.toSet(), - ranges = SaltPayWorkaround.walletCardIdRanges, - ) + onboardingWalletState.backupCardIdFilter?.let { + backupService.skipCompatibilityChecks = true + tangemSdk.config.filter.cardIdFilter = it + } backupService.addBackupCard { result -> backupService.skipCompatibilityChecks = false From 5ec2a0c5d5449ebc95f087332b72e60470d239c6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 19 Oct 2022 12:26:48 +0500 Subject: [PATCH 079/113] Updated on 2026-08-14 --- dependencies.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dependencies.gradle b/dependencies.gradle index e50dacb700..7ecf2fc3a7 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -2,9 +2,9 @@ ext.versions = [ kotlin : '1.6.10', build_gradle : '7.1.3', tangem_card_sdk : 'develop-165', - tangem_card_sdk : '0.0.1', + // tangem_card_sdk : '0.0.1', tangem_blockchain_sdk: 'develop-129', - tangem_blockchain_sdk: '0.0.1', + // tangem_blockchain_sdk: '0.0.1', ] ext.environmentConfig = [ From a74efa295197c29cf8b32c94c3987f93fd4f53cb Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 20 Oct 2022 00:20:17 +0500 Subject: [PATCH 080/113] Updated on 2026-08-14 --- .../onboarding/OnboardingSaltPayHelper.kt | 11 + .../products/wallet/ui/BackupAnimator.kt | 292 ++++++++++++++++++ .../wallet/ui/OnboardingWalletFragment.kt | 196 ++++++------ .../products/wallet/ui/WalletCardsWidget.kt | 139 +++++---- .../main/res/layout/view_onboarding_card.xml | 10 + 5 files changed, 491 insertions(+), 157 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupAnimator.kt create mode 100644 app/src/main/res/layout/view_onboarding_card.xml 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 8b56bbc65a..d874af4acc 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 @@ -30,5 +30,16 @@ class OnboardingSaltPayHelper { Result.Failure(error) } } + + suspend fun testProceedToOnboarding(scanResponse: ScanResponse): Result { + return try { + val isBackupOnboardingCase = scanResponse.card.backupStatus?.isActive == false + Result.Success(isBackupOnboardingCase) + } catch (ex: Exception) { + Result.Failure(ex) + } catch (error: SaltPayRegistrationError){ + Result.Failure(error) + } + } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupAnimator.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupAnimator.kt new file mode 100644 index 0000000000..dd980fc2cd --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupAnimator.kt @@ -0,0 +1,292 @@ +package com.tangem.tap.features.onboarding.products.wallet.ui + +import com.google.android.material.button.MaterialButton +import com.tangem.tap.common.extensions.show +import com.tangem.tap.features.onboarding.products.wallet.redux.BackupState +import com.tangem.tap.features.onboarding.products.wallet.ui.BackupCardType.FIRST_BACKUP +import com.tangem.tap.features.onboarding.products.wallet.ui.BackupCardType.ORIGIN +import com.tangem.tap.features.onboarding.products.wallet.ui.BackupCardType.SECOND_BACKUP +import com.tangem.wallet.databinding.FragmentOnboardingWalletBinding + +/** +[REDACTED_AUTHOR] + */ +interface BackupAnimator { + fun updateBackupState(backupState: BackupState) + fun setupCreateWalletState() + fun showBackupIntro(state: BackupState) + fun showScanOriginCard() + fun showAddBackupCards(state: BackupState, backupCardsCounts: Int) + fun showWritePrimaryCard(state: BackupState) + fun showWriteBackupCard(state: BackupState, backupCard: Int) + fun showSuccess(onEnd: () -> Unit) +} + +class WalletBackupAnimator( + private val cardsWidget: WalletCardsWidget, +) : BackupAnimator { + + private val viewState: State = when (cardsWidget.leapfrogWidget.getViewsCount()) { + 2 -> State.TwoCards + 3 -> State.TreeCards + else -> throw UnsupportedOperationException() + } + + private var currentStep = 0 + + override fun updateBackupState(backupState: BackupState) { + } + + override fun setupCreateWalletState() { + currentStep = STEP_CREATE_WALLET + + cardsWidget.toFolded(false) + + ORIGIN.alpha(1f) + FIRST_BACKUP.alpha(1f) + SECOND_BACKUP.alpha(1f) + } + + override fun showBackupIntro(state: BackupState) { + currentStep = STEP_BACKUP_INTRO + + ORIGIN.show() + FIRST_BACKUP.show() + SECOND_BACKUP.show() + + cardsWidget.toWelcome() + } + + override fun showScanOriginCard() { + currentStep = STEP_SCAN_ORIGIN_CARD + + cardsWidget.toFolded() + } + + override fun showAddBackupCards(state: BackupState, backupCardsCounts: Int) { + when (backupCardsCounts) { + 0 -> { + when (currentStep) { + STEP_CREATE_WALLET -> { + cardsWidget.toFolded(false) { cardsWidget.toFan() } + + FIRST_BACKUP.animateAlpha(0.6f, 0) + SECOND_BACKUP.animateAlpha(0.2f, 400) + } + STEP_BACKUP_INTRO -> { + cardsWidget.toFolded(duration = 200) { cardsWidget.toFan() } + FIRST_BACKUP.animateAlpha(0.6f, 400, 250) + SECOND_BACKUP.animateAlpha(0.2f, 400, 250) + } + STEP_SCAN_ORIGIN_CARD -> { + cardsWidget.toFan() + FIRST_BACKUP.animateAlpha(0.6f, 400, 250) + SECOND_BACKUP.animateAlpha(0.2f, 400, 250) + } + } + } + 1 -> { + cardsWidget.toFan(false) + FIRST_BACKUP.alpha(1f) + SECOND_BACKUP.alpha(0.2f) + } + 2 -> { + cardsWidget.toFan(false) + FIRST_BACKUP.alpha(1f) + SECOND_BACKUP.alpha(1f) + } + } + + currentStep = STEP_ADD_BACKUP_CARDS + } + + override fun showWritePrimaryCard(state: BackupState) { + currentStep = STEP_WRITE_PRIMARY_CARD + + cardsWidget.toLeapfrog( + onEndFold = { + FIRST_BACKUP.alpha(0.4f) + SECOND_BACKUP.alpha(0.2f) + }, + ) + } + + override fun showWriteBackupCard(state: BackupState, backupCard: Int) { + currentStep = STEP_WRITE_BACKUP_CARD + + when (viewState) { + State.TwoCards -> { + FIRST_BACKUP.alpha(1f) + cardsWidget.leapfrogWidget.leap { + ORIGIN.alpha(0.4f) + } + } + State.TreeCards -> { + when (backupCard) { + 1 -> { + FIRST_BACKUP.alpha(1f) + cardsWidget.leapfrogWidget.leap { + SECOND_BACKUP.alpha(0.4f) + ORIGIN.alpha(0.2f) + } + } + 2 -> { + SECOND_BACKUP.alpha(1f) + cardsWidget.leapfrogWidget.leap { + ORIGIN.alpha(0.4f) + FIRST_BACKUP.alpha(0.2f) + } + } + } + } + } + } + + override fun showSuccess(onEnd: () -> Unit) { + currentStep = STEP_SUCCESS + cardsWidget.leapfrogWidget.fold(onEnd = onEnd) + } + + private enum class State { + TreeCards, + TwoCards + } + + private fun BackupCardType.animateAlpha( + alpha: Float, + duration: Long = 0L, + startDelay: Long = 0L, + ) { + when (this) { + ORIGIN -> { + cardsWidget.getOriginCardView().animate().apply { + alpha(alpha) + this.startDelay = startDelay + this.duration = duration + } + } + FIRST_BACKUP -> { + cardsWidget.getFirstBackupCardView().animate().apply { + alpha(alpha) + this.startDelay = startDelay + this.duration = duration + } + } + SECOND_BACKUP -> { + if (viewState != State.TreeCards) return + cardsWidget.getSecondBackupCardView().animate().apply { + alpha(alpha) + this.startDelay = startDelay + this.duration = duration + } + } + } + } + + private fun BackupCardType.alpha(alpha: Float) { + when (this) { + ORIGIN -> cardsWidget.getOriginCardView().alpha = alpha + FIRST_BACKUP -> cardsWidget.getFirstBackupCardView().alpha = alpha + SECOND_BACKUP -> { + if (viewState != State.TreeCards) return + cardsWidget.getSecondBackupCardView().alpha = alpha + } + } + } + + private fun BackupCardType.show() { + when (this) { + ORIGIN -> cardsWidget.getOriginCardView().show() + FIRST_BACKUP -> cardsWidget.getFirstBackupCardView().show() + SECOND_BACKUP -> { + if (viewState != State.TreeCards) return + cardsWidget.getSecondBackupCardView().show() + } + } + } + + companion object { + private const val STEP_CREATE_WALLET = 0 + private const val STEP_BACKUP_INTRO = 1 + private const val STEP_SCAN_ORIGIN_CARD = 2 + private const val STEP_ADD_BACKUP_CARDS = 3 + private const val STEP_WRITE_PRIMARY_CARD = 4 + private const val STEP_WRITE_BACKUP_CARD = 5 + private const val STEP_SUCCESS = 6 + } +} + +class TestBackupAnimation( + private val animator: BackupAnimator, + binding: FragmentOnboardingWalletBinding, +) : BackupAnimator { + + private lateinit var state: BackupState + private var steps = 0 + + init { + init(binding, this) + } + + fun setStep(step: Int, onStepUpdate: (Int) -> Unit = {}) { + steps = step + when (steps) { + 0 -> setupCreateWalletState() + 1 -> showBackupIntro(state) + 2 -> showScanOriginCard() + 3 -> showAddBackupCards(state, 0) + 4 -> showAddBackupCards(state, 1) + 5 -> showAddBackupCards(state, 2) + 6 -> showWritePrimaryCard(state) + 7 -> showWriteBackupCard(state, 1) + 8 -> showWriteBackupCard(state, 2) + else -> setStep(0) + } + onStepUpdate(steps) + } + + fun onNext(onStepUpdate: (Int) -> Unit) { + steps++ + setStep(steps, onStepUpdate) + } + + override fun updateBackupState(backupState: BackupState) { + this.state = backupState + } + + override fun setupCreateWalletState() { + animator.setupCreateWalletState() + } + + override fun showBackupIntro(state: BackupState) { + animator.showBackupIntro(state) + } + + override fun showScanOriginCard() { + animator.showScanOriginCard() + } + + override fun showAddBackupCards(state: BackupState, backupCardsCounts: Int) { + animator.showAddBackupCards(state, backupCardsCounts) + } + + override fun showWritePrimaryCard(state: BackupState) { + animator.showWritePrimaryCard(state) + } + + override fun showWriteBackupCard(state: BackupState, backupCard: Int) { + animator.showWriteBackupCard(state, backupCard) + } + + override fun showSuccess(onEnd: () -> Unit) { + animator.showSuccess(onEnd) + } + + companion object { + fun init(binding: FragmentOnboardingWalletBinding, animator: TestBackupAnimation) { + val button = MaterialButton(binding.onboardingRoot.context).apply { text = "Next" } + button.setOnClickListener { animator.onNext { button.text = "$it" } } + binding.onboardingRoot.addView(button, 0) + } + } +} \ No newline at end of file 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 8a444373b5..2b22f898d7 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 @@ -5,6 +5,7 @@ import android.util.TypedValue import android.view.View import android.view.ViewGroup import android.view.WindowManager +import android.widget.FrameLayout import android.widget.ImageView import android.widget.ProgressBar import androidx.appcompat.app.AppCompatActivity @@ -30,6 +31,7 @@ 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.hide +import com.tangem.tap.common.extensions.inflate import com.tangem.tap.common.extensions.show import com.tangem.tap.common.extensions.stop import com.tangem.tap.common.feedback.SupportInfo @@ -63,7 +65,7 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), StoreSubscriber, FragmentOnBackPressedHandler { private val pbBinding: ViewOnboardingProgressBinding by viewBinding(ViewOnboardingProgressBinding::bind) - private val binding: FragmentOnboardingWalletBinding by viewBinding(FragmentOnboardingWalletBinding::bind) + val binding: FragmentOnboardingWalletBinding by viewBinding(FragmentOnboardingWalletBinding::bind) private val bindingSaltPay: LayoutOnboardingSaltpayBinding by lazy { binding.onboardingSaltpayContainer } private val saltPayView: SaltPayView = SaltPayView(this) @@ -71,9 +73,10 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), private lateinit var cardsWidget: WalletCardsWidget private var accessCodeDialog: AccessCodeDialog? = null + private lateinit var animator: BackupAnimator + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - postponeEnterTransition() setHasOptionsMenu(true) val inflater = TransitionInflater.from(requireContext()) @@ -84,16 +87,8 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val typedValue = TypedValue() - resources.getValue(R.dimen.device_scale_factor_for_twins_welcome, typedValue, true) - val deviceScaleFactor = typedValue.float - - val leapfrogCalculator = PropertyCalculator( - yTranslationFactor = 25f * deviceScaleFactor, - ) - val leapfrog = LeapfrogWidget(binding.flCardsContainer, leapfrogCalculator) - cardsWidget = WalletCardsWidget(leapfrog, deviceScaleFactor) { 200f * deviceScaleFactor } - startPostponedEnterTransition() + val scaleFactor = getDeviceScaleFactor() + initCardsWidget(createLeapfrogWidget(binding.flCardsContainer, scaleFactor), scaleFactor) binding.viewPagerBackupInfo.adapter = BackupInfoAdapter() TabLayoutMediator( @@ -113,6 +108,37 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), store.dispatch(OnboardingWalletAction.LoadArtwork) } + private fun reInitCardsWidget(backupCardsCounts: Int) = with(binding) { + val viewBackupCount = flCardsContainer.childCount - 1 + if (viewBackupCount <= 0) return@with + if (viewBackupCount == backupCardsCounts) return@with + + cardsWidget.toFolded(false) + + if (viewBackupCount > backupCardsCounts) { + flCardsContainer.removeViewAt(viewBackupCount - 1) + } else { + flCardsContainer.inflate(R.layout.view_onboarding_card, true) + } + + val scaleFactor = getDeviceScaleFactor() + initCardsWidget(createLeapfrogWidget(flCardsContainer, scaleFactor), scaleFactor) + } + + private fun createLeapfrogWidget(container: FrameLayout, deviceScaleFactor: Float): LeapfrogWidget { + val leapfrogCalculator = PropertyCalculator(yTranslationFactor = 25f * deviceScaleFactor) + return LeapfrogWidget(container, leapfrogCalculator) + } + + private fun initCardsWidget(leapfrogWidget: LeapfrogWidget, deviceScaleFactor: Float, isTest: Boolean = false) { + cardsWidget = WalletCardsWidget(leapfrogWidget, deviceScaleFactor) { 200f * deviceScaleFactor } + animator = if (isTest) { + TestBackupAnimation(WalletBackupAnimator(cardsWidget), binding) + } else { + WalletBackupAnimator(cardsWidget) + } + } + override fun onStart() { super.onStart() store.subscribe(this) { state -> @@ -129,7 +155,9 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), override fun newState(state: OnboardingWalletState) { if (activity == null || view == null) return + if (state.isSaltPay) reInitCardsWidget(1) + animator.updateBackupState(state.backupState) requireActivity().invalidateOptionsMenu() pbBinding.pbState.max = state.getMaxProgress() @@ -171,7 +199,7 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), tvHeader.setText(R.string.onboarding_create_wallet_header) tvBody.setText(R.string.onboarding_create_wallet_body) - cardsWidget.toFolded(false) { startPostponedEnterTransition() } + animator.setupCreateWalletState() } private fun setBackupState(state: BackupState, isSaltPay: Boolean) { @@ -192,8 +220,6 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), imvFirstBackupCard.show() imvSecondBackupCard.show() - cardsWidget.toWelcome() - tvHeader.hide() tvBody.hide() viewPagerBackupInfo.show() @@ -207,15 +233,11 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), btnAlternativeAction.setOnClickListener { store.dispatch(BackupAction.DismissBackup) } btnAlternativeAction.show(state.canSkipBackup && !isSaltPay) } - - startPostponedEnterTransition() + animator.showBackupIntro(state) } private fun showScanOriginCard() = with(binding) { prepareBackupView() - - cardsWidget.toFolded() - tvHeader.text = getText(R.string.onboarding_title_scan_origin_card) tvBody.text = getString( R.string.onboarding_subtitle_scan_origin_card, @@ -226,18 +248,8 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), btnAlternativeAction.hide() btnMainAction.setOnClickListener { store.dispatch(BackupAction.ScanPrimaryCard) } } - } - private fun prepareBackupView() = with(binding) { - toolbar.title = getText(R.string.onboarding_navbar_title_creating_backup) - - tvHeader.show() - tvBody.show() - viewPagerBackupInfo.hide() - tabLayoutBackupInfo.hide() - - imvFirstBackupCard.show() - imvSecondBackupCard.show() + animator.showScanOriginCard() } private fun showAddBackupCards(state: BackupState, isSaltPay: Boolean) = with(binding) { @@ -261,35 +273,37 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), if (isSaltPay) { saltPayView.showAddBackupCards(state) - return@with - } - - when (state.backupCardsNumber) { - 0 -> { - cardsWidget.toFan { - cardsWidget.getFirstBackupCardView().animate().alpha(0.6f).duration = 200 - cardsWidget.getSecondBackupCardView().animate().alpha(0.2f).duration = 200 + } else { + when (state.backupCardsNumber) { + 0 -> { + tvHeader.text = getText(R.string.onboarding_title_no_backup_cards) + tvBody.text = getText(R.string.onboarding_subtitle_no_backup_cards) } - tvHeader.text = getText(R.string.onboarding_title_no_backup_cards) - tvBody.text = getText(R.string.onboarding_subtitle_no_backup_cards) - } - 1 -> { - tvHeader.text = getText(R.string.onboarding_title_one_backup_card) - tvBody.text = getText(R.string.onboarding_subtitle_one_backup_card) - - cardsWidget.toFan(false) - cardsWidget.getFirstBackupCardView().animate().alpha(1f).duration = 400 - cardsWidget.getSecondBackupCardView().alpha = 0.2f - } - 2 -> { - tvHeader.text = getText(R.string.onboarding_title_two_backup_cards) - tvBody.text = getText(R.string.onboarding_subtitle_two_backup_cards) - - cardsWidget.toFan(false) - cardsWidget.getFirstBackupCardView().alpha = 1f - cardsWidget.getSecondBackupCardView().animate().alpha(1f).duration = 400 + 1 -> { + tvHeader.text = getText(R.string.onboarding_title_one_backup_card) + tvBody.text = getText(R.string.onboarding_subtitle_one_backup_card) + } + 2 -> { + tvHeader.text = getText(R.string.onboarding_title_two_backup_cards) + tvBody.text = getText(R.string.onboarding_subtitle_two_backup_cards) + } + else -> {} } } + + animator.showAddBackupCards(state, state.backupCardsNumber) + } + + private fun prepareBackupView() = with(binding) { + toolbar.title = getText(R.string.onboarding_navbar_title_creating_backup) + + tvHeader.show() + tvBody.show() + viewPagerBackupInfo.hide() + tabLayoutBackupInfo.hide() + + imvFirstBackupCard.show() + imvSecondBackupCard.show() } private fun showSetAccessCode() { @@ -323,13 +337,8 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), private fun showWritePrimaryCard(state: BackupState, isSaltPay: Boolean) = with(binding) { accessCodeDialog?.dismiss() + reInitCardsWidget(state.backupCardsNumber) prepareViewForFinalizeStep() - cardsWidget.getSecondBackupCardView().show(state.backupCardsNumber == 2) - - cardsWidget.toLeapfrog { - cardsWidget.getFirstBackupCardView().alpha = 0.6f - cardsWidget.getSecondBackupCardView().alpha = 0.2f - } val cardIdFormatter = CardIdFormatter(CardIdDisplayFormat.LastMasked(4)) if (isSaltPay) { @@ -344,52 +353,33 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), ) layoutButtonsCommon.btnMainAction.text = getText(R.string.onboarding_button_backup_origin) } - layoutButtonsCommon.btnMainAction.setOnClickListener { store.dispatch(BackupAction.WritePrimaryCard) } + animator.showWritePrimaryCard(state) } private fun prepareViewForFinalizeStep() = with(binding) { - layoutButtonsAddCards.root.hide() - layoutButtonsCommon.root.show() - - imvFirstBackupCard.show() - imvSecondBackupCard.show() - toolbar.title = getText(R.string.onboarding_button_finalize_backup) - imvCardBackground.hide() - - cardsWidget.toLeapfrog() - + layoutButtonsAddCards.root.hide() + layoutButtonsCommon.root.show() layoutButtonsCommon.btnAlternativeAction.hide() + + imvCardBackground.hide() + imvFirstBackupCard.show() + imvSecondBackupCard.show() } private fun showWriteBackupCard(state: BackupState, isSaltPay: Boolean) = with(binding) { prepareViewForFinalizeStep() - cardsWidget.getSecondBackupCardView().show(state.backupCardsNumber == 2) - val cardNumber = (state.backupStep as? BackupStep.WriteBackupCard)?.cardNumber ?: 1 - when (cardNumber) { - 1 -> cardsWidget.leapfrogWidget.leap { - cardsWidget.getOriginCardView().alpha = 0.4f - cardsWidget.getFirstBackupCardView().alpha = 0.2f - cardsWidget.getSecondBackupCardView().alpha = 1f - } - 2 -> cardsWidget.leapfrogWidget.leap { - cardsWidget.getOriginCardView().alpha = 0.4f - cardsWidget.getFirstBackupCardView().alpha = 0.2f - cardsWidget.getSecondBackupCardView().alpha = 1f - } - } - - val cardIdFormatter = CardIdFormatter(CardIdDisplayFormat.LastMasked(4)) 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) } else { + val cardIdFormatter = CardIdFormatter(CardIdDisplayFormat.LastMasked(4)) tvHeader.text = getString(R.string.onboarding_title_backup_card_format, cardNumber) tvBody.text = getString( R.string.onboarding_subtitle_scan_backup_card_format, @@ -403,6 +393,8 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), layoutButtonsCommon.btnMainAction.setOnClickListener { store.dispatch(BackupAction.WriteBackupCard(cardNumber)) } + + animator.showWriteBackupCard(state, cardNumber) } private fun showSuccess() = with(binding) { @@ -423,16 +415,14 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), store.dispatch(OnboardingWalletAction.FinishOnboarding) } - cardsWidget.leapfrogWidget.fold { + animator.showSuccess { flCardsContainer.hide() imvCardBackground.hide() vConfetti.lavConfetti.show() vConfetti.lavConfetti.playAnimation() imvSuccess.alpha = 0f imvSuccess.show() - - imvSuccess.animate() - ?.alpha(1f)?.duration = 400 + imvSuccess.animate()?.alpha(1f)?.duration = 400 } } @@ -450,6 +440,7 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), fun newState(state: OnboardingWalletState) { handleCardArtworks(state) + when (state.step) { OnboardingWalletStep.SaltPay -> setSaltPayStep(state.onboardingSaltPayState) OnboardingWalletStep.Backup -> { @@ -489,24 +480,20 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), //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 + // imvSecondBackupCard.alpha = 0f } fun showAddBackupCards(state: BackupState) = with(walletFragment.binding) { - walletFragment.cardsWidget.getSecondBackupCardView().alpha = 0f when (state.backupCardsNumber) { 0 -> { tvHeader.text = walletFragment.getText(R.string.onboarding_saltpay_title_no_backup_card) tvBody.text = walletFragment.getText(R.string.onboarding_saltpay_subtitle_no_backup_cards) - walletFragment.cardsWidget.getFirstBackupCardView().alpha = 0.6f - walletFragment.cardsWidget.toFan() } 1 -> { tvHeader.text = walletFragment.getText(R.string.onboarding_saltpay_title_one_backup_card) tvBody.text = walletFragment.getText(R.string.onboarding_saltpay_subtitle_one_backup_card) - walletFragment.cardsWidget.toFan(false) - walletFragment.cardsWidget.getFirstBackupCardView().animate().alpha(1f).duration = 400 } + else -> {} } } @@ -614,7 +601,6 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), } private fun showOnlyView(view: View, onShowListener: (() -> Unit)? = null) { - // onboardingSaltpayContainer.beginDelayedTransition() walletFragment.bindingSaltPay.onboardingSaltpayContainer.children .filter { it.id != view.id } .forEach { it.hide() } @@ -639,4 +625,10 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), } } } -} \ No newline at end of file + + private fun getDeviceScaleFactor(): Float { + val typedValue = TypedValue() + resources.getValue(R.dimen.device_scale_factor_for_twins_welcome, typedValue, true) + return typedValue.float + } +} diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/WalletCardsWidget.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/WalletCardsWidget.kt index 833c3dfb81..7d4831c434 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/WalletCardsWidget.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/WalletCardsWidget.kt @@ -16,87 +16,100 @@ class WalletCardsWidget( val getTopOfAnchorViewForActivateState: () -> Float, ) { - val animDuration: Long = 400 + private val animDuration: Long = 400 var currentState: WidgetState? = null + private set - fun toWelcome(animate: Boolean = true, onEnd: () -> Unit = {}) { + fun toWelcome( + animate: Boolean = true, + onEnd: () -> Unit = {}, + ) { if (currentState == WidgetState.WELCOME) return currentState = WidgetState.WELCOME - val animator = createAnimator(animate, onEnd) - animator.playTogether( - createAnimator(BackupCardType.ORIGIN, createWelcomeProperties(BackupCardType.ORIGIN)), - createAnimator(BackupCardType.FIRST_BACKUP, createWelcomeProperties(BackupCardType.FIRST_BACKUP)), - createAnimator(BackupCardType.SECOND_BACKUP, createWelcomeProperties(BackupCardType.SECOND_BACKUP)), - ) + val animator = createAnimator(animate, onEnd = onEnd) + animator.playTogether(createAnimators(::createWelcomeProperties)) leapfrogWidget.fold { animator.start() } } - fun toFolded(animate: Boolean = true, onEnd: () -> Unit = {}) { + fun toFolded( + animate: Boolean = true, + duration: Long = animDuration, + onEnd: () -> Unit = {}, + ) { if (currentState == WidgetState.FOLDED) return currentState = WidgetState.FOLDED - val animator = createAnimator(animate, onEnd) - animator.playTogether( - createAnimator(BackupCardType.ORIGIN, createLeapfrogProperties(BackupCardType.ORIGIN)), - createAnimator(BackupCardType.FIRST_BACKUP, createLeapfrogProperties(BackupCardType.FIRST_BACKUP)), - createAnimator(BackupCardType.SECOND_BACKUP, createLeapfrogProperties(BackupCardType.SECOND_BACKUP)), - ) - leapfrogWidget.fold(animate) { animator.start() } + val animator = createAnimator(animate, duration) { + leapfrogWidget.fold(animate, onEnd) + } + animator.playTogether(createAnimators(::createLeapfrogProperties)) + animator.start() } - fun toFan(animate: Boolean = true, onEnd: () -> Unit = {}) { + fun toFan( + animate: Boolean = true, + onEnd: () -> Unit = {}, + ) { if (currentState == WidgetState.FAN) return currentState = WidgetState.FAN - val animator = createAnimator(animate, onEnd) - animator.playTogether( - createAnimator(BackupCardType.ORIGIN, createFanProperties(BackupCardType.ORIGIN)), - createAnimator(BackupCardType.FIRST_BACKUP, createFanProperties(BackupCardType.FIRST_BACKUP)), - createAnimator(BackupCardType.SECOND_BACKUP, createFanProperties(BackupCardType.SECOND_BACKUP)), - ) + val animator = createAnimator(animate, onEnd = onEnd) + animator.playTogether(createAnimators(::createFanProperties)) leapfrogWidget.fold { animator.start() } } - fun toLeapfrog(animate: Boolean = true, onEnd: () -> Unit = {}) { + fun toLeapfrog( + animate: Boolean = true, + onEndFold: () -> Unit = {}, + onEnd: () -> Unit = {}, + ) { + if (currentState == WidgetState.LEAPFROG) return + currentState = WidgetState.LEAPFROG - val animator = createAnimator(animate, onEnd) - animator.playTogether( - createAnimator(BackupCardType.ORIGIN, createLeapfrogProperties(BackupCardType.ORIGIN)), - createAnimator(BackupCardType.FIRST_BACKUP, createLeapfrogProperties(BackupCardType.FIRST_BACKUP)), - createAnimator(BackupCardType.SECOND_BACKUP, createLeapfrogProperties(BackupCardType.SECOND_BACKUP)), - ) - leapfrogWidget.fold { - animator.doOnEnd { + val onAnimatorEnd = { + leapfrogWidget.fold(animate) { leapfrogWidget.initViews() - leapfrogWidget.unfold() + onEndFold() + leapfrogWidget.unfold(animate, onEnd) } - animator.start() } + val animator = createAnimator(animate, onEnd = onAnimatorEnd) + animator.playTogether(createAnimators(::createLeapfrogProperties)) + animator.start() } - private fun createAnimator(animate: Boolean, onEnd: () -> Unit): AnimatorSet { + private fun createAnimators(propertyFactory: (BackupCardType) -> CardProperties): List { + return getCardTypesTypes().map { createCardAnimator(it, propertyFactory(it)) } + } + + private fun getCardTypesTypes(): List = when (leapfrogWidget.getViewsCount()) { + 2 -> listOf(BackupCardType.ORIGIN, BackupCardType.FIRST_BACKUP) + 3 -> listOf(BackupCardType.ORIGIN, BackupCardType.FIRST_BACKUP, BackupCardType.SECOND_BACKUP) + else -> throw UnsupportedOperationException() + } + + private fun createAnimator(animate: Boolean, duration: Long = animDuration, onEnd: () -> Unit): AnimatorSet { return AnimatorSet().apply { - duration = if (animate) animDuration else 0 + this.duration = if (animate) duration else 0 doOnEnd { onEnd() } } } - private fun createAnimator( + private fun createCardAnimator( cardType: BackupCardType, properties: CardProperties, ): ObjectAnimator { - val view = getLeapViewByCardNumber(cardType).view - val animator = - ObjectAnimator.ofPropertyValuesHolder( - view, - *properties.createValuesHolders().toTypedArray(), - ) + val view = getLeapViewByCard(cardType).view + val animator = ObjectAnimator.ofPropertyValuesHolder( + view, + *properties.createValuesHolders().toTypedArray(), + ) view.elevation = properties.elevation return animator } @@ -181,7 +194,7 @@ class WalletCardsWidget( private fun createActivateProperties(cardType: BackupCardType): CardProperties { val topOfAnchorView = getTopOfAnchorViewForActivateState() - val twinProperties = CardProperties.from(getLeapViewByCardNumber(cardType).state) + val twinProperties = CardProperties.from(getLeapViewByCard(cardType).state) return twinProperties.copy( xTranslation = twinProperties.xTranslation, yTranslation = twinProperties.yTranslation - topOfAnchorView, @@ -190,24 +203,40 @@ class WalletCardsWidget( ) } - private fun getLeapViewByCardNumber(cardType: BackupCardType): LeapView { - return when (cardType) { - BackupCardType.ORIGIN -> leapfrogWidget.getViewByPosition(0) - BackupCardType.FIRST_BACKUP -> leapfrogWidget.getViewByPosition(1) - BackupCardType.SECOND_BACKUP -> leapfrogWidget.getViewByPosition(2) - } - } - + @Throws(UnsupportedOperationException::class) fun getOriginCardView(): ImageView { - return getLeapViewByCardNumber(BackupCardType.ORIGIN).view as ImageView + return getLeapViewByCard(BackupCardType.ORIGIN).view as ImageView } + @Throws(UnsupportedOperationException::class) fun getFirstBackupCardView(): ImageView { - return getLeapViewByCardNumber(BackupCardType.FIRST_BACKUP).view as ImageView + return getLeapViewByCard(BackupCardType.FIRST_BACKUP).view as ImageView } + @Throws(UnsupportedOperationException::class) fun getSecondBackupCardView(): ImageView { - return getLeapViewByCardNumber(BackupCardType.SECOND_BACKUP).view as ImageView + return getLeapViewByCard(BackupCardType.SECOND_BACKUP).view as ImageView + } + + @Throws(UnsupportedOperationException::class) + private fun getLeapViewByCard(cardType: BackupCardType): LeapView { + fun getByIndex(index: Int): LeapView { + return leapfrogWidget.getViewByPosition(leapfrogWidget.getViewPositionByIndex(index)) + } + + return when (leapfrogWidget.getViewsCount()) { + 2 -> when (cardType) { + BackupCardType.ORIGIN -> getByIndex(1) + BackupCardType.FIRST_BACKUP -> getByIndex(0) + BackupCardType.SECOND_BACKUP -> throw UnsupportedOperationException() + } + 3 -> when (cardType) { + BackupCardType.ORIGIN -> getByIndex(2) + BackupCardType.FIRST_BACKUP -> getByIndex(1) + BackupCardType.SECOND_BACKUP -> getByIndex(0) + } + else -> throw UnsupportedOperationException() + } } enum class WidgetState { WELCOME, FOLDED, FAN, LEAPFROG } diff --git a/app/src/main/res/layout/view_onboarding_card.xml b/app/src/main/res/layout/view_onboarding_card.xml new file mode 100644 index 0000000000..e3086ff628 --- /dev/null +++ b/app/src/main/res/layout/view_onboarding_card.xml @@ -0,0 +1,10 @@ + + \ No newline at end of file From 4df6bba5229beede0684370b1e0a82b6c8911be8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 20 Oct 2022 10:16:40 +0500 Subject: [PATCH 081/113] Updated on 2026-08-14 --- .../tangem/tap/features/tokens/ui/AddTokensFragment.kt | 10 +++++++--- .../tap/features/wallet/ui/WalletDetailsFragment.kt | 2 +- .../tangem/tap/features/wallet/ui/WalletFragment.kt | 4 ++-- app/src/main/res/layout/fragment_add_tokens.xml | 2 +- app/src/main/res/layout/fragment_wallet.xml | 2 +- app/src/main/res/layout/fragment_wallet_details.xml | 2 +- .../{popular_tokens.xml => menu_popular_tokens.xml} | 0 app/src/main/res/menu/{wallet.xml => menu_wallet.xml} | 0 .../{wallet_details.xml => menu_wallet_details.xml} | 0 9 files changed, 13 insertions(+), 9 deletions(-) rename app/src/main/res/menu/{popular_tokens.xml => menu_popular_tokens.xml} (100%) rename app/src/main/res/menu/{wallet.xml => menu_wallet.xml} (100%) rename app/src/main/res/menu/{wallet_details.xml => menu_wallet_details.xml} (100%) diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt index 8abbc33a2b..9a9052115c 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt @@ -30,10 +30,14 @@ import com.tangem.tap.store import com.tangem.wallet.R import com.tangem.wallet.databinding.FragmentAddTokensBinding import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import org.rekotlin.StoreSubscriber - class AddTokensFragment : Fragment(R.layout.fragment_add_tokens), StoreSubscriber { @@ -123,7 +127,7 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens), } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - inflater.inflate(R.menu.popular_tokens, menu) + inflater.inflate(R.menu.menu_popular_tokens, menu) val addCustomTokenAllowed = store.state.tokensState.allowToAdd menu.findItem(R.id.menu_navigate_add_custom_token).isVisible = addCustomTokenAllowed diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt index db9173e486..a7304c2064 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt @@ -345,7 +345,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - inflater.inflate(R.menu.wallet_details, menu) + inflater.inflate(R.menu.menu_wallet_details, menu) } private fun TextView.setWarningStatus(mainMessage: Int, error: String? = null) { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index 1f7d881721..87a31563fa 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -139,7 +139,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber diff --git a/app/src/main/res/layout/fragment_wallet.xml b/app/src/main/res/layout/fragment_wallet.xml index 8c49e592c4..ab388fa0cd 100644 --- a/app/src/main/res/layout/fragment_wallet.xml +++ b/app/src/main/res/layout/fragment_wallet.xml @@ -21,7 +21,7 @@ android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" - app:menu="@menu/wallet" + app:menu="@menu/menu_wallet" app:navigationIcon="@drawable/ic_scan_card" app:navigationIconTint="@color/darkGray6" app:title="@string/wallet_title" diff --git a/app/src/main/res/layout/fragment_wallet_details.xml b/app/src/main/res/layout/fragment_wallet_details.xml index 04649efe6b..1467361b47 100644 --- a/app/src/main/res/layout/fragment_wallet_details.xml +++ b/app/src/main/res/layout/fragment_wallet_details.xml @@ -22,7 +22,7 @@ android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:theme="@style/ThemeOverlay.MyTheme.Toolbar" - app:menu="@menu/wallet_details" + app:menu="@menu/menu_wallet_details" app:navigationIcon="@drawable/ic_baseline_arrow_back_24" app:title=" " /> diff --git a/app/src/main/res/menu/popular_tokens.xml b/app/src/main/res/menu/menu_popular_tokens.xml similarity index 100% rename from app/src/main/res/menu/popular_tokens.xml rename to app/src/main/res/menu/menu_popular_tokens.xml diff --git a/app/src/main/res/menu/wallet.xml b/app/src/main/res/menu/menu_wallet.xml similarity index 100% rename from app/src/main/res/menu/wallet.xml rename to app/src/main/res/menu/menu_wallet.xml diff --git a/app/src/main/res/menu/wallet_details.xml b/app/src/main/res/menu/menu_wallet_details.xml similarity index 100% rename from app/src/main/res/menu/wallet_details.xml rename to app/src/main/res/menu/menu_wallet_details.xml From 15995b8fc3cfc6d609823d62c34732232e5c08d7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 20 Oct 2022 12:15:38 +0500 Subject: [PATCH 082/113] Updated on 2026-08-14 --- .../com/tangem/tap/features/BaseFragment.kt | 14 ++++++++++ .../onboarding/OnboardingMenuProvider.kt | 27 +++++++++++++++++++ .../products/BaseOnboardingFragment.kt | 11 ++++++++ .../wallet/ui/OnboardingWalletFragment.kt | 14 +++++----- .../res/layout/fragment_onboarding_main.xml | 2 +- .../res/layout/fragment_onboarding_wallet.xml | 2 +- app/src/main/res/menu/menu_onboarding.xml | 8 ++++++ app/src/main/res/values-de/strings_final.xml | 1 + app/src/main/res/values-fr/strings_final.xml | 1 + app/src/main/res/values-it/strings_final.xml | 1 + app/src/main/res/values-ru/strings_final.xml | 1 + app/src/main/res/values/strings_final.xml | 1 + 12 files changed, 75 insertions(+), 8 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/OnboardingMenuProvider.kt create mode 100644 app/src/main/res/menu/menu_onboarding.xml diff --git a/app/src/main/java/com/tangem/tap/features/BaseFragment.kt b/app/src/main/java/com/tangem/tap/features/BaseFragment.kt index 8b4d0d3f70..f0e6016021 100644 --- a/app/src/main/java/com/tangem/tap/features/BaseFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/BaseFragment.kt @@ -7,7 +7,10 @@ import android.view.View import android.view.ViewGroup import androidx.activity.OnBackPressedCallback import androidx.appcompat.widget.Toolbar +import androidx.core.view.MenuHost +import androidx.core.view.MenuProvider import androidx.fragment.app.Fragment +import androidx.lifecycle.Lifecycle import androidx.transition.TransitionInflater import com.google.android.material.snackbar.Snackbar import com.tangem.common.extensions.VoidCallback @@ -42,6 +45,17 @@ abstract class BaseFragment(layoutId: Int) : Fragment(layoutId), FragmentOnBackP store.dispatch(NavigationAction.PopBackTo()) } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + loadToolbarMenu()?.let { + val menuHost = requireActivity() as? MenuHost ?: return + menuHost.addMenuProvider(it, viewLifecycleOwner, Lifecycle.State.RESUMED) + } + } + + protected open fun loadToolbarMenu():MenuProvider? = null + fun showRetrySnackbar(message: String, action: VoidCallback) { val snackbar = Snackbar.make(mainView, message, Snackbar.LENGTH_INDEFINITE) snackbar.setAction(getString(R.string.common_retry)) { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingMenuProvider.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingMenuProvider.kt new file mode 100644 index 0000000000..2fff1d0fab --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingMenuProvider.kt @@ -0,0 +1,27 @@ +package com.tangem.tap.features.onboarding + +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import androidx.core.view.MenuProvider +import com.tangem.tap.common.feedback.SupportInfo +import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.store +import com.tangem.wallet.R + +/** +[REDACTED_AUTHOR] + */ +class OnboardingMenuProvider : MenuProvider { + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { + menuInflater.inflate(R.menu.menu_onboarding, menu) + } + + override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) { + R.id.menu_item_chat_support -> { + store.dispatch(GlobalAction.OpenChat(SupportInfo())) + true + } + else -> false + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/BaseOnboardingFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/BaseOnboardingFragment.kt index f29444abc5..032cce60d3 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/BaseOnboardingFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/BaseOnboardingFragment.kt @@ -1,9 +1,14 @@ package com.tangem.tap.features.onboarding.products +import android.os.Bundle +import android.view.View +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.MenuProvider import by.kirich1409.viewbindingdelegate.viewBinding import com.tangem.tap.common.extensions.show import com.tangem.tap.common.transitions.HomeToOnboardingTransition import com.tangem.tap.features.BaseStoreFragment +import com.tangem.tap.features.onboarding.OnboardingMenuProvider import com.tangem.wallet.R import com.tangem.wallet.databinding.FragmentOnboardingMainBinding import com.tangem.wallet.databinding.ViewOnboardingProgressBinding @@ -22,6 +27,12 @@ abstract class BaseOnboardingFragment : BaseStoreFragment(R.layout.fragment_o exitTransition = HomeToOnboardingTransition() } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + (activity as? AppCompatActivity)?.setSupportActionBar(binding.toolbar) + } + + override fun loadToolbarMenu(): MenuProvider? = OnboardingMenuProvider() protected fun showConfetti(show: Boolean) = with(binding.vConfetti) { lavConfetti.show(show) 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 2b22f898d7..6f5c3fff03 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 @@ -9,8 +9,8 @@ import android.widget.FrameLayout import android.widget.ImageView import android.widget.ProgressBar import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.MenuProvider import androidx.core.view.children -import androidx.fragment.app.Fragment import androidx.transition.TransitionInflater import androidx.transition.TransitionManager import by.kirich1409.viewbindingdelegate.viewBinding @@ -39,8 +39,10 @@ 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.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 @@ -61,7 +63,7 @@ import com.tangem.wallet.databinding.LayoutOnboardingSaltpayBinding import com.tangem.wallet.databinding.ViewOnboardingProgressBinding import org.rekotlin.StoreSubscriber -class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), +class OnboardingWalletFragment : BaseFragment(R.layout.fragment_onboarding_wallet), StoreSubscriber, FragmentOnBackPressedHandler { private val pbBinding: ViewOnboardingProgressBinding by viewBinding(ViewOnboardingProgressBinding::bind) @@ -75,10 +77,7 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), private lateinit var animator: BackupAnimator - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setHasOptionsMenu(true) - + override fun configureTransitions() { val inflater = TransitionInflater.from(requireContext()) enterTransition = inflater.inflateTransition(R.transition.fade) exitTransition = inflater.inflateTransition(R.transition.fade) @@ -108,6 +107,8 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), store.dispatch(OnboardingWalletAction.LoadArtwork) } + override fun loadToolbarMenu(): MenuProvider? = OnboardingMenuProvider() + private fun reInitCardsWidget(backupCardsCounts: Int) = with(binding) { val viewBackupCount = flCardsContainer.childCount - 1 if (viewBackupCount <= 0) return@with @@ -139,6 +140,7 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), } } + override fun onStart() { super.onStart() store.subscribe(this) { state -> diff --git a/app/src/main/res/layout/fragment_onboarding_main.xml b/app/src/main/res/layout/fragment_onboarding_main.xml index faa2c0c829..97d770d2a3 100644 --- a/app/src/main/res/layout/fragment_onboarding_main.xml +++ b/app/src/main/res/layout/fragment_onboarding_main.xml @@ -12,7 +12,7 @@ diff --git a/app/src/main/res/menu/menu_onboarding.xml b/app/src/main/res/menu/menu_onboarding.xml new file mode 100644 index 0000000000..bc09d08fae --- /dev/null +++ b/app/src/main/res/menu/menu_onboarding.xml @@ -0,0 +1,8 @@ + + + + \ 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 6c801e756c..0b1aa41913 100644 --- a/app/src/main/res/values-de/strings_final.xml +++ b/app/src/main/res/values-de/strings_final.xml @@ -141,4 +141,5 @@ Prepare the SaltPay card Tap the SaltPay card Tap the Tangem card + Support \ 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 6c801e756c..0b1aa41913 100644 --- a/app/src/main/res/values-fr/strings_final.xml +++ b/app/src/main/res/values-fr/strings_final.xml @@ -141,4 +141,5 @@ Prepare the SaltPay card Tap the SaltPay card Tap the Tangem card + Support \ 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 6c801e756c..0b1aa41913 100644 --- a/app/src/main/res/values-it/strings_final.xml +++ b/app/src/main/res/values-it/strings_final.xml @@ -141,4 +141,5 @@ Prepare the SaltPay card Tap the SaltPay card Tap the Tangem card + Support \ 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 add278e752..8688a7b14c 100644 --- a/app/src/main/res/values-ru/strings_final.xml +++ b/app/src/main/res/values-ru/strings_final.xml @@ -139,4 +139,5 @@ Приготовьте SaltPay карту Приложите SaltPay карту Приложите Tangem карту + Чат diff --git a/app/src/main/res/values/strings_final.xml b/app/src/main/res/values/strings_final.xml index 5a47c7ed20..8da137a723 100644 --- a/app/src/main/res/values/strings_final.xml +++ b/app/src/main/res/values/strings_final.xml @@ -141,4 +141,5 @@ Prepare the SaltPay card Tap the SaltPay card Tap the Tangem card + Support From 1336f0dc647ec520644d9bc46c381e97678cd117 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 20 Oct 2022 19:53:59 +0500 Subject: [PATCH 083/113] Updated on 2026-08-14 --- .../java/com/tangem/tap/common/TestActions.kt | 2 +- .../onboarding/OnboardingSaltPayHelper.kt | 10 +++ .../redux/reducers/OnWalletLoadedReducer.kt | 2 + .../wallet/ui/WalletDetailsFragment.kt | 4 +- .../ui/test/TestWalletDetailsActions.kt | 80 ++++++++++++++++++- 5 files changed, 94 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/TestActions.kt b/app/src/main/java/com/tangem/tap/common/TestActions.kt index d5939dae64..5eb1021886 100644 --- a/app/src/main/java/com/tangem/tap/common/TestActions.kt +++ b/app/src/main/java/com/tangem/tap/common/TestActions.kt @@ -27,8 +27,8 @@ object TestActions { * @param isTestView - true must be used if you want to show or hide your view depends on BuildConfig */ fun initFor(view: View, actions: List, isTestView: Boolean = false) { - if (isTestView) view.show(BuildConfig.TEST_ACTION_ENABLED) if (!BuildConfig.TEST_ACTION_ENABLED) return + if (isTestView) view.show(BuildConfig.TEST_ACTION_ENABLED) view.setOnClickListener { store.dispatchDialogShow(AppDialog.TestActionsDialog(actions)) 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 d874af4acc..2ee4ef00de 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 @@ -41,5 +41,15 @@ class OnboardingSaltPayHelper { Result.Failure(error) } } + + suspend fun testProceedToMainScreen(scanResponse: ScanResponse): Result { + return try { + Result.Success(false) + } catch (ex: Exception) { + Result.Failure(ex) + } catch (error: SaltPayRegistrationError){ + Result.Failure(error) + } + } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt index 7d124f2a41..9f2f76e6fd 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt @@ -116,6 +116,8 @@ class OnWalletLoadedReducer { private fun onSingleWalletLoaded(wallet: Wallet, walletState: WalletState): WalletState { if (wallet.blockchain != walletState.primaryBlockchain) return walletState + // val ratesRespository = store.state.globalState.tapWalletManager.ratesRepository + // val tokenCurrency = val fiatCurrencyName = store.state.globalState.appCurrency.code val token = wallet.getFirstToken() val tokenData = if (token != null) { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt index a7304c2064..281d4b3938 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt @@ -42,7 +42,7 @@ import com.tangem.tap.features.wallet.redux.WalletState.Companion.UNKNOWN_AMOUNT import com.tangem.tap.features.wallet.ui.adapters.PendingTransactionsAdapter import com.tangem.tap.features.wallet.ui.adapters.WalletDetailWarningMessagesAdapter import com.tangem.tap.features.wallet.ui.images.load -import com.tangem.tap.features.wallet.ui.test.TestWalletDetails +import com.tangem.tap.features.wallet.ui.test.TestWallet import com.tangem.tap.store import com.tangem.wallet.R import com.tangem.wallet.databinding.FragmentWalletDetailsBinding @@ -125,7 +125,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), view?.findViewById(R.id.l_balance)?.let { view -> TestActions.initFor( view = view, - actions = TestWalletDetails.solanaRentExemptWarning() + actions = TestWallet.solanaRentExemptWarning() ) } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/test/TestWalletDetailsActions.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/test/TestWalletDetailsActions.kt index 6b99ef8be9..4f384d33f7 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/test/TestWalletDetailsActions.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/test/TestWalletDetailsActions.kt @@ -1,9 +1,13 @@ package com.tangem.tap.features.wallet.ui.test import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.common.AmountType +import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.WalletManager +import com.tangem.common.extensions.guard import com.tangem.tap.common.TestAction import com.tangem.tap.common.TestActions +import com.tangem.tap.common.extensions.dispatchDebugErrorNotification import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.store @@ -13,7 +17,7 @@ import kotlin.random.Random /** [REDACTED_AUTHOR] */ -class TestWalletDetails { +class TestWallet { companion object { fun solanaRentExemptWarning(): List { val checker = SolanaRentWarningActionEmitter() @@ -26,6 +30,80 @@ class TestWalletDetails { "BALANCE = 0.00089089" to { checker.setMoreThanRentExemptByOne() }, ) } + + fun getBlockchainBalanceActions(blockchainNetwork: BlockchainNetwork): List { + return getBlockchainBalanceActions( + getWalletManager(blockchainNetwork), + blockchainNetwork.blockchain.decimals(), + ) + } + + fun getTokenBalanceAction(blockchainNetwork: BlockchainNetwork, token: Token): List { + return getTokenBalanceAction(getWalletManager(blockchainNetwork), token) + } + + fun getBlockchainBalanceActions(walletManager: WalletManager?, decimals: Int): List { + val minValue = BigDecimal.ONE.movePointLeft(decimals) + val averageValue = BigDecimal.ONE.movePointRight(decimals) + .divide(BigDecimal(2)).movePointLeft(decimals) + val maxValue = BigDecimal(2).pow(32) + + return listOf( + "0.0" to { setBalance(walletManager, BigDecimal.ZERO) }, + "Min value" to { setBalance(walletManager, minValue) }, + "Average value" to { setBalance(walletManager, averageValue) }, + "Max value" to { setBalance(walletManager, maxValue) }, + "1234567890123.01234567890123" to { setBalance(walletManager, BigDecimal.ZERO) }, + ) + } + + fun getTokenBalanceAction(walletManager: WalletManager?, token: Token): List { + val minValue = BigDecimal.ONE.movePointLeft(token.decimals) + val averageValue = BigDecimal.ONE.movePointRight(token.decimals) + .divide(BigDecimal(2)).movePointLeft(token.decimals) + val maxValue = BigDecimal(2).pow(32) + + return listOf( + "0.0" to { setTokenBalance(walletManager, BigDecimal.ZERO, token) }, + "Min value" to { setTokenBalance(walletManager, minValue, token) }, + "Average value" to { setTokenBalance(walletManager, averageValue, token) }, + "Max value" to { setTokenBalance(walletManager, maxValue, token) }, + "1234567890123.01234567890123" to { setTokenBalance(walletManager, BigDecimal.ZERO, token) }, + ) + } + + fun setBalance(walletManager: WalletManager?, value: BigDecimal) { + val manager = walletManager.guard { + store.dispatchDebugErrorNotification("WalletManager not found") + return + } + val amount = manager.wallet.amounts[AmountType.Coin] ?: Amount(manager.wallet.blockchain) + setBalance(manager, amount, value) + } + + private fun setTokenBalance(walletManager: WalletManager?, value: BigDecimal, token: Token? = null) { + val manager = walletManager.guard { + store.dispatchDebugErrorNotification("WalletManager not found") + return + } + val token = token.guard { + store.dispatchDebugErrorNotification("Token not found") + return + } + + val amount = manager.wallet.amounts[AmountType.Token(token)] ?: Amount(token) + setBalance(manager, amount, value) + } + + private fun setBalance(walletManager: WalletManager, amount: Amount, value: BigDecimal) { + TestActions.testAmountInjectionForWalletManagerEnabled = true + walletManager.wallet.setAmount(amount.copy(value = value)) + store.dispatch(WalletAction.LoadData) + } + + private fun getWalletManager(blockchainNetwork: BlockchainNetwork): WalletManager? { + return store.state.walletState.getWalletManager(blockchainNetwork) + } } } From 9b903748433a361a2abed0a1aaeaa091db54648a Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 20 Oct 2022 20:11:58 +0500 Subject: [PATCH 084/113] Updated on 2026-08-14 --- .../domain/tasks/product/ScanProductTask.kt | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt index 3bfef52562..c8bf29caca 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt @@ -18,6 +18,7 @@ import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TapWorkarounds.isExcluded import com.tangem.domain.common.TapWorkarounds.isNotSupportedInThatRelease import com.tangem.domain.common.TapWorkarounds.isSaltPay +import com.tangem.domain.common.TapWorkarounds.isTangemNote import com.tangem.domain.common.TapWorkarounds.isTangemTwins import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation import com.tangem.domain.common.TwinsHelper @@ -58,6 +59,7 @@ class ScanProductTask( } val commandProcessor = when { + card.isTangemNote -> ScanNoteProcessor() card.isTangemTwins -> ScanTwinProcessor() else -> ScanWalletProcessor(userTokensRepository, additionalBlockchainsToDerive) } @@ -88,6 +90,24 @@ class ScanProductTask( } } +private class ScanNoteProcessor : ProductCommandProcessor { + override fun proceed( + card: Card, + session: CardSession, + callback: (result: CompletionResult) -> Unit + ) { + callback( + CompletionResult.Success( + ScanResponse( + card = card, + productType = ProductType.Note, + walletData = session.environment.walletData, + ), + ), + ) + } +} + private class ScanWalletProcessor( private val userTokensRepository: UserTokensRepository?, private val additionalBlockchainsToDerive: Collection? = null, @@ -108,7 +128,6 @@ private class ScanWalletProcessor( callback: (result: CompletionResult) -> Unit, ) { if (card.wallets.isEmpty() || !card.isMultiwalletAllowed) { - // match for the saltPay card to startLinkingForBackupIfNeeded(card, session, callback) return } From 21d2d0d86b747234d803c5f2675f40336fbb6eff Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 20 Oct 2022 20:19:30 +0500 Subject: [PATCH 085/113] Updated on 2026-08-14 --- .../products/wallet/redux/OnboardingWalletMiddleware.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index 66c65bbf6d..745dee84fd 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -4,6 +4,7 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.common.CompletionResult import com.tangem.common.card.Card import com.tangem.common.extensions.ifNotNull +import com.tangem.domain.common.SaltPayWorkaround import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.extensions.withMainContext import com.tangem.operations.backup.BackupService @@ -336,9 +337,11 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) } } is BackupAction.ResumeFoundUnfinishedBackup -> { - // val isSaltPay = backupService.primaryBatchId?.let { SaltPayWorkaround.isVisaBatchId(it) } ?: false - // store.dispatch(GlobalAction.Onboarding.StartForUnfinishedBackup(isSaltPay)) - // store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet)) + val isSaltPay = backupService.primaryCardId?.slice(0..3)?.let { + SaltPayWorkaround.isVisaBatchId(it) + } ?: false + store.dispatch(GlobalAction.Onboarding.StartForUnfinishedBackup(isSaltPay)) + store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet)) } is BackupAction.DismissBackup -> { if (onboardingWalletState.isSaltPay) throw UnsupportedOperationException() From cee1bec47eb2d972e1965f681070d3e3ffd522e9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 20 Oct 2022 22:20:27 +0500 Subject: [PATCH 086/113] Updated on 2026-08-14 --- .idea/inspectionProfiles/Project_Default.xml | 6 +- .../products/note/OnboardingNoteFragment.kt | 56 +-- .../OnboardingOtherCardsFragment.kt | 56 ++- .../products/twins/ui/TwinsCardsFragment.kt | 357 +++++++++--------- .../res/layout/fragment_onboarding_main.xml | 26 +- .../res/layout/layout_onboarding_main.xml | 25 ++ 6 files changed, 246 insertions(+), 280 deletions(-) create mode 100644 app/src/main/res/layout/layout_onboarding_main.xml diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index ba5af7a4ce..c914631447 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -84,9 +84,9 @@ - - - + + + \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt index a81bc28e87..a54a27bd28 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt @@ -1,19 +1,14 @@ package com.tangem.tap.features.onboarding.products.note -import android.graphics.Bitmap -import android.graphics.drawable.BitmapDrawable import android.os.Bundle import android.view.View import android.view.animation.OvershootInterpolator -import android.widget.ImageView import androidx.annotation.LayoutRes import androidx.constraintlayout.widget.ConstraintSet import androidx.core.view.isVisible import androidx.transition.TransitionManager import coil.load import com.tangem.blockchain.common.Blockchain -import com.tangem.tangem_sdk_new.extensions.fadeIn -import com.tangem.tangem_sdk_new.extensions.fadeOut import com.tangem.tap.common.extensions.getDrawableCompat import com.tangem.tap.common.extensions.stripZeroPlainString import com.tangem.tap.common.redux.navigation.ShareElement @@ -33,23 +28,18 @@ import com.tangem.wallet.R class OnboardingNoteFragment : BaseOnboardingFragment() { private var previousStep: OnboardingNoteStep = OnboardingNoteStep.None + private val mainBinding by lazy { binding.vMain } private lateinit var btnRefreshBalanceWidget: RefreshBalanceWidget - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - postponeEnterTransition() - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) addBackPressHandler(this) - binding.onboardingTopContainer.imvFrontCard.transitionName = ShareElement.imvFrontCard - startPostponedEnterTransition() + mainBinding.onboardingTopContainer.imvFrontCard.transitionName = ShareElement.imvFrontCard binding.toolbar.setTitle(R.string.onboarding_title) - btnRefreshBalanceWidget = RefreshBalanceWidget(binding.onboardingTopContainer.onboardingWalletContainer) + btnRefreshBalanceWidget = RefreshBalanceWidget(mainBinding.onboardingTopContainer.onboardingWalletContainer) store.dispatch(OnboardingNoteAction.LoadCardArtwork) store.dispatch(OnboardingNoteAction.DetermineStepOfScreen) @@ -67,7 +57,7 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { override fun newState(state: OnboardingNoteState) { if (activity == null || view == null) return - binding.onboardingTopContainer.imvFrontCard.load(state.cardArtworkUrl) { + mainBinding.onboardingTopContainer.imvFrontCard.load(state.cardArtworkUrl) { placeholder(R.drawable.card_placeholder_black) error(R.drawable.card_placeholder_black) fallback(R.drawable.card_placeholder_black) @@ -90,7 +80,7 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { private fun setBalance(state: OnboardingNoteState) { if (state.walletBalance.currency.blockchain == Blockchain.Unknown) return - with (binding.onboardingTopContainer.onboardingTvBalance) { + with(mainBinding.onboardingTopContainer.onboardingTvBalance) { if (state.balanceCriticalError == null) { val balanceValue = state.walletBalance.value.stripZeroPlainString() tvBalanceValue.text = balanceValue @@ -102,7 +92,7 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { } } - private fun setupNoneState() = with(binding.onboardingActionContainer) { + private fun setupNoneState() = with(mainBinding.onboardingActionContainer) { btnMainAction.isVisible = false btnAlternativeAction.isVisible = false btnRefreshBalanceWidget.show(false) @@ -116,7 +106,7 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { } private fun setupCreateWalletState(state: OnboardingNoteState) = - with(binding.onboardingActionContainer) { + with(mainBinding.onboardingActionContainer) { btnMainAction.setText(R.string.onboarding_create_wallet_button_create_wallet) btnMainAction.setOnClickListener { store.dispatch(OnboardingNoteAction.CreateWallet) } btnAlternativeAction.setText(R.string.onboarding_button_what_does_it_mean) @@ -127,15 +117,15 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { tvHeader.setText(R.string.onboarding_create_wallet_header) tvBody.setText(R.string.onboarding_create_wallet_body) - binding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable( - requireContext().getDrawableCompat(R.drawable.shape_circle) + mainBinding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable( + requireContext().getDrawableCompat(R.drawable.shape_circle), ) updateConstraints(state.currentStep, R.layout.lp_onboarding_create_wallet) btnAlternativeAction.isVisible = false // temporary } - private fun setupTopUpWalletState(state: OnboardingNoteState) = with(binding.onboardingActionContainer) { + private fun setupTopUpWalletState(state: OnboardingNoteState) = with(mainBinding.onboardingActionContainer) { if (state.isBuyAllowed) { btnMainAction.setText(R.string.onboarding_top_up_button_but_crypto) btnMainAction.setOnClickListener { @@ -163,7 +153,7 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { state.walletBalance.amountToCreateAccount?.let { amount -> val tvBodyMessage = getString( R.string.onboarding_top_up_body_no_account_error, - amount, state.walletBalance.currency.currencySymbol + amount, state.walletBalance.currency.currencySymbol, ) tvBody.text = tvBodyMessage } @@ -175,12 +165,13 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { store.dispatch(OnboardingNoteAction.Balance.Update) } } - binding.onboardingTopContainer.imvCardBackground - .setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8)) + + val drawable = requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8) + mainBinding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable(drawable) updateConstraints(state.currentStep, R.layout.lp_onboarding_topup_wallet) } - private fun setupDoneState(state: OnboardingNoteState) = with(binding.onboardingActionContainer) { + private fun setupDoneState(state: OnboardingNoteState) = with(mainBinding.onboardingActionContainer) { btnMainAction.setText(R.string.onboarding_done_button_continue) btnMainAction.setOnClickListener { showConfetti(false) @@ -188,15 +179,15 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { } btnAlternativeAction.isVisible = false - btnAlternativeAction.setText("") + btnAlternativeAction.text = "" btnAlternativeAction.setOnClickListener { } btnRefreshBalanceWidget.mainView.setOnClickListener(null) tvHeader.setText(R.string.onboarding_done_header) tvBody.setText(R.string.onboarding_done_body) - binding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable( - requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8) + mainBinding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable( + requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8), ) updateConstraints(state.currentStep, R.layout.lp_onboarding_done_activation) } @@ -204,7 +195,7 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { private fun updateConstraints(currentStep: OnboardingNoteStep, @LayoutRes layoutId: Int) { if (this.previousStep == currentStep) return - with(binding.onboardingTopContainer) { + with(mainBinding.onboardingTopContainer) { val constraintSet = ConstraintSet() constraintSet.clone(requireContext(), layoutId) constraintSet.applyTo(onboardingWalletContainer) @@ -213,13 +204,4 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { TransitionManager.beginDelayedTransition(onboardingWalletContainer, transition) } } -} - -fun ImageView.swapToBitmapDrawable(bitmap: Bitmap?, duration: Long = 200) { - if (drawable is BitmapDrawable && (drawable as BitmapDrawable).bitmap == bitmap) return - - fadeOut(duration) { - setImageBitmap(bitmap) - fadeIn(duration) - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt index bb09b3d434..0ed23143da 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt @@ -24,17 +24,13 @@ import com.tangem.wallet.R */ class OnboardingOtherCardsFragment : BaseOnboardingFragment() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - postponeEnterTransition() - } + private val mainBinding by lazy { binding.vMain } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) addBackPressHandler(this) - binding.onboardingTopContainer.imvFrontCard.transitionName = ShareElement.imvFrontCard - startPostponedEnterTransition() + mainBinding.onboardingTopContainer.imvFrontCard.transitionName = ShareElement.imvFrontCard binding.toolbar.setTitle(R.string.onboarding_title) store.dispatch(OnboardingOtherCardsAction.LoadCardArtwork) @@ -54,7 +50,7 @@ class OnboardingOtherCardsFragment : BaseOnboardingFragment() { + private val mainBinding by lazy { binding.vMain } private var previousStep = TwinCardsStep.None private lateinit var twinsWidget: TwinsCardWidget @@ -47,7 +48,6 @@ class TwinsCardsFragment : BaseOnboardingFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - postponeEnterTransition() store.dispatch(TwinCardsAction.Init) } @@ -66,30 +66,30 @@ class TwinsCardsFragment : BaseOnboardingFragment() { super.onViewCreated(view, savedInstanceState) addBackPressHandler(this) - reconfigureLayoutForTwins(binding.onboardingTopContainer) + reconfigureLayoutForTwins(mainBinding.onboardingTopContainer) val typedValue = TypedValue() resources.getValue(R.dimen.device_scale_factor_for_twins_welcome, typedValue, true) val deviceScaleFactorForWelcomeState = typedValue.float twinsWidget = TwinsCardWidget( - LeapfrogWidget(binding.onboardingTopContainer.cardsContainer), - deviceScaleFactorForWelcomeState + LeapfrogWidget(mainBinding.onboardingTopContainer.cardsContainer), + deviceScaleFactorForWelcomeState, ) { 285f * deviceScaleFactorForWelcomeState } btnRefreshBalanceWidget = - RefreshBalanceWidget(binding.onboardingTopContainer.onboardingWalletContainer) + RefreshBalanceWidget(mainBinding.onboardingTopContainer.onboardingWalletContainer) binding.toolbar.title = getText(R.string.twins_recreate_toolbar) - binding.onboardingTopContainer.imvTwinFrontCard.load(Artwork.TWIN_CARD_1) { + mainBinding.onboardingTopContainer.imvTwinFrontCard.load(Artwork.TWIN_CARD_1) { placeholder(R.drawable.card_placeholder_black) error(R.drawable.card_placeholder_black) fallback(R.drawable.card_placeholder_black) } - binding.onboardingTopContainer.imvTwinBackCard.load(Artwork.TWIN_CARD_2) { + mainBinding.onboardingTopContainer.imvTwinBackCard.load(Artwork.TWIN_CARD_2) { placeholder(R.drawable.card_placeholder_white) error(R.drawable.card_placeholder_white) fallback(R.drawable.card_placeholder_white) @@ -143,7 +143,7 @@ class TwinsCardsFragment : BaseOnboardingFragment() { private fun setBalance(state: TwinCardsState) { if (state.walletBalance.currency.blockchain == Blockchain.Unknown) return - with(binding.onboardingTopContainer.onboardingTvBalance) { + with(mainBinding.onboardingTopContainer.onboardingTvBalance) { if (state.balanceCriticalError == null) { val balanceValue = state.walletBalance.value.stripZeroPlainString() tvBalanceValue.text = balanceValue @@ -167,26 +167,26 @@ class TwinsCardsFragment : BaseOnboardingFragment() { } private fun setupWelcomeState(state: TwinCardsState, mainAction: VoidCallback) = - with(binding.onboardingActionContainer) { - twinsWidget.toWelcome(false) { startPostponedEnterTransition() } + with(mainBinding.onboardingActionContainer) { + twinsWidget.toWelcome(false) - binding.onboardingTopContainer.onboardingTwinsWelcomeBg.root.show() + mainBinding.onboardingTopContainer.onboardingTwinsWelcomeBg.root.show() pbBinding.pbState.hide() tvHeader.setText(R.string.twins_onboarding_subtitle) tvBody.text = getString( R.string.twins_onboarding_description_format, - state.cardNumber?.pairIndexNumber() + state.cardNumber?.pairIndexNumber(), ) btnMainAction.setText(R.string.common_continue) btnMainAction.setOnClickListener { mainAction() } } - private fun setupWarningState(state: TwinCardsState) = with(binding.onboardingActionContainer) { - twinsWidget.toWelcome(false) { startPostponedEnterTransition() } + private fun setupWarningState(state: TwinCardsState) = with(mainBinding.onboardingActionContainer) { + twinsWidget.toWelcome(false) - binding.onboardingTopContainer.onboardingTwinsWelcomeBg.root.hide() + mainBinding.onboardingTopContainer.onboardingTwinsWelcomeBg.root.hide() pbBinding.pbState.hide() chbUnderstand.show() @@ -203,184 +203,175 @@ class TwinsCardsFragment : BaseOnboardingFragment() { } } - private fun setupCreateFirstWalletState(state: TwinCardsState) = - with(binding.onboardingActionContainer) { - binding.onboardingTopContainer.onboardingTwinsWelcomeBg.root.hide() - binding.onboardingTopContainer.onboardingTwinsWelcomeBg.bgCircleLarge.hide() - binding.onboardingTopContainer.onboardingTwinsWelcomeBg.bgCircleMedium.hide() - binding.onboardingTopContainer.onboardingTwinsWelcomeBg.bgCircleMin.hide() - chbUnderstand.hide() - pbBinding.pbState.show() + private fun setupCreateFirstWalletState(state: TwinCardsState) = with(mainBinding.onboardingActionContainer) { + mainBinding.onboardingTopContainer.onboardingTwinsWelcomeBg.root.hide() + mainBinding.onboardingTopContainer.onboardingTwinsWelcomeBg.bgCircleLarge.hide() + mainBinding.onboardingTopContainer.onboardingTwinsWelcomeBg.bgCircleMedium.hide() + mainBinding.onboardingTopContainer.onboardingTwinsWelcomeBg.bgCircleMin.hide() + chbUnderstand.hide() + pbBinding.pbState.show() - binding.onboardingTopContainer.onboardingWalletContainer.beginDelayedTransition() + mainBinding.onboardingTopContainer.onboardingWalletContainer.beginDelayedTransition() - when (previousStep) { - TwinCardsStep.None -> { - twinsWidget.toLeapfrog(false) { - when (state.cardNumber) { - TwinCardNumber.First -> startPostponedEnterTransition() - TwinCardNumber.Second -> { - switchToCard( - state.cardNumber, - false - ) { startPostponedEnterTransition() } - } - } - } - } - TwinCardsStep.Welcome, TwinCardsStep.Warning -> { - twinsWidget.toLeapfrog(onEnd = { switchToCard(state.cardNumber) }) - } - } - - val twinIndexNumber = state.cardNumber?.indexNumber() - tvHeader.text = getString(R.string.twins_recreate_title_format, twinIndexNumber) - tvBody.setText(R.string.onboarding_twins_interrupt_warning) - - btnMainAction.text = getString(R.string.twins_recreate_button_format, twinIndexNumber) - btnMainAction.setOnClickListener { - store.dispatch( - TwinCardsAction.Wallet.LaunchFirstStep( - Message(getString(R.string.twins_recreate_title_format, twinIndexNumber)), - AndroidAssetReader(requireContext()), - ) - ) - } - - btnAlternativeAction.isVisible = false - btnAlternativeAction.isClickable = false - } - - private fun setupCreateSecondWalletState(state: TwinCardsState) = - with(binding.onboardingActionContainer) { - switchToCard(state.cardNumber?.pairNumber()) - - val twinPairIndexNumber = state.cardNumber?.pairIndexNumber() - tvHeader.text = getString(R.string.twins_recreate_title_format, twinPairIndexNumber) - tvBody.setText(R.string.onboarding_twins_interrupt_warning) - - btnMainAction.text = - getString(R.string.twins_recreate_button_format, twinPairIndexNumber) - btnMainAction.setOnClickListener { - store.dispatch( - TwinCardsAction.Wallet.LaunchSecondStep( - Message( - getString( - R.string.twins_recreate_title_format, - twinPairIndexNumber - ) - ), - Message(getString(R.string.twins_recreate_title_preparing)), - Message(getString(R.string.twins_recreate_title_creating_wallet)), - ) - ) - } - } - - private fun setupCreateThirdWalletState(state: TwinCardsState) = - with(binding.onboardingActionContainer) { - switchToCard(state.cardNumber) - - val twinIndexNumber = state.cardNumber?.indexNumber() - tvHeader.text = getString(R.string.twins_recreate_title_format, twinIndexNumber) - tvBody.setText(R.string.onboarding_twins_interrupt_warning) - - btnMainAction.text = getString(R.string.twins_recreate_button_format, twinIndexNumber) - btnMainAction.setOnClickListener { - store.dispatch( - TwinCardsAction.Wallet.LaunchThirdStep( - Message(getString(R.string.twins_recreate_title_format, twinIndexNumber)) - ) - ) - } - } - - - private fun setupTopUpWalletState(state: TwinCardsState) = - with(binding.onboardingActionContainer) { - when (previousStep) { - TwinCardsStep.None -> { + when (previousStep) { + TwinCardsStep.None -> { + twinsWidget.toLeapfrog(false) { when (state.cardNumber) { - TwinCardNumber.First -> { - twinsWidget.leapfrogWidget.unfold(false) { - twinsWidget.toActivate(false) { - startPostponedEnterTransition() - } - } - } + TwinCardNumber.First -> {} TwinCardNumber.Second -> { - switchToCard(state.cardNumber, false) { - twinsWidget.toActivate(false) - startPostponedEnterTransition() - } + switchToCard( + state.cardNumber, + false, + ) } } } - TwinCardsStep.CreateThirdWallet -> { - twinsWidget.toActivate() - } } - - if (state.isBuyAllowed) { - btnMainAction.setText(R.string.onboarding_top_up_button_but_crypto) - btnMainAction.setOnClickListener { - store.dispatch(TwinCardsAction.TopUp) - } - - btnAlternativeAction.isVisible = true - btnAlternativeAction.setText(R.string.onboarding_top_up_button_show_wallet_address) - btnAlternativeAction.setOnClickListener { - store.dispatch(TwinCardsAction.ShowAddressInfoDialog) - } - } else { - btnMainAction.setText(R.string.onboarding_button_receive_crypto) - btnMainAction.setOnClickListener { - store.dispatch(TwinCardsAction.ShowAddressInfoDialog) - } - - btnAlternativeAction.isVisible = false + TwinCardsStep.Welcome, TwinCardsStep.Warning -> { + twinsWidget.toLeapfrog(onEnd = { switchToCard(state.cardNumber) }) } - - tvHeader.setText(R.string.onboarding_top_up_header) - tvBody.setText(R.string.onboarding_top_up_body) - - btnRefreshBalanceWidget.changeState(state.walletBalance.state) - if (btnRefreshBalanceWidget.isShowing != true) { - btnRefreshBalanceWidget.mainView.setOnClickListener { - store.dispatch(TwinCardsAction.Balance.Update) - } - } - binding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable( - requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8) - ) - updateConstraints(state.currentStep, R.layout.lp_onboarding_topup_wallet_twins) } - private fun setupDoneState(state: TwinCardsState) = - with(binding.onboardingActionContainer) { - btnMainAction.setText(R.string.onboarding_done_button_continue) + val twinIndexNumber = state.cardNumber?.indexNumber() + tvHeader.text = getString(R.string.twins_recreate_title_format, twinIndexNumber) + tvBody.setText(R.string.onboarding_twins_interrupt_warning) + + btnMainAction.text = getString(R.string.twins_recreate_button_format, twinIndexNumber) + btnMainAction.setOnClickListener { + store.dispatch( + TwinCardsAction.Wallet.LaunchFirstStep( + Message(getString(R.string.twins_recreate_title_format, twinIndexNumber)), + AndroidAssetReader(requireContext()), + ), + ) + } + + btnAlternativeAction.isVisible = false + btnAlternativeAction.isClickable = false + } + + private fun setupCreateSecondWalletState(state: TwinCardsState) = with(mainBinding.onboardingActionContainer) { + switchToCard(state.cardNumber?.pairNumber()) + + val twinPairIndexNumber = state.cardNumber?.pairIndexNumber() + tvHeader.text = getString(R.string.twins_recreate_title_format, twinPairIndexNumber) + tvBody.setText(R.string.onboarding_twins_interrupt_warning) + + btnMainAction.text = + getString(R.string.twins_recreate_button_format, twinPairIndexNumber) + btnMainAction.setOnClickListener { + store.dispatch( + TwinCardsAction.Wallet.LaunchSecondStep( + Message( + getString( + R.string.twins_recreate_title_format, + twinPairIndexNumber, + ), + ), + Message(getString(R.string.twins_recreate_title_preparing)), + Message(getString(R.string.twins_recreate_title_creating_wallet)), + ), + ) + } + } + + private fun setupCreateThirdWalletState(state: TwinCardsState) = with(mainBinding.onboardingActionContainer) { + switchToCard(state.cardNumber) + + val twinIndexNumber = state.cardNumber?.indexNumber() + tvHeader.text = getString(R.string.twins_recreate_title_format, twinIndexNumber) + tvBody.setText(R.string.onboarding_twins_interrupt_warning) + + btnMainAction.text = getString(R.string.twins_recreate_button_format, twinIndexNumber) + btnMainAction.setOnClickListener { + store.dispatch( + TwinCardsAction.Wallet.LaunchThirdStep( + Message(getString(R.string.twins_recreate_title_format, twinIndexNumber)), + ), + ) + } + } + + private fun setupTopUpWalletState(state: TwinCardsState) = with(mainBinding.onboardingActionContainer) { + when (previousStep) { + TwinCardsStep.None -> { + when (state.cardNumber) { + TwinCardNumber.First -> { + twinsWidget.leapfrogWidget.unfold(false) { + twinsWidget.toActivate(false) + } + } + TwinCardNumber.Second -> { + switchToCard(state.cardNumber, false) { + twinsWidget.toActivate(false) + } + } + } + } + TwinCardsStep.CreateThirdWallet -> { + twinsWidget.toActivate() + } + } + + if (state.isBuyAllowed) { + btnMainAction.setText(R.string.onboarding_top_up_button_but_crypto) btnMainAction.setOnClickListener { - store.dispatch(TwinCardsAction.Confetti.Hide) - store.dispatch(TwinCardsAction.Done) + store.dispatch(TwinCardsAction.TopUp) + } + + btnAlternativeAction.isVisible = true + btnAlternativeAction.setText(R.string.onboarding_top_up_button_show_wallet_address) + btnAlternativeAction.setOnClickListener { + store.dispatch(TwinCardsAction.ShowAddressInfoDialog) + } + } else { + btnMainAction.setText(R.string.onboarding_button_receive_crypto) + btnMainAction.setOnClickListener { + store.dispatch(TwinCardsAction.ShowAddressInfoDialog) } btnAlternativeAction.isVisible = false - btnAlternativeAction.isClickable = false - - tvHeader.setText(R.string.onboarding_done_header) - tvBody.setText(R.string.onboarding_done_body) - - val layout = when (state.mode) { - CreateTwinWalletMode.CreateWallet -> R.layout.lp_onboarding_done_activation_twins - CreateTwinWalletMode.RecreateWallet -> R.layout.lp_onboarding_done - } - updateConstraints(state.currentStep, layout) } + tvHeader.setText(R.string.onboarding_top_up_header) + tvBody.setText(R.string.onboarding_top_up_body) + + btnRefreshBalanceWidget.changeState(state.walletBalance.state) + if (btnRefreshBalanceWidget.isShowing != true) { + btnRefreshBalanceWidget.mainView.setOnClickListener { + store.dispatch(TwinCardsAction.Balance.Update) + } + } + mainBinding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable( + requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8), + ) + updateConstraints(state.currentStep, R.layout.lp_onboarding_topup_wallet_twins) + } + + private fun setupDoneState(state: TwinCardsState) = with(mainBinding.onboardingActionContainer) { + btnMainAction.setText(R.string.onboarding_done_button_continue) + btnMainAction.setOnClickListener { + store.dispatch(TwinCardsAction.Confetti.Hide) + store.dispatch(TwinCardsAction.Done) + } + + btnAlternativeAction.isVisible = false + btnAlternativeAction.isClickable = false + + tvHeader.setText(R.string.onboarding_done_header) + tvBody.setText(R.string.onboarding_done_body) + + val layout = when (state.mode) { + CreateTwinWalletMode.CreateWallet -> R.layout.lp_onboarding_done_activation_twins + CreateTwinWalletMode.RecreateWallet -> R.layout.lp_onboarding_done + } + updateConstraints(state.currentStep, layout) + } + private fun updateConstraints(currentStep: TwinCardsStep, @LayoutRes layoutId: Int) { if (this.previousStep == currentStep) return - with(binding.onboardingTopContainer) { + with(mainBinding.onboardingTopContainer) { val constraintSet = ConstraintSet() constraintSet.clone(requireContext(), layoutId) constraintSet.applyTo(onboardingWalletContainer) @@ -390,11 +381,7 @@ class TwinsCardsFragment : BaseOnboardingFragment() { } } - private fun switchToCard( - cardNumber: TwinCardNumber?, - animate: Boolean = true, - onEnd: VoidCallback = {} - ) { + private fun switchToCard(cardNumber: TwinCardNumber?, animate: Boolean = true, onEnd: VoidCallback = {}) { val position = cardNumber?.number ?: return if (twinsWidget.leapfrogWidget.getViewPositionByIndex(1) != position - 1) { @@ -405,12 +392,14 @@ class TwinsCardsFragment : BaseOnboardingFragment() { } override fun handleOnBackPressed() { - store.dispatch(TwinCardsAction.Wallet.HandleOnBackPressed { should, popAction -> - store.dispatch(TwinCardsAction.Confetti.Hide) - showConfetti(false) - if (should) switchToCard(TwinCardNumber.First, true, popAction) - else popAction() - }) + store.dispatch( + TwinCardsAction.Wallet.HandleOnBackPressed { should, popAction -> + store.dispatch(TwinCardsAction.Confetti.Hide) + showConfetti(false) + if (should) switchToCard(TwinCardNumber.First, true, popAction) + else popAction() + }, + ) } } diff --git a/app/src/main/res/layout/fragment_onboarding_main.xml b/app/src/main/res/layout/fragment_onboarding_main.xml index 97d770d2a3..c88f910dcb 100644 --- a/app/src/main/res/layout/fragment_onboarding_main.xml +++ b/app/src/main/res/layout/fragment_onboarding_main.xml @@ -27,29 +27,9 @@ - - - - - - - + + + + + + + + \ No newline at end of file From f73cc3174bfb15994fd0a7b99189e35108a9285f Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 20 Oct 2022 22:24:38 +0500 Subject: [PATCH 087/113] Updated on 2026-08-14 --- .../saltPay/ui/OnboardingSaltPayView.kt | 285 ++++++++++++++++++ .../wallet/ui/OnboardingWalletFragment.kt | 237 +-------------- 2 files changed, 294 insertions(+), 228 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt 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 new file mode 100644 index 0000000000..4ba27934ae --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt @@ -0,0 +1,285 @@ +package com.tangem.tap.features.onboarding.products.wallet.saltPay.ui + +import android.view.View +import android.view.ViewGroup +import android.widget.ProgressBar +import androidx.core.view.children +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.hide +import com.tangem.tap.common.extensions.show +import com.tangem.tap.common.extensions.stop +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.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.saltPay.UtorgWebChromeClient +import com.tangem.tap.features.onboarding.products.wallet.saltPay.UtorgWebViewClient +import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.SaltPayDialog +import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction +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.store +import com.tangem.wallet.R + +/** +[REDACTED_AUTHOR] + */ +internal class OnboardingSaltPayView( + private val walletFragment: OnboardingWalletFragment, +) { + + private val toolbar: MaterialToolbar by lazy { walletFragment.binding.toolbar } + + private var progressButton: SaltPayProgressButton? = null + + fun newState(state: OnboardingWalletState) { + handleCardArtworks(state) + + when (state.step) { + OnboardingWalletStep.SaltPay -> setSaltPayStep(state.onboardingSaltPayState) + OnboardingWalletStep.Backup -> { + if (state.backupState.backupStep == BackupStep.Finished) { + // if backup finished -> switch OnboardingWalletStep to the SaltPay + store.dispatch(OnboardingWalletAction.GetToSaltPayStep) + } else { + // if not -> back to the standard backup process + walletFragment.handleOnboardingStep(state) + } + } + else -> walletFragment.handleOnboardingStep(state) + } + } + + private fun handleCardArtworks(state: OnboardingWalletState) = with(walletFragment.binding) { + //TODO: SaltPay: remove hardCode + if (state.onboardingSaltPayState?.saltPayCardArtworkUrl == null) { + imvFrontCard.load(R.drawable.img_salt_pay_visa) { + scale(Scale.FILL) + crossfade(enable = true) + } + } else { + walletFragment.loadImageIntoImageView(state.onboardingSaltPayState.saltPayCardArtworkUrl, imvFrontCard) + } + imvFirstBackupCard.load(R.drawable.card_placeholder_wallet) + + // if (state.onboardingSaltPayState?.saltPayCardArtworkUrl == null) { + // //if saltPay url not loaded -> load from resource + // val bitmap = BitmapFactory.decodeResource(walletFragment.resources, R.drawable.img_salt_pay_visa) + // imvFrontCard.setImageBitmap(bitmap) + // } else { + // 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) { + when (state.backupCardsNumber) { + 0 -> { + tvHeader.text = walletFragment.getText(R.string.onboarding_saltpay_title_no_backup_card) + tvBody.text = walletFragment.getText(R.string.onboarding_saltpay_subtitle_no_backup_cards) + } + 1 -> { + tvHeader.text = walletFragment.getText(R.string.onboarding_saltpay_title_one_backup_card) + tvBody.text = walletFragment.getText(R.string.onboarding_saltpay_subtitle_one_backup_card) + } + else -> {} + } + } + + private fun setSaltPayStep(onboardingSaltPayState: OnboardingSaltPayState?) { + val state = onboardingSaltPayState.guard { + store.dispatchDebugErrorNotification("SaltPayView: setSaltPayStep: onboardingSaltPayState = null") + return + } + + walletFragment.binding.onboardingWalletContainer.hide() + walletFragment.bindingSaltPay.onboardingSaltpayContainer.show() + + when (state.step) { + SaltPayRegistrationStep.NoGas -> handleNoGas() + SaltPayRegistrationStep.NeedPin -> handleNeedPin() + SaltPayRegistrationStep.CardRegistration -> handleCardRegistration() + SaltPayRegistrationStep.KycIntro -> handleKycIntro() + SaltPayRegistrationStep.KycStart -> handleKycStart(state) + SaltPayRegistrationStep.KycWaiting -> handleKycWaiting() + // SaltPayRegistrationStep.Claim -> handleKycWaiting() + // SaltPayRegistrationStep.ClaimInProgress -> handleKycWaiting() + // SaltPayRegistrationStep.ClaimSuccess -> handleKycWaiting() + SaltPayRegistrationStep.Finished -> handleFinished() + } + progressButton?.changeState(state.mainButtonState) + } + + private fun handleNoGas() { + // normally this shouldn't happen + store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) + store.dispatchDialogShow(SaltPayDialog.NoFundsForActivation) + } + + private fun handleNeedPin() = with(walletFragment.bindingSaltPay) { + toolbar.title = walletFragment.getString(R.string.onboarding_navbar_pin) + showOnlyView(pinCode.root) { + progressButton = SaltPayProgressButton(pinCode.root) + progressButton?.mainView?.isEnabled = false + + var enteredPinCode = "" + pinCode.flPinCodeContainer.setContent { + PinCodeWidget { code, isLastSymbolEntered -> + enteredPinCode = code + progressButton?.mainView?.isEnabled = isLastSymbolEntered + } + } + pinCode.btnSetPin.setOnClickListener { + store.dispatch(OnboardingSaltPayAction.TrySetPin(enteredPinCode)) + } + } + } + + private fun handleCardRegistration() = with(walletFragment.bindingSaltPay) { + toolbar.title = walletFragment.getString(R.string.onboarding_navbar_register_wallet) + showOnlyView(connectCard.root) { + progressButton = SaltPayProgressButton(connectCard.root) + connectCard.btnConnect.setOnClickListener { + store.dispatch(OnboardingSaltPayAction.RegisterCard) + } + } + } + + private fun handleKycIntro() = with(walletFragment.bindingSaltPay) { + toolbar.title = walletFragment.getString(R.string.onboarding_navbar_kyc_start) + showOnlyView(verifyIdentity.root) { + progressButton = SaltPayProgressButton(verifyIdentity.root) + verifyIdentity.btnVerify.setOnClickListener { + store.dispatch(OnboardingSaltPayAction.KYCStart) + } + } + } + + private fun handleKycStart(state: OnboardingSaltPayState) = with(walletFragment.bindingSaltPay) { + toolbar.title = walletFragment.getString(R.string.onboarding_navbar_kyc_start) + showOnlyView(webvVerifyIdentity) { + progressButton = null + webvVerifyIdentity.configureSettings() + webvVerifyIdentity.webViewClient = UtorgWebViewClient( + successUrl = state.saltPayManager.kycUrlProvider.doneUrl, + onSuccess = { + webvVerifyIdentity.stop() + store.dispatch(OnboardingSaltPayAction.OnFinishKYC) + }, + ) + webvVerifyIdentity.webChromeClient = UtorgWebChromeClient(walletFragment.requireActivity()) + webvVerifyIdentity.loadUrl(state.saltPayManager.kycUrlProvider.requestUrl) + } + } + + private fun handleKycWaiting() = with(walletFragment.bindingSaltPay) { + toolbar.title = walletFragment.getString(R.string.onboarding_navbar_kyc_waiting) + showOnlyView(verifyIdentityInProgress.root) { + progressButton = SaltPayProgressButton(verifyIdentityInProgress.root) + verifyIdentityInProgress.btnOpenSupportChat.setOnClickListener { + store.dispatch(GlobalAction.OpenChat(SupportInfo())) + } + verifyIdentityInProgress.btnRefresh.setOnClickListener { + store.dispatch(OnboardingSaltPayAction.Update) + } + } + } + + // 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 fun handleFinished() { + walletFragment.binding.onboardingRoot.beginDelayedTransition() + walletFragment.bindingSaltPay.onboardingSaltpayContainer.hide() + walletFragment.binding.onboardingWalletContainer.show() + walletFragment.showSuccess() + } + + private fun showOnlyView(view: View, onShowListener: (() -> Unit)? = null) { + walletFragment.bindingSaltPay.onboardingSaltpayContainer.children + .filter { it.id != view.id } + .forEach { it.hide() } + view.show { onShowListener?.invoke() } + } + + private class SaltPayProgressButton( + root: ViewGroup, + ) : IndeterminateProgressButtonWidget(findButton(root), findProgress(root)) { + companion object { + fun findButton(view: ViewGroup): MaterialButton { + return findContainer(view).children.firstOrNull { it is MaterialButton } as MaterialButton + } + + fun findProgress(view: ViewGroup): View { + return findContainer(view).children.firstOrNull { it is ProgressBar }!! + } + + fun findContainer(view: ViewGroup): ViewGroup { + return view.children.first { it.id == R.id.btn_container } as ViewGroup + } + } + } +} 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 6f5c3fff03..75c027dfee 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 @@ -7,38 +7,21 @@ import android.view.ViewGroup import android.view.WindowManager import android.widget.FrameLayout import android.widget.ImageView -import android.widget.ProgressBar import androidx.appcompat.app.AppCompatActivity import androidx.core.view.MenuProvider -import androidx.core.view.children import androidx.transition.TransitionInflater import androidx.transition.TransitionManager import by.kirich1409.viewbindingdelegate.viewBinding import coil.load -import coil.size.Scale -import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.bottomsheet.BottomSheetBehavior -import com.google.android.material.button.MaterialButton import com.google.android.material.tabs.TabLayoutMediator import com.tangem.common.CardIdFormatter import com.tangem.common.core.CardIdDisplayFormat -import com.tangem.common.extensions.guard import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.PropertyCalculator -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.hide import com.tangem.tap.common.extensions.inflate import com.tangem.tap.common.extensions.show -import com.tangem.tap.common.extensions.stop -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.features.BaseFragment import com.tangem.tap.features.FragmentOnBackPressedHandler import com.tangem.tap.features.addBackPressHandler @@ -49,12 +32,7 @@ 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.saltPay.UtorgWebChromeClient -import com.tangem.tap.features.onboarding.products.wallet.saltPay.UtorgWebViewClient -import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.SaltPayDialog -import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction -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.ui.OnboardingSaltPayView import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.AccessCodeDialog import com.tangem.tap.store import com.tangem.wallet.R @@ -67,10 +45,10 @@ class OnboardingWalletFragment : BaseFragment(R.layout.fragment_onboarding_walle StoreSubscriber, FragmentOnBackPressedHandler { private val pbBinding: ViewOnboardingProgressBinding by viewBinding(ViewOnboardingProgressBinding::bind) - val binding: FragmentOnboardingWalletBinding by viewBinding(FragmentOnboardingWalletBinding::bind) - private val bindingSaltPay: LayoutOnboardingSaltpayBinding by lazy { binding.onboardingSaltpayContainer } + internal val binding: FragmentOnboardingWalletBinding by viewBinding(FragmentOnboardingWalletBinding::bind) + internal val bindingSaltPay: LayoutOnboardingSaltpayBinding by lazy { binding.onboardingSaltpayContainer } - private val saltPayView: SaltPayView = SaltPayView(this) + private val onboardingSaltPayView: OnboardingSaltPayView = OnboardingSaltPayView(this) private lateinit var cardsWidget: WalletCardsWidget private var accessCodeDialog: AccessCodeDialog? = null @@ -140,7 +118,6 @@ class OnboardingWalletFragment : BaseFragment(R.layout.fragment_onboarding_walle } } - override fun onStart() { super.onStart() store.subscribe(this) { state -> @@ -166,7 +143,7 @@ class OnboardingWalletFragment : BaseFragment(R.layout.fragment_onboarding_walle pbBinding.pbState.progress = state.getProgressStep() if (state.isSaltPay) { - saltPayView.newState(state) + onboardingSaltPayView.newState(state) } else { loadImageIntoImageView(state.cardArtworkUrl, binding.imvFrontCard) loadImageIntoImageView(state.cardArtworkUrl, binding.imvFirstBackupCard) @@ -175,7 +152,7 @@ class OnboardingWalletFragment : BaseFragment(R.layout.fragment_onboarding_walle } } - private fun loadImageIntoImageView(url: String?, view: ImageView) { + internal fun loadImageIntoImageView(url: String?, view: ImageView) { view.load(url) { placeholder(R.drawable.card_placeholder_black) error(R.drawable.card_placeholder_black) @@ -183,7 +160,7 @@ class OnboardingWalletFragment : BaseFragment(R.layout.fragment_onboarding_walle } } - private fun handleOnboardingStep(state: OnboardingWalletState) { + internal fun handleOnboardingStep(state: OnboardingWalletState) { when (state.step) { OnboardingWalletStep.CreateWallet -> setupCreateWalletState() OnboardingWalletStep.Backup -> setBackupState(state.backupState, state.isSaltPay) @@ -274,7 +251,7 @@ class OnboardingWalletFragment : BaseFragment(R.layout.fragment_onboarding_walle layoutButtonsAddCards.btnContinue.isEnabled = state.backupCardsNumber != 0 if (isSaltPay) { - saltPayView.showAddBackupCards(state) + onboardingSaltPayView.showAddBackupCards(state) } else { when (state.backupCardsNumber) { 0 -> { @@ -399,7 +376,7 @@ class OnboardingWalletFragment : BaseFragment(R.layout.fragment_onboarding_walle animator.showWriteBackupCard(state, cardNumber) } - private fun showSuccess() = with(binding) { + internal fun showSuccess() = with(binding) { toolbar.title = getString(R.string.onboarding_done_header) tvHeader.text = getText(R.string.onboarding_done_header) @@ -432,202 +409,6 @@ class OnboardingWalletFragment : BaseFragment(R.layout.fragment_onboarding_walle store.dispatch(OnboardingWalletAction.OnBackPressed) } - private class SaltPayView( - private val walletFragment: OnboardingWalletFragment, - ) { - - private val toolbar: MaterialToolbar by lazy { walletFragment.binding.toolbar } - - private var progressButton: SaltPayProgressButton? = null - - fun newState(state: OnboardingWalletState) { - handleCardArtworks(state) - - when (state.step) { - OnboardingWalletStep.SaltPay -> setSaltPayStep(state.onboardingSaltPayState) - OnboardingWalletStep.Backup -> { - if (state.backupState.backupStep == BackupStep.Finished) { - // if backup finished -> switch OnboardingWalletStep to the SaltPay - store.dispatch(OnboardingWalletAction.GetToSaltPayStep) - } else { - // if not -> back to the standard backup process - walletFragment.handleOnboardingStep(state) - } - } - else -> walletFragment.handleOnboardingStep(state) - } - } - - private fun handleCardArtworks(state: OnboardingWalletState) = with(walletFragment.binding) { - //TODO: SaltPay: remove hardCode - if (state.onboardingSaltPayState?.saltPayCardArtworkUrl == null) { - imvFrontCard.load(R.drawable.img_salt_pay_visa) { - scale(Scale.FILL) - crossfade(enable = true) - } - } else { - walletFragment.loadImageIntoImageView(state.onboardingSaltPayState.saltPayCardArtworkUrl, imvFrontCard) - } - imvFirstBackupCard.load(R.drawable.card_placeholder_wallet) - - // if (state.onboardingSaltPayState?.saltPayCardArtworkUrl == null) { - // //if saltPay url not loaded -> load from resource - // val bitmap = BitmapFactory.decodeResource(walletFragment.resources, R.drawable.img_salt_pay_visa) - // imvFrontCard.setImageBitmap(bitmap) - // } else { - // 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) { - when (state.backupCardsNumber) { - 0 -> { - tvHeader.text = walletFragment.getText(R.string.onboarding_saltpay_title_no_backup_card) - tvBody.text = walletFragment.getText(R.string.onboarding_saltpay_subtitle_no_backup_cards) - } - 1 -> { - tvHeader.text = walletFragment.getText(R.string.onboarding_saltpay_title_one_backup_card) - tvBody.text = walletFragment.getText(R.string.onboarding_saltpay_subtitle_one_backup_card) - } - else -> {} - } - } - - private fun setSaltPayStep(onboardingSaltPayState: OnboardingSaltPayState?) { - val state = onboardingSaltPayState.guard { - store.dispatchDebugErrorNotification("SaltPayView: setSaltPayStep: onboardingSaltPayState = null") - return - } - - walletFragment.binding.onboardingWalletContainer.hide() - walletFragment.bindingSaltPay.onboardingSaltpayContainer.show() - - when (state.step) { - SaltPayRegistrationStep.NoGas -> handleNoGas() - SaltPayRegistrationStep.NeedPin -> handleNeedPin() - SaltPayRegistrationStep.CardRegistration -> handleCardRegistration() - SaltPayRegistrationStep.KycIntro -> handleKycIntro() - SaltPayRegistrationStep.KycStart -> handleKycStart(state) - SaltPayRegistrationStep.KycWaiting -> handleKycWaiting() - SaltPayRegistrationStep.Finished -> handleFinished() - } - progressButton?.changeState(state.mainButtonState) - } - - private fun handleNoGas() { - // normally this shouldn't happen - store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) - store.dispatchDialogShow(SaltPayDialog.NoFundsForActivation) - } - - private fun handleNeedPin() = with(walletFragment.bindingSaltPay) { - toolbar.title = walletFragment.getString(R.string.onboarding_navbar_pin) - showOnlyView(pinCode.root) { - progressButton = SaltPayProgressButton(pinCode.root) - progressButton?.mainView?.isEnabled = false - - var enteredPinCode = "" - pinCode.flPinCodeContainer.setContent { - PinCodeWidget { code, isLastSymbolEntered -> - enteredPinCode = code - progressButton?.mainView?.isEnabled = isLastSymbolEntered - } - } - pinCode.btnSetPin.setOnClickListener { - store.dispatch(OnboardingSaltPayAction.TrySetPin(enteredPinCode)) - } - } - } - - private fun handleCardRegistration() = with(walletFragment.bindingSaltPay) { - toolbar.title = walletFragment.getString(R.string.onboarding_navbar_register_wallet) - showOnlyView(connectCard.root) { - progressButton = SaltPayProgressButton(connectCard.root) - connectCard.btnConnect.setOnClickListener { - store.dispatch(OnboardingSaltPayAction.RegisterCard) - } - } - } - - private fun handleKycIntro() = with(walletFragment.bindingSaltPay) { - toolbar.title = walletFragment.getString(R.string.onboarding_navbar_kyc_start) - showOnlyView(verifyIdentity.root) { - progressButton = SaltPayProgressButton(verifyIdentity.root) - verifyIdentity.btnVerify.setOnClickListener { - store.dispatch(OnboardingSaltPayAction.KYCStart) - } - } - } - - private fun handleKycStart(state: OnboardingSaltPayState) = with(walletFragment.bindingSaltPay) { - toolbar.title = walletFragment.getString(R.string.onboarding_navbar_kyc_start) - showOnlyView(webvVerifyIdentity) { - progressButton = null - webvVerifyIdentity.configureSettings() - webvVerifyIdentity.webViewClient = UtorgWebViewClient( - successUrl = state.saltPayManager.kycUrlProvider.doneUrl, - onSuccess = { - webvVerifyIdentity.stop() - store.dispatch(OnboardingSaltPayAction.OnFinishKYC) - }, - ) - webvVerifyIdentity.webChromeClient = UtorgWebChromeClient(walletFragment.requireActivity()) - webvVerifyIdentity.loadUrl(state.saltPayManager.kycUrlProvider.requestUrl) - } - } - - private fun handleKycWaiting() = with(walletFragment.bindingSaltPay) { - toolbar.title = walletFragment.getString(R.string.onboarding_navbar_kyc_waiting) - showOnlyView(verifyIdentityInProgress.root) { - progressButton = SaltPayProgressButton(verifyIdentityInProgress.root) - verifyIdentityInProgress.btnOpenSupportChat.setOnClickListener { - store.dispatch(GlobalAction.OpenChat(SupportInfo())) - } - verifyIdentityInProgress.btnRefresh.setOnClickListener { - store.dispatch(OnboardingSaltPayAction.Update) - } - } - } - - private fun handleFinished() { - walletFragment.binding.onboardingRoot.beginDelayedTransition() - walletFragment.bindingSaltPay.onboardingSaltpayContainer.hide() - walletFragment.binding.onboardingWalletContainer.show() - walletFragment.showSuccess() - } - - private fun showOnlyView(view: View, onShowListener: (() -> Unit)? = null) { - walletFragment.bindingSaltPay.onboardingSaltpayContainer.children - .filter { it.id != view.id } - .forEach { it.hide() } - view.show { onShowListener?.invoke() } - } - - private class SaltPayProgressButton( - root: ViewGroup, - ) : IndeterminateProgressButtonWidget(findButton(root), findProgress(root)) { - companion object { - fun findButton(view: ViewGroup): MaterialButton { - return findContainer(view).children.firstOrNull { it is MaterialButton } as MaterialButton - } - - fun findProgress(view: ViewGroup): View { - return findContainer(view).children.firstOrNull { it is ProgressBar }!! - } - - fun findContainer(view: ViewGroup): ViewGroup { - return view.children.first { it.id == R.id.btn_container } as ViewGroup - } - } - } - } - private fun getDeviceScaleFactor(): Float { val typedValue = TypedValue() resources.getValue(R.dimen.device_scale_factor_for_twins_welcome, typedValue, true) From 86ad6beb29803510a86d63b363971f5c20aee0d1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 20 Oct 2022 22:45:30 +0500 Subject: [PATCH 088/113] Updated on 2026-08-14 --- .../main/res/drawable/img_salt_pay_visa.png | Bin 60037 -> 28037 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/app/src/main/res/drawable/img_salt_pay_visa.png b/app/src/main/res/drawable/img_salt_pay_visa.png index 19f69d5eb6987b92121aaa46334a97ae0d26751f..02b199c1e5a6c5a92ec9809f19156b62157401ba 100644 GIT binary patch literal 28037 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 -L>W84`oU;LzELq}BG^h+*aKQy| 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 MHT0iWtJ{3|KUouMmjD0& From ca3e60ea5f4cfe459afd019dbccd2a8705108319 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 20 Oct 2022 22:50:57 +0500 Subject: [PATCH 089/113] Updated on 2026-08-14 --- .../com/tangem/tap/common/redux/global/GlobalMidlleware.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt index 88a9c805aa..67e326564d 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt @@ -97,8 +97,7 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di return } - val scanResponse = globalState.onboardingState.onboardingManager?.scanResponse - ?: globalState.scanResponse + val scanResponse = globalState.scanResponse ?: globalState.onboardingState.onboardingManager?.scanResponse // if config not set -> try to get it based on a scanResponse.productType val unsafeZendeskConfig = action.zendeskConfig ?: when { From de78cc60f454d436c29c7028594a69c46454d5e1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 20 Oct 2022 22:52:55 +0500 Subject: [PATCH 090/113] Updated on 2026-08-14 --- app/src/main/java/com/tangem/tap/domain/TapErrors.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt index 6e73a11fba..5a0cf85668 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt @@ -70,7 +70,6 @@ sealed class TapSdkError(override val messageResId: Int?) : Throwable(), TangemE object CardForDifferentApp : TapSdkError(R.string.alert_unsupported_card) object CardNotSupportedByRelease : TapSdkError(R.string.error_update_app) - object ScanPrimaryCard : TapSdkError(R.string.saltpay_backup_warning) } fun TapErrors.assembleErrors(): MutableList?>> { From 5e42e641725d967ba2c9ab78e9fa9378d1bb436e Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 20 Oct 2022 22:15:08 +0400 Subject: [PATCH 091/113] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- .../domain/tasks/product/ScanProductTask.kt | 60 ++++++++++--------- .../tap/domain/tokens/UserTokensRepository.kt | 6 +- 3 files changed, 37 insertions(+), 31 deletions(-) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index cf6ea50867..c8f577f2ab 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit cf6ea50867477f97655da9da47ff94987e8f3354 +Subproject commit c8f577f2ab48e0d6c49f79b6cff21eabe67cd63e diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt index c8bf29caca..252d3585e6 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt @@ -36,6 +36,8 @@ import com.tangem.tap.domain.extensions.isMultiwalletAllowed import com.tangem.tap.domain.tokens.UserTokensRepository import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.preferencesStorage +import com.tangem.tap.scope +import kotlinx.coroutines.launch class ScanProductTask( val card: Card? = null, @@ -183,43 +185,45 @@ private class ScanWalletProcessor( session: CardSession, callback: (result: CompletionResult) -> Unit, ) { - val productType = when(card.isSaltPay){ + val productType = when (card.isSaltPay) { true -> ProductType.SaltPay else -> ProductType.Wallet } - val derivations = collectDerivations(card) - if (derivations.isEmpty() || !card.settings.isHDWalletAllowed) { - callback( - CompletionResult.Success( - ScanResponse( - card = card, - productType = productType, - walletData = session.environment.walletData, - primaryCard = primaryCard, + scope.launch { + val derivations = collectDerivations(card) + if (derivations.isEmpty() || !card.settings.isHDWalletAllowed) { + callback( + CompletionResult.Success( + ScanResponse( + card = card, + productType = productType, + walletData = session.environment.walletData, + primaryCard = primaryCard, + ), ), - ), - ) - return - } + ) + return@launch + } - DeriveMultipleWalletPublicKeysTask(derivations).run(session) { result -> - when (result) { - is CompletionResult.Success -> { - val response = ScanResponse( - card = card, - productType = productType, - walletData = session.environment.walletData, - derivedKeys = result.data.entries, - primaryCard = primaryCard, - ) - callback(CompletionResult.Success(response)) + DeriveMultipleWalletPublicKeysTask(derivations).run(session) { result -> + when (result) { + is CompletionResult.Success -> { + val response = ScanResponse( + card = card, + productType = productType, + walletData = session.environment.walletData, + derivedKeys = result.data.entries, + primaryCard = primaryCard, + ) + callback(CompletionResult.Success(response)) + } + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) } - is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) } } } - private fun getBlockchainsToDerive(card: Card): List { + private suspend fun getBlockchainsToDerive(card: Card): List { val userTokensRepository = userTokensRepository ?: return emptyList() val blockchainsToDerive = userTokensRepository.loadBlockchainsToDerive(card).toMutableList().ifEmpty { mutableListOf( @@ -253,7 +257,7 @@ private class ScanWalletProcessor( return blockchainsToDerive.distinct() } - private fun collectDerivations(card: Card): Map> { + private suspend fun collectDerivations(card: Card): Map> { val blockchains = getBlockchainsToDerive(card) val derivations = mutableMapOf>() diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt index 67eda0ee93..a92143defc 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt @@ -67,8 +67,10 @@ class UserTokensRepository( ) } - fun loadBlockchainsToDerive(card: Card): List { - return storageService.getUserTokens(card.getUserId())?.toBlockchainNetworks() ?: emptyList() + suspend fun loadBlockchainsToDerive(card: Card): List { + return storageService.getUserTokens(card.getUserId())?.toBlockchainNetworks() ?: storageService.getUserTokens( + card, + ).toBlockchainNetworks() } private fun loadDemoCurrencies(): List { From 55eb45a3a2b5c5ac8d2de3d122fc2d5775ac1ca2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 21 Oct 2022 13:11:56 +0400 Subject: [PATCH 092/113] Updated on 2026-08-14 --- .../com/tangem/tap/domain/tokens/UserTokensRepository.kt | 2 +- .../tangem/tap/domain/tokens/UserTokensStorageService.kt | 2 +- .../com/tangem/tap/features/wallet/models/Currency.kt | 8 +++++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt index a92143defc..0a63e01123 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt @@ -36,7 +36,7 @@ class UserTokensRepository( return when (val networkResult = networkService.getUserTokens(userId)) { is Result.Success -> { - val tokens = networkResult.data.tokens.map { Currency.fromTokenResponse(it) } + val tokens = networkResult.data.tokens.mapNotNull { Currency.fromTokenResponse(it) } storageService.saveUserTokens(card.getUserId(), tokens.toUserTokensResponse()) tokens } diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt index 869c49034f..c71e5afa53 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt @@ -20,7 +20,7 @@ class UserTokensStorageService( fun getUserTokens(userId: String): List? { return try { val json = fileReader.readFile(getFileNameForUserTokens(userId)) - userTokensAdapter.fromJson(json)?.tokens?.map { Currency.fromTokenResponse(it) } + userTokensAdapter.fromJson(json)?.tokens?.mapNotNull { Currency.fromTokenResponse(it) } } catch (exception: Exception) { Log.error { exception.stackTraceToString() } null diff --git a/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt b/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt index 7597156ab9..06e3cbae64 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt @@ -110,7 +110,9 @@ sealed interface Currency { ) } - fun fromTokenResponse(tokenResponse: TokenResponse): Currency { + fun fromTokenResponse(tokenResponse: TokenResponse): Currency? { + val blockchain = com.tangem.blockchain.common.Blockchain.fromNetworkId(tokenResponse.networkId) + ?: return null return when { tokenResponse.contractAddress != null -> Token( com.tangem.blockchain.common.Token( @@ -120,11 +122,11 @@ sealed interface Currency { decimals = tokenResponse.decimals, id = tokenResponse.id, ), - blockchain = com.tangem.blockchain.common.Blockchain.fromNetworkId(tokenResponse.networkId)!!, + blockchain = blockchain, derivationPath = tokenResponse.derivationPath, ) else -> Blockchain( - blockchain = com.tangem.blockchain.common.Blockchain.fromNetworkId(tokenResponse.networkId)!!, + blockchain = blockchain, derivationPath = tokenResponse.derivationPath, ) } From 3590da6e28b18b33921ca8b96d9fc703b7586dec Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 21 Oct 2022 16:03:20 +0400 Subject: [PATCH 093/113] Updated on 2026-08-14 --- .../java/com/tangem/tap/domain/tokens/UserTokensRepository.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt index 0a63e01123..721657cdb8 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt @@ -38,7 +38,7 @@ class UserTokensRepository( is Result.Success -> { val tokens = networkResult.data.tokens.mapNotNull { Currency.fromTokenResponse(it) } storageService.saveUserTokens(card.getUserId(), tokens.toUserTokensResponse()) - tokens + tokens.distinct() } is Result.Failure -> { handleGetUserTokensFailure(card = card, userId = userId, error = networkResult.error) @@ -97,7 +97,7 @@ class UserTokensRepository( } else -> { val tokens = storageService.getUserTokens(userId) ?: storageService.getUserTokens(card) - tokens + tokens.distinct() } } } From 43981148bd94cd1a773e6c8307ed866e4bcf88ff Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 22 Oct 2022 18:12:37 +0500 Subject: [PATCH 094/113] Updated on 2026-08-14 --- app/src/main/assets/tos.html | 3198 +++++++++++++++------------------- 1 file changed, 1366 insertions(+), 1832 deletions(-) diff --git a/app/src/main/assets/tos.html b/app/src/main/assets/tos.html index 67c6eba9b4..728ab43409 100644 --- a/app/src/main/assets/tos.html +++ b/app/src/main/assets/tos.html @@ -1,1832 +1,1366 @@ - - - - - file_1666042066293 - - - -

- SaltPay Oly Card Terms of Service -

-


-

We are delighted - that you (“you”, - “your”, or the “Cardholder”) have chosen to use the SaltPay Oly Card (the “Card”). - The terms - and conditions set out here (“Terms”) govern your use of your Card as operated by - SaltPay.

-

- When you use the - Card, you also agree to - the SaltPay - General Terms - which are incorporated - into these Terms by - reference, including any amendments made to those from time to time. If you do not - understand any provisions of - these Terms, please contact us before using the Card. For information about how we treat - your personal data, - please see our Privacy - Policy.

-


-
    -
  1. - Definitions

    -

    - Affiliate: means any entity controlling, controlled by or under common control with that party, where “control” means the ownership of more than 50% of (i) the voting securities or (ii) an interest in the assets, profits, or earnings of an entity. -

    -

    - Blockchain Asset: means a digital data stored in and protected by the blockchain network, including but not limited to ownership right and value of cryptocurrency, tokenized assets, non-fungible asset. -

    -

    - Cardholder: a person who holds a SaltPay Oly Card. -

    -

    Private - Key: a secret cryptographic key which provides full control over a Blockchain Asset. -

    -

    - Public - Key: means a cryptographic key, which provides access to information about a Blockchain Asset, including but not limited to Blockchain Address. -

    -

    - SaltPay: Salt Pay IIB hf. -

    -

    SaltPay - Oly Card / Card: physical card which stores Private Key and Public Key.

    -

    - Services: enabling the use of the SaltPay Oly Card at physical point of sale terminals that are enabled for the acceptance of Visa Debit cards. -

    -

    - Tangem: means Tangem AG, a company incorporated in Switzerland with company number 1315048, whose address is at Baarerstrasse 10, 6300 Zug. -

    -

    - Tangem - Mobile Application: Third party mobile application, providing interoperability between SaltPay Oly Card and blockchain, and working on NFC-capable smartphones and tablets using Google Android and Apple iOS operating systems. -

    -

    - Tangem - Service: means the service provided by Tangem, which provides and administers the Tangem Mobile Application, which enables Cardholders to generate and store Public Keys and Private Keys in a hardware crypto wallet. -

    -

    Utorg - Service: means providing services of exchanging a virtual currency against a fiat currency. -

    -


  2. -
  3. - Important Legal - Stuff

    -

    - We think - it’s important that we highlight and clearly explain how SaltPay safeguards its - interests, while offering - its services to its Cardholders (you!) which includes limitation of liability and - disclaimers. The sections - set out below reflect those safeguards and how they might impact you. It’s therefore - important that you read - them carefully. If you’re unsure about any of them, please get in touch.

    -
      -
    1. - These - Terms of Service (the “Terms”) govern the use of SaltPay Oly Cards to - authorised employees of - SaltPay and its Affiliates.

    2. -
    3. - The Cards may be used for storage of Private Key and Public Key to the Cardholder’s - Blockchain Assets - and authentication of Card Transactions with the purpose of transfer of Blockchain - Assets for goods and - services purchasing through the Visa payments infrastructure.

      -


    4. -
    5. The Cardholder - agrees that these Terms are binding.

    6. -
    7. The - Cardholder shall be - the only person having physical access to the Card.

    8. -
    9. - The Cardholder acknowledges and agrees that SaltPay does not provide backup or - recovery of Private Key - and Public Key stored by the Card. The Cardholder creates the backup of the SaltPay - Card at the point at - which they activate the Card through the Tangem Mobile Application by linking their - Card with Tangem - Wallet.

      -


    10. -
    11. - The conditions of - use of the Tangem Wallet and Mobile Application are defined in the terms of service - for the Tangem - Wallet and Mobile Application, which will also be available upon downloading the - application.

      -


    12. -
    -
  4. -
  5. - SaltPay’s - obligations

    -
      -
    1. - SaltPay agrees to - provide the Cardholder with the Service in accordance with these Terms and within - the agreed scope.

      -
    2. -
    3. - Under the condition - that the Cardholder exercises their obligations set out under Clause 4 of these - Terms, SaltPay shall use - its best endeavours to ensure that the Card will function properly and without - restriction until the - date on which the Card expires.

    4. -
    5. - SaltPay shall ensure - that the Card prevents duplication of the Private Key, and that the Cardholder has - exclusive control - over the Blockchain Asset unless the contrary is imposed by specific blockchain - network rules e.g., two - or more private keys can be used to control the same Blockchain Asset.

      -


    6. -
    7. SaltPay - agrees that it will not keep any records of Cardholder information, save for the - Cardholder’s full name, - including but not limited to the amount of Blockchain Asset stored on the Card, the - Private Key or - personalised history of Card usage.

      -


    8. -
    9. For the - avoidance of doubt, SaltPay does not guarantee that the operation of Card and/or - Tangem Mobile - Application will be secure, accurate, complete, uninterrupted, without error or free - of viruses, worms, - other harmful components or other program limitations. SaltPay may, at its sole - discretion and without - obligation to do so, correct, modify, amend, enhance, improve and make any other - changes to Card.

      -


    10. -
    -
  6. -
  7. The - Cardholder’s - obligations

    -
      -
    1. - By - accepting these Terms, you represent and warrant that (a) you have the authority to - execute and perform - the obligations contained here; (b) you (and your employees, directors, contractors - and agents) will - comply with all law applicable to your business and the use of the Card; and (c) you - will not use the - Card, directly or indirectly, for any fraudulent or illegal undertaking.

    2. -
    3. - Upon receiving the Card, the Cardholder, if they choose to use the Tangem Service, - should download the - Tangem Mobile Application in order to determine the amount of Blockchain Asset - stored on the Card.

      -


    4. -
    5. - When the Cardholder - pays for goods or services using their SaltPay Oly Card through contactless - confirmation using NFC, they - confirm that there is a sufficient balance in the Tangem Wallet and thereby agree - that amount is - deducted from the account.

    6. -
    7. - The Cardholder shall at all times keep the Card and the means of access (backup card - and pin codes) to - the Card separate from each other.

    8. -
    9. - The Cardholder shall at all times be aware of where the Card is and regularly ensure - that it is still in - their possession. The Cardholder shall avoid even temporary possession of the Card - by any other

      -

      - person. If the Card is lost, stolen or destroyed, control over the corresponding - Blockchain Asset - may be permanently lost.

    10. -
    11. - The Cardholder shall treat the Card with care and protect the Card from mechanical - damage, high - temperatures, strong electromagnetic fields, and other harmful factors.

    12. -
    13. - In the event that the - Card is stolen or - lost, and/or if a Cardholder becomes aware of an unauthorised or incorrectly - initiated transaction, - the Cardholder shall contact Tangem as soon as possible by sending an email - to support@tangem.com - or by using the chat function on the Official Mobile Application. -

      -


    14. -
    15. - In the event that the - Cardholder wishes - to deactivate their account, they shall contact Tangem by sending an email - to support@tangem.com - or by using the chat function on the Official Mobile Application. -

      -
    16. -
    17. - The Card can be used only with the official Tangem Mobile Application downloaded - from Google Play and - App Store.

    18. -
    19. - The Tangem Mobile Application shall be the exclusive source of information - concerning the Blockchain - Address of the Blockchain Asset and corresponding Public Key stored on the Card.

      -
    20. -
    21. - The Cardholder shall only use Near-Field Devices (“NFC Devices”) that are - capable of running the - Tangem Mobile Application.

    22. -
    23. - The Card shall be used only for physically tapping and holding near the Cardholder’s - NFC Device when the - Tangem Mobile Application requests it. For the avoidance of doubt, the Card cannot - be used to pay using - any method which involves gateways.

      -


    24. -
    -
  8. -
  9. The - Cardholder’s liabilities

    -
      -
    1. - The Cardholder is liable for all liabilities arising from the use and misuse of the - Card. In any case, - the Cardholder is liable for all transactions authorised using a means of - access.

    2. -
    3. - Any loss or damage resulting from the forwarding of the Card and/or means of access - shall be borne by - the Cardholder.

    4. -
    5. - Loss or damage incurred by the Cardholder in connection with the possession or use - of the Card shall be - borne solely by the Cardholder. SaltPay assumes no liability if the Card cannot be - used due to a - technical defect or because it has been cancelled or blocked.

      -


    6. -
    7. - Cardholder is only - permitted to use the Products for his personal, non-commercial use. Cardholder is - not permitted to - resell the Card.

      -


    8. -
    -
  10. -
  11. - Limitation of - liability

    -
      -
    1. - The Card is provided as-is, and except as expressly stated in these Terms, SaltPay - provides no express - or implied warranties or conditions, and SaltPay disclaims and excludes any implied - terms, - representations, warranties, and conditions with respect to the Card, including - warranties of - merchantability, fitness for a particular purpose, title, satisfactory quality and - non-infringement, as - well as any other implied warranties, such as warranties regarding data loss, - availability, accuracy, - functionality and lack of viruses. These disclaimers will apply except to the extent - applicable law does - not permit them. Any warranties, guarantees, or conditions that cannot be disclaimed - as a matter of law, - but which may be limited in duration, last for one year from the date on which you - receive the Card.

      -
    2. -
    3. - SaltPay shall not be liable and does not warrant or make any representations - regarding the services - provided by third parties in relation to the Tangem Service, including but not - limited to the Tangem - Mobile Application and the Utorg Service. The relationship between the Cardholder - and any third party in - relation to the Tangem Service shall be governed by separate agreements provided - below.

    4. -
    5. - In - no event shall SaltPay and/or any of its Affiliates be liable for any damages - whatsoever, including - direct, indirect, extraordinary, incidental or consequential damages of any kind - resulting from or - arising out of the use of Card and/or the Tangem Mobile Application or inability to - use Card and/or the - Tangem Mobile Application, failure of Card and/or the Tangem Mobile Application to - perform as - represented or expected, loss of goodwill or profits.

    6. -
    7. - SaltPay shall not be held liable for any failure to be able to use the Card and/or - the Tangem Mobile - Application for any reason whatsoever, nor will SaltPay be held liable for the loss - of the Blockchain - Asset resulting from a malfunction or inoperability of the blockchain network - hosting Blockchain Asset, - as well as the inaccessibility of its public servers and services.

    8. -
    9. - SaltPay shall not be held liable for the loss of profits, income, value or any - indirect, extraordinary, - consequential, exemplary or punitive damages.

    10. -
    11. - SaltPay shall not be - held liable for loss or breakdown of the Card and/or the Tangem Mobile - Application.

    12. -
    13. - SaltPay shall not be held liable for any loss of Blockchain Asset in the event of - loss or total - breakdown of the Card and/or the Tangem Mobile Application.

    14. -
    15. - The Cardholder hereby acknowledges and agrees that these limitations of liability - are agreed allocations - of risk constituting in part the consideration for using the Card and/or the Tangem - Mobile Application - and such limitations will apply notwithstanding the failure of essential purpose of - any limited remedy, - and even if SaltPay and/or any SaltPay Affiliates have been advised of the - possibility of such - liabilities and/or damages.

      -


    16. -
    -
  12. -
  13. Term - and - Termination

    -
      -
    1. - These Terms become effective upon the date (i) you accept these Terms online (or in - another manner - expressly approved by us) or (ii) you first access or use Tangem and shall terminate - on the date on - which the Card expires.

      -


    2. -
    3. - We may terminate - our arrangement with you under these Terms for any reason or no reason, at any time, - by giving 2 months’ - prior notice to this effect. We may also suspend your access to Tangem if you: (i) - have breached these - Terms or any other agreement you have with SaltPay, including SaltPay’s policies or - instructions; or - (ii) provided any false, incomplete, inaccurate, or misleading information or - otherwise engaged in - fraudulent or illegal conduct c) have misused the Service.

      -


    4. -
    5. - Where your - arrangement with us under these Terms terminates for any reason, you will, where - applicable, remain - liable for any fees, charges and other payment obligations you owe us.

      -


    6. -
    -
  14. -
  15. - General

    -
      -
    1. - Except as expressly provided herein, these Terms are a complete statement of the - agreement between you - and us regarding your use of Tangem. In the event of a conflict between these Terms - and the SaltPay - General Terms or any other SaltPay agreement or policy, these Terms shall - prevail.

      -


    2. -
    3. - These Terms do not - limit any rights that SaltPay may have under trade secret, copyright, patent, or - other laws. No waiver - of any term of these Terms shall be deemed a further or continuing waiver of such - term or any other - term.

    4. -
    5. - In - the event any court shall declare any section or sections of this Agreement invalid - or void, such - declaration shall not invalidate the entire Agreement and all other paragraphs of - the Agreement shall - remain in full force and effect.

    6. -
    7. - For - the avoidance of doubt, - provisions around general terms not covered in these Terms of Service, including - but not limited to - restrictions and unauthorised or illegal use, intellectual property, data, - taxes, indemnification, - warranties, severability, force majeure, and disputes, shall be governed by - the SaltPay General - Terms.

    8. -
    -
  16. -
-

TANGEM - WALLET AND TANGEM MOBILE APPLICATION

-

Terms of Service

-


-

PLEASE READ - THESE TERMS OF SERVICE - CAREFULLY. BY CLICKING TO ACCEPT, OR BY ACCESSING OR USING OUR SERVICES, YOU AGREE THAT YOU HAVE - READ, UNDERSTOOD, - AND ACCEPT ALL OF THE TERMS AND CONDITIONS CONTAINED HEREIN. BY PURCHASE OF THE SALTPAY OLY CARD - WITH THE TANGEM - WALLET OR BY USING THE SALTPAY OLY CARD OR BY USING TANGEM WALLET OR BY USING TANGEM MOBILE - APPLICATION, YOU - DEMONSTRATE YOUR AGREEMENT TO THESE TERMS AND CONDITIONS CONTAINED HEREIN.

-


-
    -
  1. - DEFINITIONS

    -


    -


    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    - “Cardholder

    - means a person who owns Tangem Wallet and SaltPay Oly Card.

    - “Blockchain Asset

    - means a digital data stored in and protected by the blockchain network, - including but not limited to - ownership right and value of cryptocurrency, tokenized assets, non-fungible - asset.

    - “Blockchain Address

    - means a unique identifier that serves as a virtual location of the Blockchain - Asset in the - blockchain.

    - “Card Transaction

    - means transfer of Blockchain Asset from the Blockchain Address associated with - the Public Key stored - on the SaltPay Oly Card for goods and services purchased through traditional - payments rail.

    - “Official Mobile Application

    - means an application developed and distributed by Tangem, providing - interoperability between SaltPay - Oly Card or Tangem Wallet and blockchain, and working on NFC-capable smartphones - and tablets using - Google Android and Apple iOS operation systems.

    - “Private Key

    - a secret cryptographic key which provides full control over a Blockchain - Asset.

    - “Tangem

    - means Tangem AG, Baarerstrasse 10, 6300 Zug.

    - “Public Key

    - means a cryptographic key, which provides access to information about a - Blockchain Asset, including - but not limited to Blockchain Address

    - “Services

    - means the purchase and/or use of the Tangem Wallet and the services or any other - features, - technologies or functionalities linked to the Card provided or operated by - Tangem AG via the website - or Official Mobile Applications.

    - “Tangem Wallet/Tangem Card / Card

    - means physical card which stores Private Key and Public Key and is used as a - backup card for SaltPay - Oly Card.

    -


  2. -
  3. GENERAL - PROVISIONS

    -


    -
      -
    1. - These Terms of Service (the “Terms”) govern the use of Tangem Wallet and/or - Official Mobile - Application provided by Tangem AG (referred to as "Tangem", - "we" or - "us" in this document) and the related services or any other - features, technologies or - functionalities linked to the Tangem Wallet. Tangem is a company

      -

      - incorporated under the laws of Switzerland, with a registered address at - Baarerstrasse 10, 6300 Zug, - Switzerland.

      -


    2. -
    3. - Tangem Wallet - represents a backup to the SaltPay Oly Card, which may be used only for storage of - Private Key and - Public Key to Cardholder’s Blockchain Assets. Tangem Wallet, unlike SaltPay Oly - Card, can’t be used for - goods and services purchasing through traditional payments rail. The use of SaltPay - Oly Card is governed - by a separate Terms of Service, provided above.

      -


    4. -
    5. - Official Mobile - Application is intended for usage only with SaltPay Oly and Tangem Wallet, providing - interoperability - between the cards and blockchain via NFC interface. Official Mobile Application DOES - NOT:

      -
        -
      1. - Generate, store, transmit, or have access to private (secret) cryptographic - keys to blockchain - wallets holding Blockchain assets.

      2. -
      3. - Generate, - store, transmit, or have access to secret keys, passwords, passphrases, - recovery phrases that - can be used to restore or to copy private (secret) keys to blockchain - wallets holding Blockchain - assets.

      4. -
      5. Provide - exchange, trading, investment services on behalf of Tangem.

        -


      6. -
      -
    6. -
    -
  4. -
  5. - RIGHTS AND - OBLIGATIONS

    -


    -
      -
    1. - Cardholder agrees that - these Terms are binding.

      -


    2. -
    3. - Cardholder shall be the - only person having physical access to the Tangem Wallet.

      -


    4. -
    5. - Cardholder acknowledges and agrees that Tangem does not provide backup or recovery - of Private Key and - Public Key stored by the Tangem Wallet.

      -


    6. -
    7. - Tangem does not - keep records of Cardholder’s personal data, the amount of Blockchain Asset stored on - the Card, the - Private Key, or personalized history of Card usage.

      -


    8. -
    -
  6. -
  7. - COSTS

    -


    -
      -
    1. - Costs, fees and commission (the “Costs”) may be charged in connection with the use - of SaltPay Oly Card. - These Costs are disclosed in the Official Mobile Application to the Cardholder.

      -


    2. -
    3. - Amendments to Costs - due to changing expenses or market conditions may be made at any time via - adjustments to the fee - schedules. Such amendments shall be communicated to the Cardholder in an appropriate - manner. Upon - notification and in the event of the objection, the Cardholder may cancel the - SaltPay Oly Card with - immediate effect.

    4. -
    -
  8. -
  9. - CARDHOLDER’S DUTIES OF CARE

    -


    -

    In particular, the - Cardholder shall exercise the - following duties of care:

    -


    -
      -
    1. - Upon receiving the SaltPay Oly Card and the Tangem Wallet, the Cardholder should - download the Official - Mobile Application from Google Play or Apple app store and create the backup of the - SaltPay Oly Card by - linking SaltPay Oly card with Tangem backup wallet following the instructions in the - Official Mobile - Application.

      -


    2. -
    3. Cardholder - shall keep the SaltPay Oly Card and Tangem Wallet with care and separate from each - other.

      -


    4. -
    5. - Cardholder must always know where the Tangem Wallet is and regularly ensure that it - is still in his/her - possession. He/she shall avoid even temporary possession of the Tangem Wallet by any - other person.

      -


    6. -
    7. - Cardholder shall - treat the Tangem Wallet in the same manner as physical money (cash) and keep it - safe. If the SaltPay Oly - Card and Tangem Wallet are lost, stolen or destroyed at the same time, control over - the corresponding - Blockchain Asset may be permanently lost.

      -


    8. -
    9. - Official Mobile - Application shall be the only source of information about the Blockchain Address of - the Blockchain Asset - and corresponding Public Key stored on the SaltPay Oly Card/Tangem Wallet.

      -


    10. -
    11. - Cardholder shall - only use Near-Field Devices (the “NFC”) devices that are capable of running Official - Mobile - Applications. He/she shall avoid leaving the Tangem Wallet in the proximity of the - NFC devices of other - persons.

      -


    12. -
    13. - Tangem Wallet shall - be used only for physically tapping and holding near Cardholder’s NFC device when - Official Mobile - Application requests it.

      -


    14. -
    15. - Cardholder shall - keep the Tangem Wallet with care and protect the Tangem Wallet from mechanical - damage, high - temperatures, strong electromagnetic fields, and other harmful factors.

      -


    16. -
    -
  10. -
  11. - RIGHTS AND - RESPONSIBILITIES OF CARDHOLDER

    -


    -
      -
    1. - The Cardholder is liable for all liabilities arising from the use of the Tangem - Wallet and/or Tangem - Official Mobile Application. As a matter of principle, the Cardholder is liable for - any risks resulting - from the misuse of the Tangem Wallet and/or Official Mobile Application. In any - case, the Cardholder is - liable for all transactions authorized using a means of access.

      -


    2. -
    3. - Any loss or damage - resulting from the forwarding of the Tangem Wallet and/or means of access shall be - borne by the - Cardholder.

      -


    4. -
    5. - Loss or damage - incurred by the Cardholder in connection with the possession or use of the Tangem - Wallet and/or Official - Mobile Application shall be borne solely by the Cardholder. Tangem assumes no - liability if the Tangem - Wallet and/or Official Mobile Application cannot be used due to a technical defect - or because it has - been canceled, blocked or the spending limit has been adjusted.

      -


    6. -
    7. - Cardholder is only - permitted to use the Tangem Wallet and Official Mobile Application for his personal, - non-commercial use. - Cardholder is not allowed to resell the Tangem Wallet.

    8. -
    9. - Cardholder is solely responsible to determinate what, if any, taxes apply to Card - Transactions. Tangem - or contributors to the Official Mobile Application are NOT responsible for - determining the taxes that - apply to Card Transactions.

      -


    10. -
    11. - Before Cardholder - engages in transactions - using an electronic system, Cardholder should carefully review the rules and - regulations of the - third-party provider Utorg OÜ (https://utorg.pro/terms/), - who provides exchange services for SaltPay Oly Card. Online trading has inherent - risk due to system - response and access times that may vary due to market conditions, system - performance, and other factors. - Cardholder should understand, fully accept and take on these and additional risks - before executing any - Card Transactions.

      -


    12. -
    13. - There is - considerable exposure to risk in the Blockchain Asset exchange transaction. Any - transaction involving - the Blockchain Asset involves risks including, but not limited to, the potential for - changing economic - conditions that may substantially affect the price or liquidity of the Blockchain - Asset. Investments in - the Blockchain Asset exchange speculation may also be susceptible to sharp rises and - falls as the - relevant market values fluctuate. It is for this reason that when speculating in - such markets it is - advisable to use only risk capital.

      -


    14. -
    -
  12. -
  13. - RESPONSIBILITIES AND - LIABILITIES OF TANGEM

    -


    -
      -
    1. - Tangem does not warrant or make any representations regarding the use, the inability - to use or operate, - or the results of the use or operation of the Tangem Wallet and/or Tangem Official - Mobile - Application.

      -


    2. -
    3. Tangem does not - provide any backup or recovery of Private Key and Public Key stored on the Tangem - Wallet.

      -


    4. -
    5. - Tangem does not keep any records of Cardholder information, the amount of Blockchain - Asset stored on the - Tangem Wallet or SaltPay Oly Card, the Private Key, or personalized history of cards - usage.

      -


    6. -
    7. - Tangem shall not be - held liable for any failure to be able to use the Tangem Wallet and/or Official - Mobile Application, for - any reason whatsoever, nor will Tangem be held liable for the loss of the Blockchain - Asset resulting - from a malfunction or inoperability of the blockchain network hosting Blockchain - Asset, as well as the - inaccessibility of its public servers and services.

      -


    8. -
    9. - The Tangem does not - guarantee that the operation of Tangem Wallet and/or Official Mobile Application - will be secure, - accurate, complete, uninterrupted, without error or free of viruses, worms, other - harmful components or - other program limitations. Tangem may, at its sole discretion and without obligation - to do so, correct, - modify, amend, enhance, improve and make any other changes to Tangem Wallet and/or - Official Mobile - Application.

      -


    10. -
    11. - Tangem shall not be - held liable for the loss of profits, income, value or any indirect, extraordinary, - consequential, - exemplary or punitive damages.

      -


    12. -
    13. Tangem shall - not be held liable for loss or breakdown of the Tangem Wallet.

      -


    14. -
    15. - Tangem shall not be held liable for any loss of Blockchain Asset in the event of - loss or total breakdown - of the Tangem Wallet or SaltPay Oly Card.

    16. -
    -
  14. -
  15. - GUARANTEES

    -


    -
      -
    1. - Under the condition that the Cardholder exercises the duties of care as stated in - Clause 5, Tangem - guarantees that the Tangem Wallet will function properly and without restriction for - a period of 2 (two) - years.

      -


    2. -
    3. - Tangem guarantees - that the Tangem Wallet prevents duplication of the Private Key and that the - Cardholder has exclusive - control over the Blockchain Asset unless the contrary is imposed by specific - blockchain network - rules,

      -

      - e.g. two or more - private keys can be used to control the same Blockchain Asset.

      -


    4. -
    -
  16. -
  17. - LIMITATION OF LIABILITY

    -


    -
      -
    1. - The Tangem Wallet, including without limitation any content, data and information - related thereto, is - provided on an “as is” basis and “as available” basis, without any warranties of any - kind, express or - implied warranties of use, merchantability or suitability for a certain purpose or - use, including - without limitation, the quality of products and services provided by users, - third-party services, and/or - exchanges (except for the guarantees set forth in Clause 8).

      -


      -

      - Official Mobile - Application is provided on an “as is” basis and “as available” basis without any - warranties of any - kind regarding Official Mobile Application and/or any content, data, materials - and/or services - provided on Official Mobile Application.

      -


    2. -
    3. - Tangem and its - affiliates, including any of their officers, directors, shareholders, employees, - sub-contractors, - agents, parent companies, subsidiaries and other affiliates (collectively, the - “Tangem Affiliates”), - jointly and severally, disclaim and make no representations or warranties as to the - usability, accuracy, - quality, availability, reliability, suitability, completeness, truthfulness, - usefulness or effectiveness - of any content, data, results or other information obtained or generated by Tangem - and/or any user - related to you or any other user of the Tangem Wallet, and Official Mobile - Applications.

      -


    4. -
    5. - In no event shall - Tangem and/or any of the Tangem Affiliates be liable for any damages whatsoever, - including direct, - indirect, extraordinary, incidental or consequential damages of any kind, but not - limited to, resulting - from or arising out of the use of Tangem Wallet and/or Official Mobile Applications - or inability to use - Tangem Wallet and/or Official Mobile Applications, failure of Tangem Wallet and/or - Official Mobile - Applications to perform as represented or expected, loss of goodwill or profits, or - loss of data arising - out of or in any way connected with the use of Tangem Wallet and/or Official Mobile - Applications. In no - event shall Tangem and/or any of the Tangem Affiliates be liable for the performance - or failure of the - Tangem Wallet and/ or Official Mobile Applications to perform under these Terms of - Use and any other act - or omission by Tangem by any cause whatsoever including without limitation damages - arising from the - conduct of any users, third party services and/or exchanges. In no way Tangem or - contributors to - Official Mobile Application are responsible for the actions, decisions, or other - behavior taken or not - taken by Cardholder in reliance upon Tangem Official Mobile Application.

      -


    6. -
    7. - You hereby - acknowledge and agree that these limitations of liability are agreed allocations of - risk constituting in - part the consideration for using the Tangem Wallet and Official Mobile Applications - and such limitations - will apply notwithstanding the failure of essential purpose of any limited remedy, - and even if Tangem - and/or any Tangem Affiliates has been advised of the possibility of such liabilities - and/or damages.

      -


    8. -
    9. - Tangem will not be - responsible for any losses, damages or claims arising from events falling within the - scope of the - following five categories:

      -
        -
      1. - Mistakes made by Cardholder, e.g., forgotten passwords, payments sent to - wrong addresses, and - accidental deletion of blockchain wallets on Tangem Wallet or SaltPay Oly - cards.

      2. -
      3. - Problems of - Official Mobile Application and/or any blockchain- or cryptocurrency- - related software or - service, e.g., corrupted files, incorrectly constructed transactions, unsafe - cryptographic - libraries, malware.

      4. -
      5. - Technical - failures in the hardware of Cardholder, including cards, of any blockchain- - or - cryptocurrency-related software or service, e.g., data loss due to a faulty - or damaged storage - device.

      6. -
      7. - Security - problems experienced by Cardholder, - e.g., unauthorized - access to Cardholders' wallets and/or accounts.

      8. -
      9. - Actions or - inactions of third parties and/or events experienced by third parties, e.g., - bankruptcy of - service providers, information security attacks on service providers, and - fraud conducted by - third parties.

        -


      10. -
      -
    10. -
    -
  18. -
  19. - SEVERABILITY

    -


    -
      -
    1. - In - the event any court shall declare any section or sections of this Agreement invalid - or void, such - declaration shall not invalidate the entire Agreement and all other paragraphs of - the Agreement shall - remain in full force and effect.

      -


    2. -
    -
  20. -
  21. - ARBITRATION AND - GOVERNING LAW

    -


    -
      -
    1. - These Terms of Service are governed exclusively by the laws of Switzerland, without - regard to its - conflict of law rules. You consent to the exclusive jurisdiction of the court of - Zug, Switzerland for - any dispute arising under this Agreement.

    2. -
    -
  22. -
-

- Utorg OÜ Terms of Use

-


-

These Terms of - Use constitute an - electronic agreement between you (hereinafter the "User") and Utorg OÜ (hereinafter - the “Utorg.pro”, - “we”, “us”, “our”), a company incorporated in Estonia with Company No. - 14786273, located at - Roosikrantsi tn 2-1068, Kesklinna linnaosa,Tallinn, Harju maakond, 10119, that applies to the - User's use of this - website, any and all services, products and content provided by Utorg.pro.

-


-

As used herein, - "Utorg.pro" refers to the company Utorg OÜ, including but not limited to, its - owners, directors, - investors, officers, employees, agents or other related parties, unless otherwise provided - herein.

-


-

These Terms of - Use contain - important provisions, which the User must consider carefully when choosing whether to use the - services, products and - content of Utorg.pro. Please read these Terms of Use carefully before agreeing to them.

-


-

The User is - solely responsible for - understanding and complying with any and all laws, rules and regulations of his/her specific - jurisdiction that may - be applicable to the User in connection with the use of any and all services, products and - content of Utorg.pro.

-


-

These Terms of Use - incorporate our Privacy Policy - as well as any other policies published on the Site by reference, so by accepting these Terms of Use, the User agrees with and accepts all the policies published on the Site. -

-


-

IF THE USER - DOES NOT ACCEPT THESE - TERMS OF USE, THE USER SHALL NOT USE ANY OF UTORG.PRO’S SERVICES, PRODUCTS AND CONTENT.

-


-
    -
  1. - Terms and - Definitions

    -


    -

    - “Account” - shall mean a record created and maintained by the Software that accumulates and stores - all information about - the Cardholder, including without limitation Transactional history, Cryptocurrency - balance, and the - Cardholder personal information.

    -

    - “SaltPay - Oly Card” shall mean a plastic card linked to the Account. “Cardholder” shall - mean an individual - or entity holding a SaltPay Oly Card.

    -

    - "Confidential - Information" shall mean any data or information, oral or written, treated as - confidential that - relates to either party’s (or, if either party is bound to protect the confidentiality - of any third party’s - information, such third party’s) past, present, or future research, development or - business activities, - including any unannounced products and services, any information relating to Services, - developments, - relevant documentation (in whatever form or media provided), inventions, processes, - plans, financial - information, end-user data, revenue, transaction volume, forecasts, projections, and the - financial terms of - this Agreement. Notwithstanding the foregoing, Confidential Information shall not be - deemed to include - information if: (i) it was already known to the receiving party prior to the Effective - Date of this - Agreement, as established by documentary evidence; (ii) it is in or has entered the - public domain through no - breach of this Agreement or other wrongful act of the receiving party; (iii) it has been - rightfully received - by the receiving party from a third party and without breach of any obligation of - confidentiality of such - third party to the owner of the Confidential Information; (iv) it has been approved for - release by written - authorization of the owner of the Confidential Information; or, (v) it has been - independently developed by a - party without access to or use of the Confidential Information of the other party.”

    -

    - “Cryptocurrency” - shall mean any medium of exchange using cryptography to secure transactions and to - control the creation of - new units, as bitcoins (http://bitcoin.org), or any other that meet those requirements. -

    -

    - “Force-Majeure - Event” shall mean any act or event beyond a Party’s reasonable control, including - without limitation - non-delivery or defective delivery of third party services necessary to provide the - Services (including but - not limited to those of our partners, vendors and suppliers), strikes, lock-outs or - other industrial action - by third parties, civil commotion, riot, invasion, terrorist attack or threat of - terrorist attack, war - (whether declared or not) or threat or

    -

    - preparation for war, fire, explosion, storm, flood, earthquake, subsidence, epidemic or - other natural - disaster, failure of public or private telecommunications networks, worldwide Web - unavailability or - malfunction, governmental prohibition or other limitation of Cryptocurrencies, or - seizing of infrastructure - and operations of Cryptocurrencies.

    -

    - “Over-the-counter - (OTC) deal” shall mean a direct, off-exchange, Transaction between Utorg and - Cardholder. “Price” - shall mean a "price per coin" for which users are willing to purchase or sell - Cryptocurrency using - the Services. “Relevant Exchange Rate” shall mean the weighted spot price average - at the time of the - transaction.

    -

    Services” - shall mean - providing services of exchanging a virtual currency against a fiat currency. - “Site” means Utorg.pro’s website - at https://Utorg.pro.

    -

    Specified - Bank Account” - means the bank account belonging to Salt Pay IIB hf., the details of which shall be - provided to Utorg.pro. -

    -

    Software” - shall mean a - software owned by Utorg and enabling Transactions’ processing.

    -

    - “Transaction” - shall mean any of the following: (i) a purchase of the Cryptocurrency by the Cardholder; - (ii) a sale of the - Cryptocurrency by the Cardholder; (iii) a transfer of Cryptocurrency to a Cardholder. -

    -


  2. -
  3. - Scope of the - Services

    -


    -
      -
    1. - Utorg undertakes to - provide the Cardholder with Services, including but not limited to OTC deal. For the - avoidance of doubt, - Utorg shall use the Relevant Exchange Rate when executing the OTC deal.

      -


    2. -
    3. - The Services will - be provided in such a manner that Utorg will be acting on its own behalf strictly - under the Cardholder’s - instructions regarding any Transaction as well as other matters which the Cardholder - consider to be - necessary hereunder and are accepted by Utorg.

      -


    4. -
    5. - Utorg will treat - the each individual Cardholder as its sole client to which it owes obligations and - will bear no - responsibility to third parties for whom the Cardholder may be acting as an agent, - intermediary or - fiduciary, whether or not the existence or identity of such person(s) has been - disclosed to, or is known - by Utorg.

      -


    6. -
    7. - Utorg does not - provide banking, or payment Services and does not hold or store the Cardholder’s - fiat or crypto - currencies.

      -


    8. -
    -
  4. -
  5. - Warranties

    -


    -

    The - Cardholder hereby warrants - and represents to Utorg as follows:

    -
      -
    1. - It - represents and warrants that all than all outgoing Transactions, cryptocurrency - and/or fiat currency, - are made only to the Specified Bank Account, and Utorg shall not be liable for the - consequences of any - other incoming Transactions.

      -


    2. -
    3. - It also represents - and warrants than all incoming Transactions, cryptocurrency and/or fiat currency, - are made only from its - own wallet and/or bank account, and Utorg shall not be liable for the consequences - of any other incoming - Transactions.

      -


    4. -
    5. - It shall use the - Services in strict conformity with the Terms of Use and for no other purpose and in - no other manner. All - other warranties and representations regarding the use of the Services set forth - below shall be in - addition to and not in limitation of this general overriding warranty and - representation.

      -


    6. -
    7. - The Cardholder represents - and warrants to accept - and comply with all policies posted on Utorg.pro website: https://utorg.pro, Terms of Use (https://utorg.pro/terms/), Privacy Policy - (https://utorg.pro/privacy-policy), KYC/AML Policy - (https://utorg.pro/kyc-aml-policy). -

    8. -
    -
  6. -
  7. Rights - and Obligations

    -


    -
      -
    1. The Cardholder - has right to:

      -


      -
        -
      1. ask - Utorg for any type of assistance and support;

        -


      2. -
      3. - request a confirmation letter stating all information on Cardholder’s - Transactions; and

        -


      4. -
      5. - be promptly notified of any relevant changes, including but not limited to Commissions, bank details, cryptocurrency wallets, etc. -

        -


      6. -
      -
    2. -
    3. - Utorg has the right - to stop providing the Services and terminate the Agreement immediately in case of - any breach of the - Agreement or/and the Terms of Use;

      -


    4. -
    5. Utorg - undertakes to provide the Cardholder with full assistant and support regarding the - Service - provision.

      -


    6. -
    -
  8. -
  9. KYC - Verification

    -


    -
      -
    1. - Identification and - verification procedures (also known as “Know Your Customer” or “KYC”) - are required for all - Transactions, which shall be undertaken by Utorg. If the User refuses to provide - required documents and - information under KYC, including additional information that can be requested after - the business - relationships were established, Utorg.pro reserves the right to immediately - terminate the Services - provision to the User.

      -


    2. -
    3. - The User hereby - authorises Utorg.pro to, directly or indirectly (through third parties), make any - inquiries as - considered necessary to check the relevance and accuracy of the information provided - for verification - purposes. Personal Data transferred will be limited to strictly the necessary and - with security measures - in use to protect the data.

      -


    4. -
    -
  10. -
  11. - Regulatory - Compliance

    -


    -
      -
    1. - For the purposes of - anti-money laundering prevention and combatting terrorist activities, Utorg performs - a thorough due - diligence on each of its customers. Cardholder will have to provide all documents - and information - requested by Utorg for KYC purposesin order to be able to use the Services. Utorg - will keep records of - the Cardholder’s information and documents safe and confidential in accordance with - its Privacy Policy - and the applicable legal and regulatory requirements.

      -


    2. -
    3. - Utorg reserves the - right to request any additional information about the Cardholder and/or the - Cardholder’s Transaction, - and the Cardholder agrees to provide such documents and information in due time. - Utorg has a right to - decline to deliver the Services anytime in case the documents or information - provided by the Cardholder - are unsatisfactory or insufficient, or in case such actions are necessary to comply - with the applicable - laws and regulations, as Utorg may decide in its sole discretion.

      -


    4. -
    5. - The Cardholder will - be able to perform Transactions within certain amounts according to the limits - established by Utorg and - the applicable law. Cardholder will be able to provide some additional information - as instructed by - Utorg to increase or eliminate these limits.

      -


    6. -
    -
  12. -
  13. - Intellectual - Property

    -


    -
      -
    1. - All content at - Utorg's website is the property of Utorg and is protected by copyright, patent, - trademark and any - other applicable laws, unless otherwise specified hereby.

      -


    2. -
    3. - The trademarks, - brands, trade names, service marks and logos of Utorg and others used on the Site - (hereinafter, the - "Trademarks") are the property of Utorg and its respective owners. - The software, - applications, text, images, graphics, data, prices, trades, charts, graphs, video - and audio materials - used on this Site belong to Utorg. The Trademarks and other content on the Platform - and at Utorg's - website should not be copied, reproduced, modified, republished, uploaded, posted, - transmitted, scraped, - collected or distributed in any form or by any means, whether manual or automated. - The use of any - content from the Platform and at Utorg's website on any other site or a - networked computer - environment for any other purpose

      -

      - is - strictly prohibited; any such unauthorized use may violate copyright, patent, - trademark and any - other applicable laws and could result in criminal or civil - penalties/liabilities.

      -


    4. -
    -
  14. -
  15. - Confidentiality

    -


    -
      -
    1. - Any information of - a Party (hereinafter, the “Disclosing Party”) which is not public and which - was obtained by the - other Party (hereinafter, the “Receiving Party”) in the course of performance - of this Agreement, - whether proprietary to such other Party or any third party, whether expressly marked - as confidential or - not and regardless of the form and manner, in which it was obtained, shall be deemed - “Confidential - Information”.

      -


    2. -
    3. With respect to - any and all Confidential Information of the Disclosing Party the Receiving Party - agrees to:

      -


      -
        -
      1. - hold it in - confidence and treat it with the same degree of care and diligence, as it - employs with respect - to its own confidential information, but in no case less than a reasonable - degree of care and - diligence;

        -


      2. -
      3. use - it solely for the purpose of performance of this Agreement;

        -


      4. -
      5. - communicate - it only to the employers and agents of the Receiving Party who have a need - to know such - Confidential Information for the purpose of performance by the Receiving - Party of this Agreement - and who are subject to confidentiality obligations with respect to such - information; and

        -


      6. -
      7. - upon - request of the Disclosing Party or following termination of this Agreement - promptly return to - the Disclosing Party any and all material embodiments of its Confidential - Information and - destroy those material embodiments of Confidential Information return of - which is not - practicable and certify such destruction to the Disclosing Party in - writing.

        -


      8. -
      -
    4. -
    5. - The Parties hereby - agree and acknowledge that restrictions on use of Confidential Information set forth - in Clauses 9.1 and - 9.2 above shall not apply to information, which (subject to the burden of proof - being on the Receiving - Party):

      -


      -
        -
      1. - was known - to the Receiving Party prior to it being obtained from the Disclosing Party - in connection with - this Agreement;

        -


      2. -
      3. - has been - developed or otherwise obtained by the Receiving Party without the use of - the Confidential - Information;

        -


      4. -
      5. has - become public through no fault of the Receiving Party.

        -


      6. -
      -
    6. -
    7. - The Parties hereby - agree that limitations on use and disclosure of Confidential Information as per the - Agreement shall not - apply to any disclosure made in accordance with a valid law, court order, or other - statutory act or - decision of a judicial or governmental authority that has relevant jurisdiction over - the Receiving Party - (the ”Act”), provided that:

      -


      -
        -
      1. The - Receiving Party has provided the Disclosing Party with notice of the Act as - soon as - practicable;

        -


      2. -
      3. - the - Receiving Party has provided all reasonable assistance to the Disclosing - Party (at the - Disclosing Party’s expense) in contesting the Act as the Disclosing Party - had requested;

        -


      4. -
      5. - the - Receiving Party has disclosed only the portion of Confidential Information - which it was obliged - to disclose as per the Act (subject to any alterations due to the Disclosing - Party contesting - the Act).

        -


      6. -
      -
    8. -
    -
  16. -
  17. - Taxation

    -

    - Utorg - makes no representations concerning the tax implications of the Transactions or the - possession or use the - Cryptocurrency. The Cardholder bears the sole responsibility to determine if the - Cryptocurrency or the - potential appreciation or depreciation in the value of the Cryptocurrency over time has - tax implications for - the Cardholder in the

    -

    - Cardholder's - home jurisdiction. By using the Services, and to the extent permitted by law, the - Cardholder agrees not to - hold Utorg liable for any tax liability associated with or arising from the Cardholder's - use of the - Services.

    -


  18. -
  19. - Governing Law

    -

    - This - Agreement shall be governed by the laws of Republic of Estonia. All disputes and - controversies arising out - of or in connection with this Agreement shall be submitted to the relevant Estonian - Court in Tallinn, as the - Court of first instance.

    -


  20. -
  21. - Dispute Resolution

    -


    -
      -
    1. - The Parties - undertake to make every possible effort to resolve disputes that may arise in the - performance of this - Agreement by means of negotiations.

      -


    2. -
    3. - Any dispute, - controversy or claim arising out of or relating to this contract, or the breach - termination or - invalidity thereof, that cannot be solved in the process of negotiations shall be - settled in the - appropriate court according to the Governing Law.

    4. -
    -
  22. -
-
-
-
-
-
-
- - + + + + + file_1666291144852 + + + +

SaltPay Oly Card Terms of Service

+


+

We are delighted that you (“you”, + “your”, or the “Cardholder”) have chosen to use the SaltPay Oly Card (the “Card”). The terms + and conditions set out here (“Terms”) govern your use of your Card as operated by SaltPay.

+

When you use the Card, you also agree to + the SaltPay General Terms + which are incorporated into these Terms by + reference, including any amendments made to those from time to time. If you do not understand any provisions of + these Terms, please contact us before using the Card. For information about how we treat your personal data, + please see our Privacy Policy.

+


+
    +
  1. + Definitions

    +

    + Affiliate: means any entity controlling, controlled by or under common control with that party, where “control” means the ownership of more than 50% of (i) the voting securities or (ii) an interest in the assets, profits, or earnings of an entity. +

    +

    + Blockchain Asset: means a digital data stored in and protected by the blockchain network, including but not limited to ownership right and value of cryptocurrency, tokenized assets, non-fungible asset. +

    +

    Cardholder: a person who holds a SaltPay Oly Card. +

    +

    Private Key: a secret cryptographic key which provides full control over a Blockchain Asset. +

    +

    Public + Key: means a cryptographic key, which provides access to information about a Blockchain Asset, including but not limited to Blockchain Address. +

    +

    SaltPay: Salt Pay IIB hf. +

    +

    SaltPay Oly Card / Card: physical card which stores Private Key and Public Key.

    +

    Services: enabling the use of the SaltPay Oly Card at physical point of sale terminals that are enabled for the acceptance of Visa Debit cards. +

    +

    + Tangem: means Tangem AG, a company incorporated in Switzerland with company number 1315048, whose address is at Baarerstrasse 10, 6300 Zug. +

    +

    Tangem + Mobile Application: Third party mobile application, providing interoperability between SaltPay Oly Card and blockchain, and working on NFC-capable smartphones and tablets using Google Android and Apple iOS operating systems. +

    +

    Tangem + Service: means the service provided by Tangem, which provides and administers the Tangem Mobile Application, which enables Cardholders to generate and store Public Keys and Private Keys in a hardware crypto wallet. +

    +

    Transaction: means any payment made by a Cardholder at any physical POS terminal using the Card. +

    +

    Utorg Service: means providing services of exchanging a virtual currency against a fiat currency. +

    +


  2. +
  3. Important Legal + Stuff

    +

    We think + it’s important that we highlight and clearly explain how SaltPay safeguards its interests, while offering + its services to its Cardholders (you!) which includes limitation of liability and disclaimers. The sections + set out below reflect those safeguards and how they might impact you. It’s therefore important that you read + them carefully. If you’re unsure about any of them, please get in touch.

    +
      +
    1. + These Terms of Service (the “Terms”) govern the use of SaltPay Oly Cards to authorised employees + of SaltPay and its Affiliates.

    2. +
    3. + The Cards may be used for storage of Private Key and Public Key to the Cardholder’s Blockchain Assets + and authentication of Card Transactions with the purpose of transfer of Blockchain Assets for goods and + services purchasing through the Visa payments infrastructure.

      +


    4. +
    5. The Cardholder + agrees that these Terms are binding.

    6. +
    7. The Cardholder shall be + the only person having physical access to the Card.

    8. +
    9. + The Cardholder acknowledges and agrees that SaltPay does not provide backup or recovery of Private Key + and Public Key stored by the Card. The Cardholder creates the backup of the SaltPay Card at the point at + which they activate the Card through the Tangem Mobile Application by linking their Card with Tangem + Wallet.

      +


    10. +
    11. The conditions of + use of the Tangem Wallet and Mobile Application are defined in the terms of service for the Tangem + Wallet and Mobile Application, which will also be available upon downloading the application.

      +


    12. +
    +
  4. +
  5. SaltPay’s + obligations

    +
      +
    1. SaltPay agrees to + provide the Cardholder with the Service in accordance with these Terms and within the agreed scope.

      +
    2. +
    3. Under the condition + that the Cardholder exercises their obligations set out under Clause 4 of these Terms, SaltPay shall use + its best endeavours to ensure that the Card will function properly and without restriction until the + date on which the Card expires.

    4. +
    5. SaltPay shall ensure + that the Card prevents duplication of the Private Key, and that the Cardholder has exclusive control + over the Blockchain Asset unless the contrary is imposed by specific blockchain network rules e.g., two + or more private keys can be used to control the same Blockchain Asset.

      +


    6. +
    7. SaltPay + agrees that it will not keep any records of Cardholder information, save for the Cardholder’s full name, + including but not limited to the amount of Blockchain Asset stored on the Card, the Private Key or + personalised history of Card usage.

      +


    8. +
    9. For the + avoidance of doubt, SaltPay does not guarantee that the operation of Card and/or Tangem Mobile + Application will be secure, accurate, complete, uninterrupted, without error or free of viruses, worms, + other harmful components or other program limitations. SaltPay may, at its sole discretion and without + obligation to do so, correct, modify, amend, enhance, improve and make any other changes to Card.

      +


    10. +
    +
  6. +
  7. The Cardholder’s + obligations

    +
      +
    1. By + accepting these Terms, you represent and warrant that (a) you have the authority to execute and perform + the obligations contained here; (b) you (and your employees, directors, contractors and agents) will + comply with all law applicable to your business and the use of the Card; and (c) you will not use the + Card, directly or indirectly, for any fraudulent or illegal undertaking.

    2. +
    3. + Upon receiving the Card, the Cardholder, if they choose to use the Tangem Service, should download the + Tangem Mobile Application in order to determine the amount of Blockchain Asset stored on the Card.

      +


    4. +
    5. When the Cardholder + pays for goods or services using their SaltPay Oly Card through contactless confirmation using NFC, they + confirm that there is a sufficient balance in the Tangem Wallet and thereby agree that amount is + deducted from the account.

    6. +
    7. + The Cardholder shall at all times keep the Card and the means of access (backup card and pin codes) to + the Card separate from each other.

    8. +
    9. + The Cardholder shall at all times be aware of where the Card is and regularly ensure that it is still in + their possession. The Cardholder shall avoid even temporary possession of the Card by any other person. + If the Card is lost, stolen or destroyed, control over the corresponding Blockchain Asset may be + permanently lost.

    10. +
    11. + The Cardholder shall treat the Card with care and protect the Card from mechanical damage, high + temperatures, strong electromagnetic fields, and other harmful factors.

    12. +
    13. + In the event that the Card is stolen or + lost, and/or if a Cardholder becomes aware of an unauthorised or incorrectly initiated transaction, + the Cardholder shall contact Tangem as soon as possible by sending an email to support@tangem.com + or by using the chat function on the Official Mobile Application.

      +


    14. +
    15. + In the event that the Cardholder wishes + to deactivate their account, they shall contact Tangem by sending an email to support@tangem.com + or by using the chat function on the Official Mobile Application.

      +
    16. +
    17. + The Card can be used only with the official Tangem Mobile Application downloaded from Google Play and + App Store.

    18. +
    19. + The Tangem Mobile Application shall be the exclusive source of information concerning the Blockchain + Address of the Blockchain Asset and corresponding Public Key stored on the Card.

    20. +
    21. + The Cardholder shall only use Near-Field Devices (“NFC Devices”) that are capable of running the + Tangem Mobile Application.

    22. +
    23. + The Card shall be used only for physically tapping and holding near the Cardholder’s NFC Device when the + Tangem Mobile Application requests it. For the avoidance of doubt, the Card cannot be used to pay using + any method which involves gateways.

      +


    24. +
    +
  8. +
  9. The + Cardholder’s liabilities

    +
      +
    1. + The Cardholder is liable for all liabilities arising from the use and misuse of the Card. In any case, + the Cardholder is liable for all transactions authorised using a means of access.

    2. +
    3. + Any loss or damage resulting from the forwarding of the Card and/or means of access shall be borne by + the Cardholder.

    4. +
    5. + Loss or damage incurred by the Cardholder in connection with the possession or use of the Card shall be + borne solely by the Cardholder. SaltPay assumes no liability if the Card cannot be used due to a + technical defect or because it has been cancelled or blocked.

      +


    6. +
    7. Cardholder is only + permitted to use the Products for his personal, non-commercial use. Cardholder is not permitted to + resell the Card.

      +


    8. +
    +
  10. +
  11. SaltPay’s right to + suspend and/or reject payments initiated by the Cardholder

    +
      +
    1. + SaltPay may rightfully suspend any payments initiated by you using the Card in any of the following + circumstances:

      +
        +
      1. + in case of any Transactions which SaltPay suspects may not be valid Transactions, we may suspend + payment until we are satisfied that the Transactions are valid and legitimate;

      2. +
      3. + where SaltPay has requested information from the Cardholder, we may suspend all payments + initiated using the Card until such information has been provided to SaltPay’s satisfaction;

        +
      4. +
      5. + in case of the Cardholder’s violation of these Terms or the terms of service entered into + between the Cardholder and Tangem or Utorg, SaltPay may suspend all payments initiated using the + Card until the violation has been remedied by the Cardholder; and

      6. +
      7. + in case of a suspicion of Cardholder fraud, any illegal activity or a potential violation of the + Terms or the terms of service entered into between you and Tangem or Utorg, SaltPay may suspend + all payments initiated using the Card until an investigation has been concluded by us and we are + satisfied that there has been no Cardholder fraud or violation of the Terms.

      8. +
      +
    2. +
    3. To + the extent permitted by law, we shall notify you of any suspended payment and the reason for suspending + such payment. We reserve the right to change or assert additional reasons for withholding payment should + new information come to SaltPay’s attention that would independently give us a right to suspend in + accordance with clause 6.1.

    4. +
    5. + Once the circumstances entitling us to suspend payment have ended, we will enable the Transaction to be + forwarded on to Tangem, provided that there are no circumstances then in existence which entitle us to + withhold payment.

    6. +
    7. In + the event that a Transaction is found to be invalid or illegitimate, we may rightfully reject the + payment initiated by you and the you will not be able to proceed to access the Tangem Service or Utorg + Service for that Transaction.

    8. +
    9. + For the avoidance of doubt, where evidence of any fraud or illegal activity is found, we may rightfully + terminate the Card and you will no longer have access to the Service, Tangem Service or Utorg + Service.

      +


    10. +
    +
  12. +
  13. + Limitation of liability

    +
      +
    1. + The Card is provided as-is, and except as expressly stated in these Terms, SaltPay provides no express + or implied warranties or conditions, and SaltPay disclaims and excludes any implied terms, + representations, warranties, and conditions with respect to the Card, including warranties of + merchantability, fitness for a particular purpose, title, satisfactory quality and non-infringement, as + well as any other implied warranties, such as warranties regarding data loss, availability, accuracy, + functionality and lack of viruses. These disclaimers will apply except to the extent applicable law does + not permit them. Any warranties, guarantees, or conditions that cannot be disclaimed as a matter of law, + but which may be limited in duration, last for one year from the date on which you receive the Card.

      +
    2. +
    3. + SaltPay shall not be liable and does not warrant or make any representations regarding the services + provided by third parties in relation to the Tangem Service, including but not limited to the Tangem + Mobile Application and the Utorg Service. The relationship between the Cardholder and any third party in + relation to the Tangem Service shall be governed by separate agreements provided below.

    4. +
    5. In + no event shall SaltPay and/or any of its Affiliates be liable for any damages whatsoever, including + direct, indirect, extraordinary, incidental or consequential damages of any kind resulting from or + arising out of the use of Card and/or the Tangem Mobile Application or inability to use Card and/or the + Tangem Mobile Application, failure of Card and/or the Tangem Mobile Application to perform as + represented or expected, loss of goodwill or profits.

    6. +
    7. + SaltPay shall not be held liable for any failure to be able to use the Card and/or the Tangem Mobile + Application for any reason whatsoever, nor will SaltPay be held liable for the loss of the Blockchain + Asset resulting from a malfunction or inoperability of the blockchain network hosting Blockchain Asset, + as well as the inaccessibility of its public servers and services.

    8. +
    9. + SaltPay shall not be held liable for the loss of profits, income, value or any indirect, extraordinary, + consequential, exemplary or punitive damages.

    10. +
    11. SaltPay shall not be + held liable for loss or breakdown of the Card and/or the Tangem Mobile Application.

    12. +
    13. + SaltPay shall not be held liable for any loss of Blockchain Asset in the event of loss or total + breakdown of the Card and/or the Tangem Mobile Application.

    14. +
    15. + The Cardholder hereby acknowledges and agrees that these limitations of liability are agreed allocations + of risk constituting in part the consideration for using the Card and/or the Tangem Mobile Application + and such limitations will apply notwithstanding the failure of essential purpose of any limited

      +

      + remedy, and even if SaltPay and/or any SaltPay Affiliates have been advised of the possibility of + such liabilities and/or damages.

      +


    16. +
    +
  14. +
  15. Term and + Termination

    +
      +
    1. + These Terms become effective upon the date (i) you accept these Terms online (or in another manner + expressly approved by us) or (ii) you first access or use Tangem and shall terminate on the date on + which the Card expires.

      +


    2. +
    3. We may terminate + our arrangement with you under these Terms for any reason or no reason, at any time, by giving 2 months’ + prior notice to this effect. We also reserve the right, pursuant to clause 6, to suspend or terminate + the use of the Card effective immediately.

      +


    4. +
    5. Where your + arrangement with us under these Terms terminates for any reason, you will, where applicable, remain + liable for any fees, charges and other payment obligations you owe us.

      +


    6. +
    +
  16. +
  17. General

    +
      +
    1. + Except as expressly provided herein, these Terms are a complete statement of the agreement between you + and us regarding your use of Tangem. In the event of a conflict between these Terms and the SaltPay + General Terms or any other SaltPay agreement or policy, these Terms shall prevail.

      +


    2. +
    3. These Terms do not + limit any rights that SaltPay may have under trade secret, copyright, patent, or other laws. No waiver + of any term of these Terms shall be deemed a further or continuing waiver of such term or any other + term.

    4. +
    5. In + the event any court shall declare any section or sections of this Agreement invalid or void, such + declaration shall not invalidate the entire Agreement and all other paragraphs of the Agreement shall + remain in full force and effect.

    6. +
    7. For the avoidance of doubt, + provisions around general terms not covered in these Terms of Service, including but not limited to + restrictions and unauthorised or illegal use, intellectual property, data, taxes, indemnification, + warranties, severability, force majeure, and disputes, shall be governed by the SaltPay General + Terms.

    8. +
    +
  18. +
+

TANGEM + WALLET AND TANGEM MOBILE APPLICATION

+

Terms of Service

+


+

PLEASE READ THESE TERMS OF SERVICE + CAREFULLY. BY CLICKING TO ACCEPT, OR BY ACCESSING OR USING OUR SERVICES, YOU AGREE THAT YOU HAVE READ, UNDERSTOOD, + AND ACCEPT ALL OF THE TERMS AND CONDITIONS CONTAINED HEREIN. BY PURCHASE OF THE SALTPAY OLY CARD WITH THE TANGEM + WALLET OR BY USING THE SALTPAY OLY CARD OR BY USING TANGEM WALLET OR BY USING TANGEM MOBILE APPLICATION, YOU + DEMONSTRATE YOUR AGREEMENT TO THESE TERMS AND CONDITIONS CONTAINED HEREIN.

+


+
    +
  1. DEFINITIONS

    +


    +


    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    + “Cardholder

    + means a person who owns Tangem Wallet and SaltPay Oly Card.

    + “Blockchain Asset

    + means a digital data stored in and protected by the blockchain network, including but not limited to + ownership right and value of cryptocurrency, tokenized assets, non-fungible asset.

    + “Blockchain Address

    + means a unique identifier that serves as a virtual location of the Blockchain Asset in the + blockchain.

    + “Card Transaction

    + means transfer of Blockchain Asset from the Blockchain Address associated with the Public Key stored + on the SaltPay Oly Card for goods and services purchased through traditional payments rail.

    + “Official Mobile Application

    + means an application developed and distributed by Tangem, providing interoperability between SaltPay + Oly Card or Tangem Wallet and blockchain, and working on NFC-capable smartphones and tablets using + Google Android and Apple iOS operation systems.

    + “Private Key

    + a secret cryptographic key which provides full control over a Blockchain Asset.

    + “Tangem

    + means Tangem AG, Baarerstrasse 10, 6300 Zug.

    + “Public Key

    + means a cryptographic key, which provides access to information about a Blockchain Asset, including + but not limited to Blockchain Address

    + “Services

    + means the purchase and/or use of the Tangem Wallet and the services or any other features, + technologies or functionalities linked to the Card provided or operated by Tangem AG via the website + or Official Mobile Applications.

    + “Tangem Wallet/Tangem Card / Card

    + means physical card which stores Private Key and Public Key and is used as a backup card for SaltPay + Oly Card.

    +


  2. +
  3. GENERAL + PROVISIONS

    +


    +
      +
    1. + These Terms of Service (the “Terms”) govern the use of Tangem Wallet and/or Official Mobile + Application provided by Tangem AG (referred to as "Tangem", "we" or + "us" in this document) and the related services or any other features, technologies or + functionalities linked to the Tangem Wallet. Tangem is a company

      +

      + incorporated under the laws of Switzerland, with a registered address at Baarerstrasse 10, 6300 Zug, + Switzerland.

      +


    2. +
    3. Tangem Wallet + represents a backup to the SaltPay Oly Card, which may be used only for storage of Private Key and + Public Key to Cardholder’s Blockchain Assets. Tangem Wallet, unlike SaltPay Oly Card, can’t be used for + goods and services purchasing through traditional payments rail. The use of SaltPay Oly Card is governed + by a separate Terms of Service, provided above.

      +


    4. +
    5. Official Mobile + Application is intended for usage only with SaltPay Oly and Tangem Wallet, providing interoperability + between the cards and blockchain via NFC interface. Official Mobile Application DOES NOT:

      +
        +
      1. + Generate, store, transmit, or have access to private (secret) cryptographic keys to blockchain + wallets holding Blockchain assets.

      2. +
      3. Generate, + store, transmit, or have access to secret keys, passwords, passphrases, recovery phrases that + can be used to restore or to copy private (secret) keys to blockchain wallets holding Blockchain + assets.

      4. +
      5. Provide + exchange, trading, investment services on behalf of Tangem.

        +


      6. +
      +
    6. +
    +
  4. +
  5. RIGHTS AND + OBLIGATIONS

    +


    +
      +
    1. Cardholder agrees that + these Terms are binding.

      +


    2. +
    3. Cardholder shall be the + only person having physical access to the Tangem Wallet.

      +


    4. +
    5. + Cardholder acknowledges and agrees that Tangem does not provide backup or recovery of Private Key and + Public Key stored by the Tangem Wallet.

      +


    6. +
    7. Tangem does not + keep records of Cardholder’s personal data, the amount of Blockchain Asset stored on the Card, the + Private Key, or personalized history of Card usage.

      +


    8. +
    +
  6. +
  7. COSTS

    +


    +
      +
    1. + Costs, fees and commission (the “Costs”) may be charged in connection with the use of SaltPay Oly Card. + These Costs are disclosed in the Official Mobile Application to the Cardholder.

      +


    2. +
    3. Amendments to Costs + due to changing expenses or market conditions may be made at any time via adjustments to the fee + schedules. Such amendments shall be communicated to the Cardholder in an appropriate manner. Upon + notification and in the event of the objection, the Cardholder may cancel the SaltPay Oly Card with + immediate effect.

    4. +
    +
  8. +
  9. + CARDHOLDER’S DUTIES OF CARE

    +


    +

    In particular, the Cardholder shall exercise the + following duties of care:

    +


    +
      +
    1. + Upon receiving the SaltPay Oly Card and the Tangem Wallet, the Cardholder should download the Official + Mobile Application from Google Play or Apple app store and create the backup of the SaltPay Oly Card by + linking SaltPay Oly card with Tangem backup wallet following the instructions in the Official Mobile + Application.

      +


    2. +
    3. Cardholder + shall keep the SaltPay Oly Card and Tangem Wallet with care and separate from each other.

      +


    4. +
    5. + Cardholder must always know where the Tangem Wallet is and regularly ensure that it is still in his/her + possession. He/she shall avoid even temporary possession of the Tangem Wallet by any other person.

      +


    6. +
    7. Cardholder shall + treat the Tangem Wallet in the same manner as physical money (cash) and keep it safe. If the SaltPay Oly + Card and Tangem Wallet are lost, stolen or destroyed at the same time, control over the corresponding + Blockchain Asset may be permanently lost.

      +


    8. +
    9. Official Mobile + Application shall be the only source of information about the Blockchain Address of the Blockchain Asset + and corresponding Public Key stored on the SaltPay Oly Card/Tangem Wallet.

      +


    10. +
    11. Cardholder shall + only use Near-Field Devices (the “NFC”) devices that are capable of running Official Mobile + Applications. He/she shall avoid leaving the Tangem Wallet in the proximity of the NFC devices of other + persons.

      +


    12. +
    13. Tangem Wallet shall + be used only for physically tapping and holding near Cardholder’s NFC device when Official Mobile + Application requests it.

      +


    14. +
    15. Cardholder shall + keep the Tangem Wallet with care and protect the Tangem Wallet from mechanical damage, high + temperatures, strong electromagnetic fields, and other harmful factors.

      +


    16. +
    +
  10. +
  11. RIGHTS AND + RESPONSIBILITIES OF CARDHOLDER

    +


    +
      +
    1. + The Cardholder is liable for all liabilities arising from the use of the Tangem Wallet and/or Tangem + Official Mobile Application. As a matter of principle, the Cardholder is liable for any risks resulting + from the misuse of the Tangem Wallet and/or Official Mobile Application. In any case, the Cardholder is + liable for all transactions authorized using a means of access.

      +


    2. +
    3. Any loss or damage + resulting from the forwarding of the Tangem Wallet and/or means of access shall be borne by the + Cardholder.

      +


    4. +
    5. Loss or damage + incurred by the Cardholder in connection with the possession or use of the Tangem Wallet and/or Official + Mobile Application shall be borne solely by the Cardholder. Tangem assumes no liability if the Tangem + Wallet and/or Official Mobile Application cannot be used due to a technical defect or because it has + been canceled, blocked or the spending limit has been adjusted.

      +


    6. +
    7. Cardholder is only + permitted to use the Tangem Wallet and Official Mobile Application for his personal, non-commercial use. + Cardholder is not allowed to resell the Tangem Wallet.

    8. +
    9. + Cardholder is solely responsible to determinate what, if any, taxes apply to Card Transactions. Tangem + or contributors to the Official Mobile Application are NOT responsible for determining the taxes that + apply to Card Transactions.

      +


    10. +
    11. Before Cardholder engages in transactions + using an electronic system, Cardholder should carefully review the rules and regulations of the + third-party provider Utorg OÜ (https://utorg.pro/terms/), + who provides exchange services for SaltPay Oly Card. Online trading has inherent risk due to system + response and access times that may vary due to market conditions, system performance, and other factors. + Cardholder should understand, fully accept and take on these and additional risks before executing any + Card Transactions.

      +


    12. +
    13. There is + considerable exposure to risk in the Blockchain Asset exchange transaction. Any transaction involving + the Blockchain Asset involves risks including, but not limited to, the potential for changing economic + conditions that may substantially affect the price or liquidity of the Blockchain Asset. Investments in + the Blockchain Asset exchange speculation may also be susceptible to sharp rises and falls as the + relevant market values fluctuate. It is for this reason that when speculating in such markets it is + advisable to use only risk capital.

      +


    14. +
    +
  12. +
  13. RESPONSIBILITIES AND + LIABILITIES OF TANGEM

    +


    +
      +
    1. + Tangem does not warrant or make any representations regarding the use, the inability to use or operate, + or the results of the use or operation of the Tangem Wallet and/or Tangem Official Mobile + Application.

      +


    2. +
    3. Tangem does not + provide any backup or recovery of Private Key and Public Key stored on the Tangem Wallet.

      +


    4. +
    5. + Tangem does not keep any records of Cardholder information, the amount of Blockchain Asset stored on the + Tangem Wallet or SaltPay Oly Card, the Private Key, or personalized history of cards usage.

      +


    6. +
    7. Tangem shall not be + held liable for any failure to be able to use the Tangem Wallet and/or Official Mobile Application, for + any reason whatsoever, nor will Tangem be held liable for the loss of the Blockchain Asset resulting + from a malfunction or inoperability of the blockchain network hosting Blockchain Asset, as well as the + inaccessibility of its public servers and services.

      +


    8. +
    9. The Tangem does not + guarantee that the operation of Tangem Wallet and/or Official Mobile Application will be secure, + accurate, complete, uninterrupted, without error or free of viruses, worms, other harmful components or + other program limitations. Tangem may, at its sole discretion and without obligation to do so, correct, + modify, amend, enhance, improve and make any other changes to Tangem Wallet and/or Official Mobile + Application.

      +


    10. +
    11. Tangem shall not be + held liable for the loss of profits, income, value or any indirect, extraordinary, consequential, + exemplary or punitive damages.

      +


    12. +
    13. Tangem shall + not be held liable for loss or breakdown of the Tangem Wallet.

      +


    14. +
    15. + Tangem shall not be held liable for any loss of Blockchain Asset in the event of loss or total breakdown + of the Tangem Wallet or SaltPay Oly Card.

    16. +
    +
  14. +
  15. + GUARANTEES

    +


    +
      +
    1. + Under the condition that the Cardholder exercises the duties of care as stated in Clause 5, Tangem + guarantees that the Tangem Wallet will function properly and without restriction for a period of 2 (two) + years.

      +


    2. +
    3. Tangem guarantees + that the Tangem Wallet prevents duplication of the Private Key and that the Cardholder has exclusive + control over the Blockchain Asset unless the contrary is imposed by specific blockchain network + rules,

      +

      e.g. two or more + private keys can be used to control the same Blockchain Asset.

      +


    4. +
    +
  16. +
  17. + LIMITATION OF LIABILITY

    +


    +
      +
    1. + The Tangem Wallet, including without limitation any content, data and information related thereto, is + provided on an “as is” basis and “as available” basis, without any warranties of any kind, express or + implied warranties of use, merchantability or suitability for a certain purpose or use, including + without limitation, the quality of products and services provided by users, third-party services, and/or + exchanges (except for the guarantees set forth in Clause 8).

      +


      +

      Official Mobile + Application is provided on an “as is” basis and “as available” basis without any warranties of any + kind regarding Official Mobile Application and/or any content, data, materials and/or services + provided on Official Mobile Application.

      +


    2. +
    3. Tangem and its + affiliates, including any of their officers, directors, shareholders, employees, sub-contractors, + agents, parent companies, subsidiaries and other affiliates (collectively, the “Tangem Affiliates”), + jointly and severally, disclaim and make no representations or warranties as to the usability, accuracy, + quality, availability, reliability, suitability, completeness, truthfulness, usefulness or effectiveness + of any content, data, results or other information obtained or generated by Tangem and/or any user + related to you or any other user of the Tangem Wallet, and Official Mobile Applications.

      +


    4. +
    5. In no event shall + Tangem and/or any of the Tangem Affiliates be liable for any damages whatsoever, including direct, + indirect, extraordinary, incidental or consequential damages of any kind, but not limited to, resulting + from or arising out of the use of Tangem Wallet and/or Official Mobile Applications or inability to use + Tangem Wallet and/or Official Mobile Applications, failure of Tangem Wallet and/or Official Mobile + Applications to perform as represented or expected, loss of goodwill or profits, or loss of data arising + out of or in any way connected with the use of Tangem Wallet and/or Official Mobile Applications. In no + event shall Tangem and/or any of the Tangem Affiliates be liable for the performance or failure of the + Tangem Wallet and/ or Official Mobile Applications to perform under these Terms of Use and any other act + or omission by Tangem by any cause whatsoever including without limitation damages arising from the + conduct of any users, third party services and/or exchanges. In no way Tangem or contributors to + Official Mobile Application are responsible for the actions, decisions, or other behavior taken or not + taken by Cardholder in reliance upon Tangem Official Mobile Application.

      +


    6. +
    7. You hereby + acknowledge and agree that these limitations of liability are agreed allocations of risk constituting in + part the consideration for using the Tangem Wallet and Official Mobile Applications and such limitations + will apply notwithstanding the failure of essential purpose of any limited remedy, and even if Tangem + and/or any Tangem Affiliates has been advised of the possibility of such liabilities and/or damages.

      +


    8. +
    9. Tangem will not be + responsible for any losses, damages or claims arising from events falling within the scope of the + following five categories:

      +
        +
      1. + Mistakes made by Cardholder, e.g., forgotten passwords, payments sent to wrong addresses, and + accidental deletion of blockchain wallets on Tangem Wallet or SaltPay Oly cards.

      2. +
      3. Problems of + Official Mobile Application and/or any blockchain- or cryptocurrency- related software or + service, e.g., corrupted files, incorrectly constructed transactions, unsafe cryptographic + libraries, malware.

      4. +
      5. Technical + failures in the hardware of Cardholder, including cards, of any blockchain- or + cryptocurrency-related software or service, e.g., data loss due to a faulty or damaged storage + device.

      6. +
      7. Security + problems experienced by Cardholder, e.g., unauthorized + access to Cardholders' wallets and/or accounts.

      8. +
      9. Actions or + inactions of third parties and/or events experienced by third parties, e.g., bankruptcy of + service providers, information security attacks on service providers, and fraud conducted by + third parties.

        +


      10. +
      +
    10. +
    +
  18. +
  19. SEVERABILITY

    +


    +
      +
    1. In + the event any court shall declare any section or sections of this Agreement invalid or void, such + declaration shall not invalidate the entire Agreement and all other paragraphs of the Agreement shall + remain in full force and effect.

      +


    2. +
    +
  20. +
  21. ARBITRATION AND + GOVERNING LAW

    +


    +
      +
    1. + These Terms of Service are governed exclusively by the laws of Switzerland, without regard to its + conflict of law rules. You consent to the exclusive jurisdiction of the court of Zug, Switzerland for + any dispute arising under this Agreement.

    2. +
    +
  22. +
+

Utorg OÜ Terms of Use

+


+

These Terms of Use constitute an + electronic agreement between you (hereinafter the "User") and Utorg OÜ (hereinafter the “Utorg.pro”, + “we”, “us”, “our”), a company incorporated in Estonia with Company No. 14786273, located at + Roosikrantsi tn 2-1068, Kesklinna linnaosa,Tallinn, Harju maakond, 10119, that applies to the User's use of this + website, any and all services, products and content provided by Utorg.pro.

+


+

As used herein, + "Utorg.pro" refers to the company Utorg OÜ, including but not limited to, its owners, directors, + investors, officers, employees, agents or other related parties, unless otherwise provided herein.

+


+

These Terms of Use contain + important provisions, which the User must consider carefully when choosing whether to use the services, products and + content of Utorg.pro. Please read these Terms of Use carefully before agreeing to them.

+


+

The User is solely responsible for + understanding and complying with any and all laws, rules and regulations of his/her specific jurisdiction that may + be applicable to the User in connection with the use of any and all services, products and content of Utorg.pro.

+


+

These Terms of Use incorporate our Privacy Policy + as well as any other policies published on the Site by reference, so by accepting these Terms of Use, the User agrees with and accepts all the policies published on the Site. +

+


+

IF THE USER DOES NOT ACCEPT THESE + TERMS OF USE, THE USER SHALL NOT USE ANY OF UTORG.PRO’S SERVICES, PRODUCTS AND CONTENT.

+


+
    +
  1. Terms and + Definitions

    +


    +

    Account” + shall mean a record created and maintained by the Software that accumulates and stores all information about + the Cardholder, including without limitation Transactional history, Cryptocurrency balance, and the + Cardholder personal information.

    +

    SaltPay + Oly Card” shall mean a plastic card linked to the Account. “Cardholder” shall mean an individual + or entity holding a SaltPay Oly Card.

    +

    "Confidential + Information" shall mean any data or information, oral or written, treated as confidential that + relates to either party’s (or, if either party is bound to protect the confidentiality of any third party’s + information, such third party’s) past, present, or future research, development or business activities, + including any unannounced products and services, any information relating to Services, developments, + relevant documentation (in whatever form or media provided), inventions, processes, plans, financial + information, end-user data, revenue, transaction volume, forecasts, projections, and the financial terms of + this Agreement. Notwithstanding the foregoing, Confidential Information shall not be deemed to include + information if: (i) it was already known to the receiving party prior to the Effective Date of this + Agreement, as established by documentary evidence; (ii) it is in or has entered the public domain through no + breach of this Agreement or other wrongful act of the receiving party; (iii) it has been rightfully received + by the receiving party from a third party and without breach of any obligation of confidentiality of such + third party to the owner of the Confidential Information; (iv) it has been approved for release by written + authorization of the owner of the Confidential Information; or, (v) it has been independently developed by a + party without access to or use of the Confidential Information of the other party.”

    +

    Cryptocurrency” + shall mean any medium of exchange using cryptography to secure transactions and to control the creation of + new units, as bitcoins (http://bitcoin.org), or any other that meet those requirements.

    +

    Force-Majeure + Event” shall mean any act or event beyond a Party’s reasonable control, including without limitation + non-delivery or defective delivery of third party services necessary to provide the Services (including but + not limited to those of our partners, vendors and suppliers), strikes, lock-outs or other industrial action + by third parties, civil commotion, riot, invasion, terrorist attack or threat of terrorist attack, war + (whether declared or not) or threat or

    +

    + preparation for war, fire, explosion, storm, flood, earthquake, subsidence, epidemic or other natural + disaster, failure of public or private telecommunications networks, worldwide Web unavailability or + malfunction, governmental prohibition or other limitation of Cryptocurrencies, or seizing of infrastructure + and operations of Cryptocurrencies.

    +

    Over-the-counter + (OTC) deal” shall mean a direct, off-exchange, Transaction between Utorg and Cardholder. “Price” + shall mean a "price per coin" for which users are willing to purchase or sell Cryptocurrency using + the Services. “Relevant Exchange Rate” shall mean the weighted spot price average at the time of the + transaction.

    +

    Services” shall mean + providing services of exchanging a virtual currency against a fiat currency. “Site” means Utorg.pro’s website at https://Utorg.pro.

    +

    Specified Bank Account” + means the bank account belonging to Salt Pay IIB hf., the details of which shall be provided to Utorg.pro. +

    +

    Software” shall mean a + software owned by Utorg and enabling Transactions’ processing.

    +

    Transaction” + shall mean any of the following: (i) a purchase of the Cryptocurrency by the Cardholder; (ii) a sale of the + Cryptocurrency by the Cardholder; (iii) a transfer of Cryptocurrency to a Cardholder.

    +


  2. +
  3. Scope of the + Services

    +


    +
      +
    1. Utorg undertakes to + provide the Cardholder with Services, including but not limited to OTC deal. For the avoidance of doubt, + Utorg shall use the Relevant Exchange Rate when executing the OTC deal.

      +


    2. +
    3. The Services will + be provided in such a manner that Utorg will be acting on its own behalf strictly under the Cardholder’s + instructions regarding any Transaction as well as other matters which the Cardholder consider to be + necessary hereunder and are accepted by Utorg.

      +


    4. +
    5. Utorg will treat + the each individual Cardholder as its sole client to which it owes obligations and will bear no + responsibility to third parties for whom the Cardholder may be acting as an agent, intermediary or + fiduciary, whether or not the existence or identity of such person(s) has been disclosed to, or is known + by Utorg.

      +


    6. +
    7. Utorg does not + provide banking, or payment Services and does not hold or store the Cardholder’s fiat or crypto + currencies.

      +


    8. +
    +
  4. +
  5. Warranties

    +


    +

    The Cardholder hereby warrants + and represents to Utorg as follows:

    +
      +
    1. It + represents and warrants that all than all outgoing Transactions, cryptocurrency and/or fiat currency, + are made only to the Specified Bank Account, and Utorg shall not be liable for the consequences of any + other incoming Transactions.

      +


    2. +
    3. It also represents + and warrants than all incoming Transactions, cryptocurrency and/or fiat currency, are made only from its + own wallet and/or bank account, and Utorg shall not be liable for the consequences of any other incoming + Transactions.

      +


    4. +
    5. It shall use the + Services in strict conformity with the Terms of Use and for no other purpose and in no other manner. All + other warranties and representations regarding the use of the Services set forth below shall be in + addition to and not in limitation of this general overriding warranty and representation.

      +


    6. +
    7. The Cardholder represents and warrants to accept + and comply with all policies posted on Utorg.pro website: https://utorg.pro, Terms of Use (https://utorg.pro/terms/), Privacy Policy (https://utorg.pro/privacy-policy), KYC/AML Policy (https://utorg.pro/kyc-aml-policy). +

    8. +
    +
  6. +
  7. Rights + and Obligations

    +


    +
      +
    1. The Cardholder + has right to:

      +


      +
        +
      1. ask + Utorg for any type of assistance and support;

        +


      2. +
      3. + request a confirmation letter stating all information on Cardholder’s Transactions; and

        +


      4. +
      5. + be promptly notified of any relevant changes, including but not limited to Commissions, bank details, cryptocurrency wallets, etc. +

        +


      6. +
      +
    2. +
    3. Utorg has the right + to stop providing the Services and terminate the Agreement immediately in case of any breach of the + Agreement or/and the Terms of Use;

      +


    4. +
    5. Utorg + undertakes to provide the Cardholder with full assistant and support regarding the Service + provision.

      +


    6. +
    +
  8. +
  9. KYC Verification

    +


    +
      +
    1. Identification and + verification procedures (also known as “Know Your Customer” or “KYC”) are required for all + Transactions, which shall be undertaken by Utorg. If the User refuses to provide required documents and + information under KYC, including additional information that can be requested after the business + relationships were established, Utorg.pro reserves the right to immediately terminate the Services + provision to the User.

      +


    2. +
    3. The User hereby + authorises Utorg.pro to, directly or indirectly (through third parties), make any inquiries as + considered necessary to check the relevance and accuracy of the information provided for verification + purposes. Personal Data transferred will be limited to strictly the necessary and with security measures + in use to protect the data.

      +


    4. +
    +
  10. +
  11. Regulatory + Compliance

    +


    +
      +
    1. For the purposes of + anti-money laundering prevention and combatting terrorist activities, Utorg performs a thorough due + diligence on each of its customers. Cardholder will have to provide all documents and information + requested by Utorg for KYC purposesin order to be able to use the Services. Utorg will keep records of + the Cardholder’s information and documents safe and confidential in accordance with its Privacy Policy + and the applicable legal and regulatory requirements.

      +


    2. +
    3. Utorg reserves the + right to request any additional information about the Cardholder and/or the Cardholder’s Transaction, + and the Cardholder agrees to provide such documents and information in due time. Utorg has a right to + decline to deliver the Services anytime in case the documents or information provided by the Cardholder + are unsatisfactory or insufficient, or in case such actions are necessary to comply with the applicable + laws and regulations, as Utorg may decide in its sole discretion.

      +


    4. +
    5. The Cardholder will + be able to perform Transactions within certain amounts according to the limits established by Utorg and + the applicable law. Cardholder will be able to provide some additional information as instructed by + Utorg to increase or eliminate these limits.

      +


    6. +
    +
  12. +
  13. Intellectual + Property

    +


    +
      +
    1. All content at + Utorg's website is the property of Utorg and is protected by copyright, patent, trademark and any + other applicable laws, unless otherwise specified hereby.

      +


    2. +
    3. The trademarks, + brands, trade names, service marks and logos of Utorg and others used on the Site (hereinafter, the + "Trademarks") are the property of Utorg and its respective owners. The software, + applications, text, images, graphics, data, prices, trades, charts, graphs, video and audio materials + used on this Site belong to Utorg. The Trademarks and other content on the Platform and at Utorg's + website should not be copied, reproduced, modified, republished, uploaded, posted, transmitted, scraped, + collected or distributed in any form or by any means, whether manual or automated. The use of any + content from the Platform and at Utorg's website on any other site or a networked computer + environment for any other purpose

      +

      is + strictly prohibited; any such unauthorized use may violate copyright, patent, trademark and any + other applicable laws and could result in criminal or civil penalties/liabilities.

      +


    4. +
    +
  14. +
  15. Confidentiality

    +


    +
      +
    1. Any information of + a Party (hereinafter, the “Disclosing Party”) which is not public and which was obtained by the + other Party (hereinafter, the “Receiving Party”) in the course of performance of this Agreement, + whether proprietary to such other Party or any third party, whether expressly marked as confidential or + not and regardless of the form and manner, in which it was obtained, shall be deemed “Confidential + Information”.

      +


    2. +
    3. With respect to + any and all Confidential Information of the Disclosing Party the Receiving Party agrees to:

      +


      +
        +
      1. hold it in + confidence and treat it with the same degree of care and diligence, as it employs with respect + to its own confidential information, but in no case less than a reasonable degree of care and + diligence;

        +


      2. +
      3. use + it solely for the purpose of performance of this Agreement;

        +


      4. +
      5. communicate + it only to the employers and agents of the Receiving Party who have a need to know such + Confidential Information for the purpose of performance by the Receiving Party of this Agreement + and who are subject to confidentiality obligations with respect to such information; and

        +


      6. +
      7. upon + request of the Disclosing Party or following termination of this Agreement promptly return to + the Disclosing Party any and all material embodiments of its Confidential Information and + destroy those material embodiments of Confidential Information return of which is not + practicable and certify such destruction to the Disclosing Party in writing.

        +


      8. +
      +
    4. +
    5. The Parties hereby + agree and acknowledge that restrictions on use of Confidential Information set forth in Clauses 9.1 and + 9.2 above shall not apply to information, which (subject to the burden of proof being on the Receiving + Party):

      +


      +
        +
      1. was known + to the Receiving Party prior to it being obtained from the Disclosing Party in connection with + this Agreement;

        +


      2. +
      3. has been + developed or otherwise obtained by the Receiving Party without the use of the Confidential + Information;

        +


      4. +
      5. has + become public through no fault of the Receiving Party.

        +


      6. +
      +
    6. +
    7. The Parties hereby + agree that limitations on use and disclosure of Confidential Information as per the Agreement shall not + apply to any disclosure made in accordance with a valid law, court order, or other statutory act or + decision of a judicial or governmental authority that has relevant jurisdiction over the Receiving Party + (the ”Act”), provided that:

      +


      +
        +
      1. The + Receiving Party has provided the Disclosing Party with notice of the Act as soon as + practicable;

        +


      2. +
      3. the + Receiving Party has provided all reasonable assistance to the Disclosing Party (at the + Disclosing Party’s expense) in contesting the Act as the Disclosing Party had requested;

        +


      4. +
      5. the + Receiving Party has disclosed only the portion of Confidential Information which it was obliged + to disclose as per the Act (subject to any alterations due to the Disclosing Party contesting + the Act).

        +


      6. +
      +
    8. +
    +
  16. +
  17. Taxation

    +

    Utorg + makes no representations concerning the tax implications of the Transactions or the possession or use the + Cryptocurrency. The Cardholder bears the sole responsibility to determine if the Cryptocurrency or the + potential appreciation or depreciation in the value of the Cryptocurrency over time has tax implications for + the Cardholder in the

    +

    Cardholder's + home jurisdiction. By using the Services, and to the extent permitted by law, the Cardholder agrees not to + hold Utorg liable for any tax liability associated with or arising from the Cardholder's use of the + Services.

    +


  18. +
  19. + Governing Law

    +

    This + Agreement shall be governed by the laws of Republic of Estonia. All disputes and controversies arising out + of or in connection with this Agreement shall be submitted to the relevant Estonian Court in Tallinn, as the + Court of first instance.

    +


  20. +
  21. + Dispute Resolution

    +


    +
      +
    1. The Parties + undertake to make every possible effort to resolve disputes that may arise in the performance of this + Agreement by means of negotiations.

      +


    2. +
    3. Any dispute, + controversy or claim arising out of or relating to this contract, or the breach termination or + invalidity thereof, that cannot be solved in the process of negotiations shall be settled in the + appropriate court according to the Governing Law.

    4. +
    +
  22. +
+
+
+
+
+
+
+ + From 6690bcc0c30307b62b6b54a3f94044b48797f53f Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 22 Oct 2022 18:16:05 +0500 Subject: [PATCH 095/113] Updated on 2026-08-14 --- .../src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain/src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt b/domain/src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt index cd122d1a34..c397160c2d 100644 --- a/domain/src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt +++ b/domain/src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt @@ -10,7 +10,7 @@ object SaltPayWorkaround { return when (blockchain) { Blockchain.SaltPay -> Token( name = "WXDAI", - symbol = "WXDAI", + symbol = "wxDAI", contractAddress = "0x4346186e7461cB4DF06bCFCB4cD591423022e417", decimals = 18, id = "wrapped-xdai", From c32cfdeda6ea307e225f17e24db81e206a28ca1f Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 22 Oct 2022 18:18:19 +0500 Subject: [PATCH 096/113] Updated on 2026-08-14 --- .../toggleWidget/RefreshBalanceWidget.kt | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/toggleWidget/RefreshBalanceWidget.kt b/app/src/main/java/com/tangem/tap/common/toggleWidget/RefreshBalanceWidget.kt index 277f839c05..7bccdffc08 100644 --- a/app/src/main/java/com/tangem/tap/common/toggleWidget/RefreshBalanceWidget.kt +++ b/app/src/main/java/com/tangem/tap/common/toggleWidget/RefreshBalanceWidget.kt @@ -2,7 +2,14 @@ package com.tangem.tap.common.toggleWidget import android.view.View import android.view.ViewGroup -import android.view.animation.* +import android.view.animation.AccelerateInterpolator +import android.view.animation.AlphaAnimation +import android.view.animation.Animation +import android.view.animation.AnimationSet +import android.view.animation.AnticipateOvershootInterpolator +import android.view.animation.LinearInterpolator +import android.view.animation.RotateAnimation +import android.view.animation.ScaleAnimation import android.widget.ViewSwitcher import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.wallet.R @@ -11,7 +18,7 @@ import com.tangem.wallet.R [REDACTED_AUTHOR] */ class RefreshBalanceWidget( - private val root: ViewGroup + private val root: ViewGroup, ) : ViewStateWidget { var isShowing: Boolean? = null @@ -19,7 +26,6 @@ class RefreshBalanceWidget( override val mainView: View = root.findViewById(R.id.btn_refresh_balance) - private val viewSwitcher: ViewSwitcher by lazy { mainView.findViewById(R.id.switcher_refresh_arrow_to_progress) } private val arrowView = mainView.findViewById(R.id.imv_arrow_refresh) @@ -46,6 +52,7 @@ class RefreshBalanceWidget( currentStateByView == ProgressState.Done -> if (progressState == ProgressState.Error) return } + mainView.isClickable = currentStateByView == ProgressState.Done || currentStateByView == ProgressState.Error animateState(progressState) viewSwitcher.showNext() } @@ -60,7 +67,7 @@ class RefreshBalanceWidget( progressViewAnimation = RotateAnimation( 0f, 360f, Animation.RELATIVE_TO_SELF, 0.5f, - Animation.RELATIVE_TO_SELF, 0.5f + Animation.RELATIVE_TO_SELF, 0.5f, ) progressViewAnimation?.duration = 700 progressViewAnimation?.interpolator = AccelerateInterpolator() @@ -88,11 +95,13 @@ class RefreshBalanceWidget( class ShowAnimation : AnimationSet(true) { init { - addAnimation(ScaleAnimation( - 0f, 1f, - 0f, 1f, - Animation.RELATIVE_TO_SELF, 0.5f, - Animation.RELATIVE_TO_SELF, 0.5f) + addAnimation( + ScaleAnimation( + 0f, 1f, + 0f, 1f, + Animation.RELATIVE_TO_SELF, 0.5f, + Animation.RELATIVE_TO_SELF, 0.5f, + ), ) addAnimation(AlphaAnimation(0f, 1f)) duration = 300 From ad9529676898e15b2aeed713fcde953b3dede778 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 22 Oct 2022 18:18:52 +0500 Subject: [PATCH 097/113] Updated on 2026-08-14 --- app/src/main/java/com/tangem/tap/domain/TangemSigner.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt b/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt index 9e22833682..af5cdb6a5c 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt @@ -14,6 +14,7 @@ class TangemSigner( private val card: Card, private val tangemSdk: TangemSdk, private val initialMessage: Message, + private val accessCode: String? = null, private val signerCallback: (TangemSignerResponse) -> Unit, ) : TransactionSigner { @@ -29,6 +30,7 @@ class TangemSigner( runnable = task, cardId = cardId, initialMessage = initialMessage, + accessCode = accessCode, ) { result -> when (result) { is CompletionResult.Success -> { From 2a754c50cf7684e3842397b89c50fcbc846cc4b5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 22 Oct 2022 18:24:47 +0500 Subject: [PATCH 098/113] 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', ] From a0e708e46f6abd635d0d52ee4d9deffe194d6eca Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 23 Oct 2022 00:51:16 +0500 Subject: [PATCH 099/113] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- .../com/tangem/tap/common/DialogManager.kt | 15 +-- .../tangem/tap/common/extensions/ImageView.kt | 11 +++ .../tangem/tap/common/redux/StateDialog.kt | 6 +- .../tap/features/home/redux/HomeMiddleware.kt | 19 +--- .../onboarding/OnboardingSaltPayHelper.kt | 4 +- .../note/redux/OnboardingNoteMiddleware.kt | 3 +- .../wallet/redux/OnboardingWalletState.kt | 14 +-- .../wallet/saltPay/SaltPayExceptionHandler.kt | 43 +++++++++ .../redux/OnboardingSaltPayMiddleware.kt | 94 ++++--------------- .../saltPay/redux/OnboardingSaltPayState.kt | 3 +- .../saltPay/ui/OnboardingSaltPayView.kt | 56 +++++++---- .../wallet/ui/dialogs/SimpleOkDialog.kt | 19 +++- app/src/main/res/drawable/ic_reject.xml | 22 +++++ ...ut_onboarding_saltpay_kyc_in_progress.xml} | 15 ++- .../res/layout/layout_onboarding_main.xml | 26 +++-- .../res/layout/layout_onboarding_saltpay.xml | 8 +- ...out_onboarding_saltpay_kyc_in_progress.xml | 87 +++++++++++++++++ ...> layout_onboarding_saltpay_kyc_start.xml} | 0 app/src/main/res/values-de/strings_final.xml | 4 + app/src/main/res/values-fr/strings_final.xml | 4 + app/src/main/res/values-it/strings_final.xml | 4 + app/src/main/res/values-ru/strings_final.xml | 6 +- app/src/main/res/values/strings_final.xml | 4 + .../network/api/paymentology/Responses.kt | 11 +++ 25 files changed, 333 insertions(+), 147 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/common/extensions/ImageView.kt create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt create mode 100644 app/src/main/res/drawable/ic_reject.xml rename app/src/main/res/{layout/layout_onboarding_saltpay_verify_identity_in_progress.xml => layout-h680dp/layout_onboarding_saltpay_kyc_in_progress.xml} (87%) create mode 100644 app/src/main/res/layout/layout_onboarding_saltpay_kyc_in_progress.xml rename app/src/main/res/layout/{layout_onboarding_saltpay_verify_identity.xml => layout_onboarding_saltpay_kyc_start.xml} (100%) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index c8f577f2ab..cf6ea50867 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit c8f577f2ab48e0d6c49f79b6cff21eabe67cd63e +Subproject commit cf6ea50867477f97655da9da47ff94987e8f3354 diff --git a/app/src/main/java/com/tangem/tap/common/DialogManager.kt b/app/src/main/java/com/tangem/tap/common/DialogManager.kt index d3fe7eacd2..0508230bba 100644 --- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt +++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt @@ -54,7 +54,6 @@ class DialogManager : StoreSubscriber { store.unsubscribe(this) } - override fun newState(state: GlobalState) { if (state.dialog == null) { dialog?.dismiss() @@ -67,6 +66,8 @@ class DialogManager : StoreSubscriber { dialog = when (state.dialog) { is AppDialog.SimpleOkDialog -> SimpleOkDialog.create(state.dialog, context) is AppDialog.SimpleOkDialogRes -> SimpleOkDialog.create(state.dialog, context) + is AppDialog.SimpleOkErrorDialog -> SimpleOkDialog.create(state.dialog, context) + is AppDialog.SimpleOkWarningDialog -> SimpleOkDialog.create(state.dialog, context) is AppDialog.ScanFailsDialog -> ScanFailsDialog.create(context) is AppDialog.AddressInfoDialog -> AddressInfoBottomSheetDialog(state.dialog, context) is AppDialog.TestActionsDialog -> TestActionsBottomSheetDialog(state.dialog, context) @@ -76,16 +77,16 @@ class DialogManager : StoreSubscriber { SimpleAlertDialog.create( titleRes = R.string.wallet_connect, messageRes = R.string.wallet_connect_scanner_error_no_ethereum_wallet, - context = context + context = context, ) is WalletConnectDialog.AddNetwork -> SimpleAlertDialog.create( titleRes = R.string.wallet_connect, message = context.getString( R.string.wallet_connect_network_not_found_format, - state.dialog.network + state.dialog.network, ), - context = context + context = context, ) is WalletConnectDialog.OpeningSessionRejected -> { SimpleAlertDialog.create( @@ -123,7 +124,7 @@ class DialogManager : StoreSubscriber { SimpleAlertDialog.create( titleRes = R.string.wallet_connect, messageRes = R.string.wallet_connect_scanner_error_unsupported_network, - context = context + context = context, ) is BackupDialog.AddMoreBackupCards -> AddMoreBackupCardsDialog.create(context) is BackupDialog.BackupInProgress -> BackupInProgressDialog.create(context) @@ -142,7 +143,7 @@ class DialogManager : StoreSubscriber { is WalletDialog.TokensAreLinkedDialog -> SimpleAlertDialog.create( title = context.getString(state.dialog.titleRes, state.dialog.currencySymbol), message = context.getString( - state.dialog.messageRes, state.dialog.currencySymbol, state.dialog.currencyTitle + state.dialog.messageRes, state.dialog.currencySymbol, state.dialog.currencyTitle, ), context = context, ) @@ -151,7 +152,7 @@ class DialogManager : StoreSubscriber { messageRes = state.dialog.messageRes, context = context, primaryButtonRes = state.dialog.primaryButtonRes, - primaryButtonAction = state.dialog.onOk + primaryButtonAction = state.dialog.onOk, ) is WalletDialog.RussianCardholdersWarningDialog -> RussianCardholdersWarningBottomSheetDialog(context) diff --git a/app/src/main/java/com/tangem/tap/common/extensions/ImageView.kt b/app/src/main/java/com/tangem/tap/common/extensions/ImageView.kt new file mode 100644 index 0000000000..0c3a471d54 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.tangem.tap.common.extensions + +import android.widget.ImageView +import androidx.annotation.DrawableRes + +/** +[REDACTED_AUTHOR] + */ +fun ImageView.setDrawable(@DrawableRes resId: Int) { + setImageDrawable(context.getDrawableCompat(resId)) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt b/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt index 89d609b32c..0b30d72ff0 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt @@ -12,10 +12,12 @@ interface StateDialog sealed class AppDialog : StateDialog { data class SimpleOkDialog(val header: String, val message: String, val onOk: VoidCallback? = null) : AppDialog() + data class SimpleOkErrorDialog(val message: String, val onOk: VoidCallback? = null) : AppDialog() + data class SimpleOkWarningDialog(val message: String, val onOk: VoidCallback? = null) : AppDialog() data class SimpleOkDialogRes( val headerId: Int, val messageId: Int, - val onOk: VoidCallback? = null + val onOk: VoidCallback? = null, ) : AppDialog() object ScanFailsDialog : AppDialog() @@ -25,6 +27,6 @@ sealed class AppDialog : StateDialog { ) : AppDialog() data class TestActionsDialog( - val actionsList: List + val actionsList: List, ) : AppDialog() } \ No newline at end of file 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 1354659274..60eaff1abf 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 @@ -10,7 +10,6 @@ import com.tangem.tap.common.analytics.AnalyticsEvent import com.tangem.tap.common.analytics.AnalyticsParam import com.tangem.tap.common.analytics.GetCardSourceParams import com.tangem.tap.common.entities.IndeterminateProgressButton -import com.tangem.tap.common.extensions.dispatchDebugErrorNotification import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchOpenUrl import com.tangem.tap.common.extensions.onCardScanned @@ -28,8 +27,7 @@ import com.tangem.tap.features.onboarding.OnboardingHelper import com.tangem.tap.features.onboarding.OnboardingSaltPayHelper import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep -import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.SaltPayDialog -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayRegistrationError +import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayExceptionHandler import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayState import com.tangem.tap.features.send.redux.states.ButtonState @@ -130,20 +128,7 @@ private fun onScanSuccess(scanResponse: ScanResponse) { } is Result.Failure -> { changeButtonState(ButtonState.ENABLED) - when (val error = result.error) { - is SaltPayRegistrationError -> { - val dialog = when (error) { - is SaltPayRegistrationError.NoGas -> SaltPayDialog.NoFundsForActivation - else -> SaltPayDialog.RegistrationError(error) - } - store.dispatchDialogShow(dialog) - } - else -> { - store.dispatchDebugErrorNotification( - error.localizedMessage ?: "SaltPay: Unknown error", - ) - } - } + SaltPayExceptionHandler.handle(result.error) } } } 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 a849c4219e..2b4ad93ab0 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 @@ -16,7 +16,7 @@ class OnboardingSaltPayHelper { suspend fun isOnboardingCase( scanResponse: ScanResponse, - manager: SaltPayRegistrationManager + manager: SaltPayRegistrationManager, ): Result { return try { val status = manager.updateSaltPayStatus( @@ -24,7 +24,7 @@ class OnboardingSaltPayHelper { step = SaltPayRegistrationStep.None, ).successOr { return it } - val isRegistrationCase = status.step != SaltPayRegistrationStep.ClaimSuccess + val isRegistrationCase = status.step != SaltPayRegistrationStep.Finished val isBackupCase = scanResponse.card.backupStatus?.isActive == false Result.Success(isRegistrationCase || isBackupCase) } catch (ex: Exception) { 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 335e3fdf46..d990941bd5 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 @@ -20,7 +20,6 @@ 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 @@ -130,7 +129,7 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch store.dispatch(OnboardingNoteAction.Balance.Set(balanceIsLoading)) scope.launch { - val loadedBalance = WalletBalanceFetcher().updateBalance(walletManager) + val loadedBalance = onboardingManager.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 16a3165bfe..3ddaa53339 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 -> 14 + isSaltPay -> 12 else -> 6 } @@ -54,12 +54,12 @@ data class OnboardingWalletState( SaltPayRegistrationStep.None, SaltPayRegistrationStep.NoGas -> 0 SaltPayRegistrationStep.NeedPin -> 7 SaltPayRegistrationStep.CardRegistration -> 8 - SaltPayRegistrationStep.KycIntro -> 9 - SaltPayRegistrationStep.KycStart -> 10 - SaltPayRegistrationStep.KycWaiting -> 11 - SaltPayRegistrationStep.Claim -> 12 - SaltPayRegistrationStep.ClaimInProgress -> 13 - SaltPayRegistrationStep.ClaimSuccess -> getMaxProgress() + SaltPayRegistrationStep.KycIntro, SaltPayRegistrationStep.KycStart, + SaltPayRegistrationStep.KycWaiting, SaltPayRegistrationStep.KycReject, + -> 9 + SaltPayRegistrationStep.Claim -> 10 + SaltPayRegistrationStep.ClaimInProgress -> 11 + SaltPayRegistrationStep.Finished -> getMaxProgress() } } step == OnboardingWalletStep.Done -> getMaxProgress() diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt new file mode 100644 index 0000000000..90e35d1bbd --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt @@ -0,0 +1,43 @@ +package com.tangem.tap.features.onboarding.products.wallet.saltPay + +import com.tangem.common.core.TangemSdkError +import com.tangem.tap.common.extensions.dispatchDialogShow +import com.tangem.tap.common.redux.AppDialog +import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.SaltPayDialog +import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayRegistrationError +import com.tangem.tap.store + +/** +[REDACTED_AUTHOR] + */ +class SaltPayExceptionHandler { + companion object { + fun handle(throwable: Throwable) { + when (throwable) { + is SaltPayRegistrationError -> { + val dialog = when (throwable) { + is SaltPayRegistrationError.NoGas -> SaltPayDialog.NoFundsForActivation + else -> SaltPayDialog.RegistrationError(throwable) + } + store.dispatchDialogShow(dialog) + } + + is TangemSdkError -> { + when (throwable) { + is TangemSdkError.NetworkError -> { + val message = (throwable).customMessage + store.dispatchDialogShow(AppDialog.SimpleOkErrorDialog(message)) + } + else -> { + // do nothing + } + } + } + else -> { + val message = throwable.message ?: "SaltPay unknown error" + store.dispatchDialogShow(AppDialog.SimpleOkErrorDialog(message)) + } + } + } + } +} \ No newline at end of file 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 f0ff158c8f..6bac5540ee 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 @@ -4,24 +4,21 @@ 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 import com.tangem.common.extensions.guard import com.tangem.common.services.Result import com.tangem.domain.common.extensions.successOr import com.tangem.domain.common.extensions.withMainContext +import com.tangem.network.api.paymentology.KYCStatus import com.tangem.network.api.paymentology.RegistrationResponse -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 import com.tangem.tap.features.onboarding.products.wallet.saltPay.AllSymbolsTheSameFilter +import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayExceptionHandler import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayRegistrationManager -import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.SaltPayDialog import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayRegistrationError import com.tangem.tap.scope import com.tangem.tap.store @@ -76,37 +73,9 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App handleInProgress = false withMainContext { - // store.dispatch(OnboardingSaltPayAction.SetStep(updateResult.step)) - store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayRegistrationStep.KycWaiting)) + store.dispatch(OnboardingSaltPayAction.SetStep(updateResult.step)) } } - - // 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 -> { handleInProgress = true @@ -191,7 +160,7 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App store.dispatch(OnboardingSaltPayAction.SetPin(action.pin)) store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayRegistrationStep.CardRegistration)) } catch (error: SaltPayRegistrationError) { - handleError = error + SaltPayExceptionHandler.handle(error) } } is OnboardingSaltPayAction.Claim -> { @@ -237,7 +206,7 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App val state = getState() scope.launch { val balance = state.saltPayManager.getTokenAmount().successOr { - handleException = it.error + SaltPayExceptionHandler.handle(it.error) handleClaimRefreshInProgress = false return@launch } @@ -245,7 +214,7 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App handleInProgress = false handleClaimRefreshInProgress = false dispatchOnMain(OnboardingSaltPayAction.SetTokenBalance(balance)) - dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayRegistrationStep.ClaimSuccess)) + dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayRegistrationStep.Finished)) } } else -> { @@ -283,7 +252,7 @@ suspend fun SaltPayRegistrationManager.updateSaltPayStatus( updateResult = when { saltPayStep == SaltPayRegistrationStep.Claim && fetchedAmountToClaim == null -> { updateResult.copy( - step = SaltPayRegistrationStep.ClaimSuccess, + step = SaltPayRegistrationStep.KycReject, amountToClaim = fetchedAmountToClaim, ) } @@ -334,51 +303,17 @@ private var handleInProgress: Boolean = false private suspend fun onError(error: SaltPayRegistrationError) { withMainContext { handleInProgress = false - handleError = error + SaltPayExceptionHandler.handle(error) } } private suspend fun onException(error: Throwable) { withMainContext { handleInProgress = false - handleException = error + SaltPayExceptionHandler.handle(error) } } -private var handleException: Throwable = Throwable("Init") - set(value) { - field = value - when (value) { - is SaltPayRegistrationError -> handleError = value - else -> { - Timber.e(value) - when (value) { - is TangemSdkError -> { - // store.dispatchErrorNotification(TapError.CustomError(value.customMessage)) - } - else -> { - store.dispatchErrorNotification( - TapError.CustomError(value.message ?: "Unknown SaltPay exception"), - ) - } - } - } - } - } - -private var handleError: SaltPayRegistrationError = SaltPayRegistrationError.Unknown - set(value) { - field = value - when (value) { - SaltPayRegistrationError.NoGas -> { - store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayRegistrationStep.NoGas)) - } - else -> { - store.dispatchDialogShow(SaltPayDialog.RegistrationError(value)) - } - } - } - private suspend fun checkGasIfNeeded( saltPayManager: SaltPayRegistrationManager, step: SaltPayRegistrationStep, @@ -386,7 +321,7 @@ private suspend fun checkGasIfNeeded( Timber.d("checkGasIfNeeded: for step: %s", step) if (step == SaltPayRegistrationStep.KycStart || step == SaltPayRegistrationStep.KycWaiting || - step == SaltPayRegistrationStep.ClaimSuccess + step == SaltPayRegistrationStep.Finished ) { Timber.d("checkGasIfNeeded: no need to check for step: %s", step) return Result.Success(Unit) @@ -451,6 +386,15 @@ fun RegistrationResponse.Item.toSaltPayStep(): SaltPayRegistrationStep { // pinSet is false, go toPin screen pinSet == false -> SaltPayRegistrationStep.NeedPin + kycStatus != null -> { + when (kycStatus) { + KYCStatus.NOT_STARTED, KYCStatus.STARTED -> SaltPayRegistrationStep.KycStart + KYCStatus.WAITING_FOR_APPROVAL -> SaltPayRegistrationStep.KycWaiting + KYCStatus.CORRECTION_REQUESTED, KYCStatus.REJECTED -> SaltPayRegistrationStep.KycReject + KYCStatus.APPROVED -> SaltPayRegistrationStep.Claim + null -> throw UnsupportedOperationException() + } + } // kycDate is set, go to kyc waiting screen kycDate != null -> SaltPayRegistrationStep.KycWaiting 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 4851a49fb3..6e9c2c049b 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 @@ -123,7 +123,8 @@ enum class SaltPayRegistrationStep { KycIntro, KycStart, KycWaiting, + KycReject, Claim, ClaimInProgress, - ClaimSuccess; + Finished; } \ 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 5174bf7329..b7840bf99e 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 @@ -20,6 +20,7 @@ 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 @@ -58,12 +59,15 @@ internal class OnboardingSaltPayView( private val toolbar: MaterialToolbar by lazy { walletFragment.binding.toolbar } private val claimBinding: LayoutOnboardingMainBinding by lazy { walletFragment.bindingSaltPay.claim } + private val topContainer: LayoutOnboardingContainerTopBinding by lazy { claimBinding.onboardingTopContainer } + private val actionContainer: LayoutOnboardingContainerBottomBinding by lazy { claimBinding.onboardingActionContainer } private val btnRefreshBalanceWidget by lazy { RefreshBalanceWidget(claimBinding.onboardingTopContainer.onboardingWalletContainer) } private var progressButton: SaltPayProgressButton? = null + private var previousStep: SaltPayRegistrationStep = SaltPayRegistrationStep.None fun newState(state: OnboardingWalletState) { handleCardArtworks(state) @@ -135,9 +139,10 @@ internal class OnboardingSaltPayView( SaltPayRegistrationStep.KycIntro -> handleKycIntro() SaltPayRegistrationStep.KycStart -> handleKycStart(state) SaltPayRegistrationStep.KycWaiting -> handleKycWaiting() + SaltPayRegistrationStep.KycReject -> handleKycReject() SaltPayRegistrationStep.Claim -> handleClaim(state) SaltPayRegistrationStep.ClaimInProgress -> handleClaim(state) - SaltPayRegistrationStep.ClaimSuccess -> handleClaim(state) + SaltPayRegistrationStep.Finished -> handleClaim(state) } progressButton?.changeState(state.mainButtonState) } @@ -204,22 +209,41 @@ internal class OnboardingSaltPayView( } } - private fun handleKycWaiting() = with(walletFragment.bindingSaltPay) { - toolbar.title = getString(R.string.onboarding_navbar_kyc_waiting) - showOnlyView(verifyIdentityInProgress.root) { - progressButton = SaltPayProgressButton(verifyIdentityInProgress.root) - verifyIdentityInProgress.btnOpenSupportChat.setOnClickListener { - store.dispatch(GlobalAction.OpenChat(SupportInfo())) - } - verifyIdentityInProgress.btnRefresh.setOnClickListener { - store.dispatch(OnboardingSaltPayAction.Update) - } + private fun handleKycWaiting() = with(walletFragment.bindingSaltPay.kycInProgress) { + toolbar.title = getString(R.string.onboarding_navbar_kyc_progress) + showOnlyView(root) + + imvInProgress.setDrawable(R.drawable.ic_in_progress) + tvHeader.text = getString(R.string.onboarding_title_kyc_waiting) + tvBody.text = getString(R.string.onboarding_subtitle_kyc_waiting) + + btnOpenSupportChat.hide() + btnOpenSupportChat.setOnClickListener { + store.dispatch(GlobalAction.OpenChat(SupportInfo())) } + + btnKycAction.text = getString(R.string.onboarding_button_kyc_waiting) + btnKycAction.setOnClickListener { + store.dispatch(OnboardingSaltPayAction.Update) + } + progressButton = SaltPayProgressButton(root) } - private val topContainer: LayoutOnboardingContainerTopBinding by lazy { claimBinding.onboardingTopContainer } - private val actionContainer: LayoutOnboardingContainerBottomBinding by lazy { claimBinding.onboardingActionContainer } - private var previousStep: SaltPayRegistrationStep = SaltPayRegistrationStep.None + private fun handleKycReject() = with(walletFragment.bindingSaltPay.kycInProgress) { + toolbar.title = getString(R.string.onboarding_navbar_kyc_progress) + showOnlyView(root) + + imvInProgress.setDrawable(R.drawable.ic_reject) + tvHeader.text = getString(R.string.onboarding_title_kyc_retry) + tvBody.text = getString(R.string.onboarding_subtitle_kyc_retry) + + btnOpenSupportChat.hide() + btnKycAction.text = getString(R.string.onboarding_button_kyc_start) + btnKycAction.setOnClickListener { + store.dispatch(OnboardingSaltPayAction.Update) + } + progressButton = SaltPayProgressButton(root) + } private fun handleClaim(state: OnboardingSaltPayState) = with(walletFragment.bindingSaltPay) { toolbar.title = getString(R.string.onboarding_navbar_claim) @@ -270,7 +294,7 @@ internal class OnboardingSaltPayView( tvBody.text = getString(R.string.onboarding_subtitle_claim_progress) btnMain.text = getString(R.string.onboarding_button_claim) } - SaltPayRegistrationStep.ClaimSuccess -> { + SaltPayRegistrationStep.Finished -> { tvHeader.setText(R.string.common_success) tvBody.setText(R.string.onboarding_subtitle_success_claim) @@ -350,4 +374,4 @@ internal class OnboardingSaltPayView( } } } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SimpleOkDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SimpleOkDialog.kt index 3fc5187741..d52c26763e 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SimpleOkDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SimpleOkDialog.kt @@ -36,6 +36,23 @@ class SimpleOkDialog { } }.create() } - } + fun create(dialog: AppDialog.SimpleOkErrorDialog, context: Context): AlertDialog = create( + dialog = AppDialog.SimpleOkDialog( + header = context.getString(R.string.common_error), + message = dialog.message, + onOk = dialog.onOk, + ), + context = context, + ) + + fun create(dialog: AppDialog.SimpleOkWarningDialog, context: Context): AlertDialog = create( + dialog = AppDialog.SimpleOkDialog( + header = context.getString(R.string.common_warning), + message = dialog.message, + onOk = dialog.onOk, + ), + context = context, + ) + } } \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_reject.xml b/app/src/main/res/drawable/ic_reject.xml new file mode 100644 index 0000000000..776aa18595 --- /dev/null +++ b/app/src/main/res/drawable/ic_reject.xml @@ -0,0 +1,22 @@ + + + + + + diff --git a/app/src/main/res/layout/layout_onboarding_saltpay_verify_identity_in_progress.xml b/app/src/main/res/layout-h680dp/layout_onboarding_saltpay_kyc_in_progress.xml similarity index 87% rename from app/src/main/res/layout/layout_onboarding_saltpay_verify_identity_in_progress.xml rename to app/src/main/res/layout-h680dp/layout_onboarding_saltpay_kyc_in_progress.xml index c9fb7d611b..4eb63182f8 100644 --- a/app/src/main/res/layout/layout_onboarding_saltpay_verify_identity_in_progress.xml +++ b/app/src/main/res/layout-h680dp/layout_onboarding_saltpay_kyc_in_progress.xml @@ -9,12 +9,19 @@ android:id="@+id/imv_in_progress" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginTop="113dp" + android:layout_marginTop="100dp" android:src="@drawable/ic_in_progress" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + + app:layout_constraintTop_toBottomOf="@+id/guideline1" /> @@ -62,7 +67,7 @@ app:layout_constraintStart_toStartOf="parent"> diff --git a/app/src/main/res/layout/layout_onboarding_main.xml b/app/src/main/res/layout/layout_onboarding_main.xml index ddd34cf81c..29b34f187f 100644 --- a/app/src/main/res/layout/layout_onboarding_main.xml +++ b/app/src/main/res/layout/layout_onboarding_main.xml @@ -1,5 +1,5 @@ - + android:clipChildren="false" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + + android:layout_height="wrap_content" + android:layout_weight="@dimen/home_bottom_weight" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toTopOf="@+id/guideline3" /> - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_onboarding_saltpay.xml b/app/src/main/res/layout/layout_onboarding_saltpay.xml index d837796c42..fbb3534b9b 100644 --- a/app/src/main/res/layout/layout_onboarding_saltpay.xml +++ b/app/src/main/res/layout/layout_onboarding_saltpay.xml @@ -16,7 +16,7 @@ + android:visibility="gone" /> \ No newline at end of file diff --git a/app/src/main/res/layout/layout_onboarding_saltpay_kyc_in_progress.xml b/app/src/main/res/layout/layout_onboarding_saltpay_kyc_in_progress.xml new file mode 100644 index 0000000000..580995fc6f --- /dev/null +++ b/app/src/main/res/layout/layout_onboarding_saltpay_kyc_in_progress.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_onboarding_saltpay_verify_identity.xml b/app/src/main/res/layout/layout_onboarding_saltpay_kyc_start.xml similarity index 100% rename from app/src/main/res/layout/layout_onboarding_saltpay_verify_identity.xml rename to app/src/main/res/layout/layout_onboarding_saltpay_kyc_start.xml diff --git a/app/src/main/res/values-de/strings_final.xml b/app/src/main/res/values-de/strings_final.xml index 1549136e24..60f556f535 100644 --- a/app/src/main/res/values-de/strings_final.xml +++ b/app/src/main/res/values-de/strings_final.xml @@ -149,4 +149,8 @@ Congratulations! Your first payment crypto card has been activated Claiming It will take a few seconds + Something went wrong + Please check you email for further instructions + Do you want to exit the activation process? + In this case, you will need to start from the beginning. \ 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 1549136e24..60f556f535 100644 --- a/app/src/main/res/values-fr/strings_final.xml +++ b/app/src/main/res/values-fr/strings_final.xml @@ -149,4 +149,8 @@ Congratulations! Your first payment crypto card has been activated Claiming It will take a few seconds + Something went wrong + Please check you email for further instructions + Do you want to exit the activation process? + In this case, you will need to start from the beginning. \ 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 1549136e24..60f556f535 100644 --- a/app/src/main/res/values-it/strings_final.xml +++ b/app/src/main/res/values-it/strings_final.xml @@ -149,4 +149,8 @@ Congratulations! Your first payment crypto card has been activated Claiming It will take a few seconds + Something went wrong + Please check you email for further instructions + Do you want to exit the activation process? + In this case, you will need to start from the beginning. \ 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 82ff421103..6ca61fbc79 100644 --- a/app/src/main/res/values-ru/strings_final.xml +++ b/app/src/main/res/values-ru/strings_final.xml @@ -142,9 +142,13 @@ Чат Начало работы Запросить %s - Для начала работы просто запросите начисление xDai на свой кошелек + Для начала работы просто запросите начисление wxDai на свой кошелек Запросить Поздравляем! Ваша платежная крипто карта теперь активирована! Запрашивается Это займет несколько секунд + Что-то пошло не так + Более подробная информация отправлена на ваш адрес электронной почты. + Вы хотите выйти из процесса активации? + В этом случае вам будет необходимо начать процесс заново. diff --git a/app/src/main/res/values/strings_final.xml b/app/src/main/res/values/strings_final.xml index fdbccb6f42..57ee686bed 100644 --- a/app/src/main/res/values/strings_final.xml +++ b/app/src/main/res/values/strings_final.xml @@ -149,4 +149,8 @@ Congratulations! Your first payment crypto card has been activated Claiming It will take a few seconds + Something went wrong + Please check you email for further instructions + Do you want to exit the activation process? + In this case, you will need to start from the beginning. diff --git a/network/src/main/java/com/tangem/network/api/paymentology/Responses.kt b/network/src/main/java/com/tangem/network/api/paymentology/Responses.kt index e70fdd3b1b..260c9e5c9d 100644 --- a/network/src/main/java/com/tangem/network/api/paymentology/Responses.kt +++ b/network/src/main/java/com/tangem/network/api/paymentology/Responses.kt @@ -28,12 +28,23 @@ data class RegistrationResponse( val kycProvider: String?, @Json(name = "kyc_date") val kycDate: String?, + @Json(name = "kyc_status") + val kycStatus: KYCStatus?, @Json(name = "disabled_by_admin") val disabledByAdmin: Boolean?, val error: String?, ) } +enum class KYCStatus { + NOT_STARTED, + STARTED, + WAITING_FOR_APPROVAL, + CORRECTION_REQUESTED, + REJECTED, + APPROVED, +} + data class AttestationResponse( val challenge: ByteArray?, val success: Boolean, From 342f3e4152206c24dcbacc36c8ee584bf639b256 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 23 Oct 2022 00:51:16 +0500 Subject: [PATCH 100/113] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- .../com/tangem/tap/common/DialogManager.kt | 15 +-- .../tangem/tap/common/extensions/ImageView.kt | 11 +++ .../tangem/tap/common/redux/StateDialog.kt | 6 +- .../tap/features/home/redux/HomeMiddleware.kt | 19 +--- .../onboarding/OnboardingSaltPayHelper.kt | 4 +- .../note/redux/OnboardingNoteMiddleware.kt | 3 +- .../wallet/redux/OnboardingWalletState.kt | 14 +-- .../wallet/saltPay/SaltPayExceptionHandler.kt | 43 +++++++++ .../redux/OnboardingSaltPayMiddleware.kt | 94 ++++--------------- .../saltPay/redux/OnboardingSaltPayState.kt | 3 +- .../saltPay/ui/OnboardingSaltPayView.kt | 56 +++++++---- .../wallet/ui/dialogs/SimpleOkDialog.kt | 19 +++- app/src/main/res/drawable/ic_reject.xml | 22 +++++ ...ut_onboarding_saltpay_kyc_in_progress.xml} | 15 ++- .../res/layout/layout_onboarding_main.xml | 26 +++-- .../res/layout/layout_onboarding_saltpay.xml | 8 +- ...out_onboarding_saltpay_kyc_in_progress.xml | 87 +++++++++++++++++ ...> layout_onboarding_saltpay_kyc_start.xml} | 0 app/src/main/res/values-de/strings_final.xml | 4 + app/src/main/res/values-fr/strings_final.xml | 4 + app/src/main/res/values-it/strings_final.xml | 4 + app/src/main/res/values-ru/strings_final.xml | 6 +- app/src/main/res/values/strings_final.xml | 4 + .../network/api/paymentology/Responses.kt | 11 +++ 25 files changed, 333 insertions(+), 147 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/common/extensions/ImageView.kt create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt create mode 100644 app/src/main/res/drawable/ic_reject.xml rename app/src/main/res/{layout/layout_onboarding_saltpay_verify_identity_in_progress.xml => layout-h680dp/layout_onboarding_saltpay_kyc_in_progress.xml} (87%) create mode 100644 app/src/main/res/layout/layout_onboarding_saltpay_kyc_in_progress.xml rename app/src/main/res/layout/{layout_onboarding_saltpay_verify_identity.xml => layout_onboarding_saltpay_kyc_start.xml} (100%) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index c8f577f2ab..cf6ea50867 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit c8f577f2ab48e0d6c49f79b6cff21eabe67cd63e +Subproject commit cf6ea50867477f97655da9da47ff94987e8f3354 diff --git a/app/src/main/java/com/tangem/tap/common/DialogManager.kt b/app/src/main/java/com/tangem/tap/common/DialogManager.kt index d3fe7eacd2..0508230bba 100644 --- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt +++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt @@ -54,7 +54,6 @@ class DialogManager : StoreSubscriber { store.unsubscribe(this) } - override fun newState(state: GlobalState) { if (state.dialog == null) { dialog?.dismiss() @@ -67,6 +66,8 @@ class DialogManager : StoreSubscriber { dialog = when (state.dialog) { is AppDialog.SimpleOkDialog -> SimpleOkDialog.create(state.dialog, context) is AppDialog.SimpleOkDialogRes -> SimpleOkDialog.create(state.dialog, context) + is AppDialog.SimpleOkErrorDialog -> SimpleOkDialog.create(state.dialog, context) + is AppDialog.SimpleOkWarningDialog -> SimpleOkDialog.create(state.dialog, context) is AppDialog.ScanFailsDialog -> ScanFailsDialog.create(context) is AppDialog.AddressInfoDialog -> AddressInfoBottomSheetDialog(state.dialog, context) is AppDialog.TestActionsDialog -> TestActionsBottomSheetDialog(state.dialog, context) @@ -76,16 +77,16 @@ class DialogManager : StoreSubscriber { SimpleAlertDialog.create( titleRes = R.string.wallet_connect, messageRes = R.string.wallet_connect_scanner_error_no_ethereum_wallet, - context = context + context = context, ) is WalletConnectDialog.AddNetwork -> SimpleAlertDialog.create( titleRes = R.string.wallet_connect, message = context.getString( R.string.wallet_connect_network_not_found_format, - state.dialog.network + state.dialog.network, ), - context = context + context = context, ) is WalletConnectDialog.OpeningSessionRejected -> { SimpleAlertDialog.create( @@ -123,7 +124,7 @@ class DialogManager : StoreSubscriber { SimpleAlertDialog.create( titleRes = R.string.wallet_connect, messageRes = R.string.wallet_connect_scanner_error_unsupported_network, - context = context + context = context, ) is BackupDialog.AddMoreBackupCards -> AddMoreBackupCardsDialog.create(context) is BackupDialog.BackupInProgress -> BackupInProgressDialog.create(context) @@ -142,7 +143,7 @@ class DialogManager : StoreSubscriber { is WalletDialog.TokensAreLinkedDialog -> SimpleAlertDialog.create( title = context.getString(state.dialog.titleRes, state.dialog.currencySymbol), message = context.getString( - state.dialog.messageRes, state.dialog.currencySymbol, state.dialog.currencyTitle + state.dialog.messageRes, state.dialog.currencySymbol, state.dialog.currencyTitle, ), context = context, ) @@ -151,7 +152,7 @@ class DialogManager : StoreSubscriber { messageRes = state.dialog.messageRes, context = context, primaryButtonRes = state.dialog.primaryButtonRes, - primaryButtonAction = state.dialog.onOk + primaryButtonAction = state.dialog.onOk, ) is WalletDialog.RussianCardholdersWarningDialog -> RussianCardholdersWarningBottomSheetDialog(context) diff --git a/app/src/main/java/com/tangem/tap/common/extensions/ImageView.kt b/app/src/main/java/com/tangem/tap/common/extensions/ImageView.kt new file mode 100644 index 0000000000..0c3a471d54 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.tangem.tap.common.extensions + +import android.widget.ImageView +import androidx.annotation.DrawableRes + +/** +[REDACTED_AUTHOR] + */ +fun ImageView.setDrawable(@DrawableRes resId: Int) { + setImageDrawable(context.getDrawableCompat(resId)) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt b/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt index 89d609b32c..0b30d72ff0 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt @@ -12,10 +12,12 @@ interface StateDialog sealed class AppDialog : StateDialog { data class SimpleOkDialog(val header: String, val message: String, val onOk: VoidCallback? = null) : AppDialog() + data class SimpleOkErrorDialog(val message: String, val onOk: VoidCallback? = null) : AppDialog() + data class SimpleOkWarningDialog(val message: String, val onOk: VoidCallback? = null) : AppDialog() data class SimpleOkDialogRes( val headerId: Int, val messageId: Int, - val onOk: VoidCallback? = null + val onOk: VoidCallback? = null, ) : AppDialog() object ScanFailsDialog : AppDialog() @@ -25,6 +27,6 @@ sealed class AppDialog : StateDialog { ) : AppDialog() data class TestActionsDialog( - val actionsList: List + val actionsList: List, ) : AppDialog() } \ No newline at end of file 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 1354659274..60eaff1abf 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 @@ -10,7 +10,6 @@ import com.tangem.tap.common.analytics.AnalyticsEvent import com.tangem.tap.common.analytics.AnalyticsParam import com.tangem.tap.common.analytics.GetCardSourceParams import com.tangem.tap.common.entities.IndeterminateProgressButton -import com.tangem.tap.common.extensions.dispatchDebugErrorNotification import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchOpenUrl import com.tangem.tap.common.extensions.onCardScanned @@ -28,8 +27,7 @@ import com.tangem.tap.features.onboarding.OnboardingHelper import com.tangem.tap.features.onboarding.OnboardingSaltPayHelper import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep -import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.SaltPayDialog -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayRegistrationError +import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayExceptionHandler import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayState import com.tangem.tap.features.send.redux.states.ButtonState @@ -130,20 +128,7 @@ private fun onScanSuccess(scanResponse: ScanResponse) { } is Result.Failure -> { changeButtonState(ButtonState.ENABLED) - when (val error = result.error) { - is SaltPayRegistrationError -> { - val dialog = when (error) { - is SaltPayRegistrationError.NoGas -> SaltPayDialog.NoFundsForActivation - else -> SaltPayDialog.RegistrationError(error) - } - store.dispatchDialogShow(dialog) - } - else -> { - store.dispatchDebugErrorNotification( - error.localizedMessage ?: "SaltPay: Unknown error", - ) - } - } + SaltPayExceptionHandler.handle(result.error) } } } 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 a849c4219e..2b4ad93ab0 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 @@ -16,7 +16,7 @@ class OnboardingSaltPayHelper { suspend fun isOnboardingCase( scanResponse: ScanResponse, - manager: SaltPayRegistrationManager + manager: SaltPayRegistrationManager, ): Result { return try { val status = manager.updateSaltPayStatus( @@ -24,7 +24,7 @@ class OnboardingSaltPayHelper { step = SaltPayRegistrationStep.None, ).successOr { return it } - val isRegistrationCase = status.step != SaltPayRegistrationStep.ClaimSuccess + val isRegistrationCase = status.step != SaltPayRegistrationStep.Finished val isBackupCase = scanResponse.card.backupStatus?.isActive == false Result.Success(isRegistrationCase || isBackupCase) } catch (ex: Exception) { 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 335e3fdf46..d990941bd5 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 @@ -20,7 +20,6 @@ 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 @@ -130,7 +129,7 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch store.dispatch(OnboardingNoteAction.Balance.Set(balanceIsLoading)) scope.launch { - val loadedBalance = WalletBalanceFetcher().updateBalance(walletManager) + val loadedBalance = onboardingManager.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 16a3165bfe..3ddaa53339 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 -> 14 + isSaltPay -> 12 else -> 6 } @@ -54,12 +54,12 @@ data class OnboardingWalletState( SaltPayRegistrationStep.None, SaltPayRegistrationStep.NoGas -> 0 SaltPayRegistrationStep.NeedPin -> 7 SaltPayRegistrationStep.CardRegistration -> 8 - SaltPayRegistrationStep.KycIntro -> 9 - SaltPayRegistrationStep.KycStart -> 10 - SaltPayRegistrationStep.KycWaiting -> 11 - SaltPayRegistrationStep.Claim -> 12 - SaltPayRegistrationStep.ClaimInProgress -> 13 - SaltPayRegistrationStep.ClaimSuccess -> getMaxProgress() + SaltPayRegistrationStep.KycIntro, SaltPayRegistrationStep.KycStart, + SaltPayRegistrationStep.KycWaiting, SaltPayRegistrationStep.KycReject, + -> 9 + SaltPayRegistrationStep.Claim -> 10 + SaltPayRegistrationStep.ClaimInProgress -> 11 + SaltPayRegistrationStep.Finished -> getMaxProgress() } } step == OnboardingWalletStep.Done -> getMaxProgress() diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt new file mode 100644 index 0000000000..90e35d1bbd --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt @@ -0,0 +1,43 @@ +package com.tangem.tap.features.onboarding.products.wallet.saltPay + +import com.tangem.common.core.TangemSdkError +import com.tangem.tap.common.extensions.dispatchDialogShow +import com.tangem.tap.common.redux.AppDialog +import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.SaltPayDialog +import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayRegistrationError +import com.tangem.tap.store + +/** +[REDACTED_AUTHOR] + */ +class SaltPayExceptionHandler { + companion object { + fun handle(throwable: Throwable) { + when (throwable) { + is SaltPayRegistrationError -> { + val dialog = when (throwable) { + is SaltPayRegistrationError.NoGas -> SaltPayDialog.NoFundsForActivation + else -> SaltPayDialog.RegistrationError(throwable) + } + store.dispatchDialogShow(dialog) + } + + is TangemSdkError -> { + when (throwable) { + is TangemSdkError.NetworkError -> { + val message = (throwable).customMessage + store.dispatchDialogShow(AppDialog.SimpleOkErrorDialog(message)) + } + else -> { + // do nothing + } + } + } + else -> { + val message = throwable.message ?: "SaltPay unknown error" + store.dispatchDialogShow(AppDialog.SimpleOkErrorDialog(message)) + } + } + } + } +} \ No newline at end of file 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 f0ff158c8f..6bac5540ee 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 @@ -4,24 +4,21 @@ 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 import com.tangem.common.extensions.guard import com.tangem.common.services.Result import com.tangem.domain.common.extensions.successOr import com.tangem.domain.common.extensions.withMainContext +import com.tangem.network.api.paymentology.KYCStatus import com.tangem.network.api.paymentology.RegistrationResponse -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 import com.tangem.tap.features.onboarding.products.wallet.saltPay.AllSymbolsTheSameFilter +import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayExceptionHandler import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayRegistrationManager -import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.SaltPayDialog import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayRegistrationError import com.tangem.tap.scope import com.tangem.tap.store @@ -76,37 +73,9 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App handleInProgress = false withMainContext { - // store.dispatch(OnboardingSaltPayAction.SetStep(updateResult.step)) - store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayRegistrationStep.KycWaiting)) + store.dispatch(OnboardingSaltPayAction.SetStep(updateResult.step)) } } - - // 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 -> { handleInProgress = true @@ -191,7 +160,7 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App store.dispatch(OnboardingSaltPayAction.SetPin(action.pin)) store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayRegistrationStep.CardRegistration)) } catch (error: SaltPayRegistrationError) { - handleError = error + SaltPayExceptionHandler.handle(error) } } is OnboardingSaltPayAction.Claim -> { @@ -237,7 +206,7 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App val state = getState() scope.launch { val balance = state.saltPayManager.getTokenAmount().successOr { - handleException = it.error + SaltPayExceptionHandler.handle(it.error) handleClaimRefreshInProgress = false return@launch } @@ -245,7 +214,7 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App handleInProgress = false handleClaimRefreshInProgress = false dispatchOnMain(OnboardingSaltPayAction.SetTokenBalance(balance)) - dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayRegistrationStep.ClaimSuccess)) + dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayRegistrationStep.Finished)) } } else -> { @@ -283,7 +252,7 @@ suspend fun SaltPayRegistrationManager.updateSaltPayStatus( updateResult = when { saltPayStep == SaltPayRegistrationStep.Claim && fetchedAmountToClaim == null -> { updateResult.copy( - step = SaltPayRegistrationStep.ClaimSuccess, + step = SaltPayRegistrationStep.KycReject, amountToClaim = fetchedAmountToClaim, ) } @@ -334,51 +303,17 @@ private var handleInProgress: Boolean = false private suspend fun onError(error: SaltPayRegistrationError) { withMainContext { handleInProgress = false - handleError = error + SaltPayExceptionHandler.handle(error) } } private suspend fun onException(error: Throwable) { withMainContext { handleInProgress = false - handleException = error + SaltPayExceptionHandler.handle(error) } } -private var handleException: Throwable = Throwable("Init") - set(value) { - field = value - when (value) { - is SaltPayRegistrationError -> handleError = value - else -> { - Timber.e(value) - when (value) { - is TangemSdkError -> { - // store.dispatchErrorNotification(TapError.CustomError(value.customMessage)) - } - else -> { - store.dispatchErrorNotification( - TapError.CustomError(value.message ?: "Unknown SaltPay exception"), - ) - } - } - } - } - } - -private var handleError: SaltPayRegistrationError = SaltPayRegistrationError.Unknown - set(value) { - field = value - when (value) { - SaltPayRegistrationError.NoGas -> { - store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayRegistrationStep.NoGas)) - } - else -> { - store.dispatchDialogShow(SaltPayDialog.RegistrationError(value)) - } - } - } - private suspend fun checkGasIfNeeded( saltPayManager: SaltPayRegistrationManager, step: SaltPayRegistrationStep, @@ -386,7 +321,7 @@ private suspend fun checkGasIfNeeded( Timber.d("checkGasIfNeeded: for step: %s", step) if (step == SaltPayRegistrationStep.KycStart || step == SaltPayRegistrationStep.KycWaiting || - step == SaltPayRegistrationStep.ClaimSuccess + step == SaltPayRegistrationStep.Finished ) { Timber.d("checkGasIfNeeded: no need to check for step: %s", step) return Result.Success(Unit) @@ -451,6 +386,15 @@ fun RegistrationResponse.Item.toSaltPayStep(): SaltPayRegistrationStep { // pinSet is false, go toPin screen pinSet == false -> SaltPayRegistrationStep.NeedPin + kycStatus != null -> { + when (kycStatus) { + KYCStatus.NOT_STARTED, KYCStatus.STARTED -> SaltPayRegistrationStep.KycStart + KYCStatus.WAITING_FOR_APPROVAL -> SaltPayRegistrationStep.KycWaiting + KYCStatus.CORRECTION_REQUESTED, KYCStatus.REJECTED -> SaltPayRegistrationStep.KycReject + KYCStatus.APPROVED -> SaltPayRegistrationStep.Claim + null -> throw UnsupportedOperationException() + } + } // kycDate is set, go to kyc waiting screen kycDate != null -> SaltPayRegistrationStep.KycWaiting 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 4851a49fb3..6e9c2c049b 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 @@ -123,7 +123,8 @@ enum class SaltPayRegistrationStep { KycIntro, KycStart, KycWaiting, + KycReject, Claim, ClaimInProgress, - ClaimSuccess; + Finished; } \ 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 5174bf7329..b7840bf99e 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 @@ -20,6 +20,7 @@ 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 @@ -58,12 +59,15 @@ internal class OnboardingSaltPayView( private val toolbar: MaterialToolbar by lazy { walletFragment.binding.toolbar } private val claimBinding: LayoutOnboardingMainBinding by lazy { walletFragment.bindingSaltPay.claim } + private val topContainer: LayoutOnboardingContainerTopBinding by lazy { claimBinding.onboardingTopContainer } + private val actionContainer: LayoutOnboardingContainerBottomBinding by lazy { claimBinding.onboardingActionContainer } private val btnRefreshBalanceWidget by lazy { RefreshBalanceWidget(claimBinding.onboardingTopContainer.onboardingWalletContainer) } private var progressButton: SaltPayProgressButton? = null + private var previousStep: SaltPayRegistrationStep = SaltPayRegistrationStep.None fun newState(state: OnboardingWalletState) { handleCardArtworks(state) @@ -135,9 +139,10 @@ internal class OnboardingSaltPayView( SaltPayRegistrationStep.KycIntro -> handleKycIntro() SaltPayRegistrationStep.KycStart -> handleKycStart(state) SaltPayRegistrationStep.KycWaiting -> handleKycWaiting() + SaltPayRegistrationStep.KycReject -> handleKycReject() SaltPayRegistrationStep.Claim -> handleClaim(state) SaltPayRegistrationStep.ClaimInProgress -> handleClaim(state) - SaltPayRegistrationStep.ClaimSuccess -> handleClaim(state) + SaltPayRegistrationStep.Finished -> handleClaim(state) } progressButton?.changeState(state.mainButtonState) } @@ -204,22 +209,41 @@ internal class OnboardingSaltPayView( } } - private fun handleKycWaiting() = with(walletFragment.bindingSaltPay) { - toolbar.title = getString(R.string.onboarding_navbar_kyc_waiting) - showOnlyView(verifyIdentityInProgress.root) { - progressButton = SaltPayProgressButton(verifyIdentityInProgress.root) - verifyIdentityInProgress.btnOpenSupportChat.setOnClickListener { - store.dispatch(GlobalAction.OpenChat(SupportInfo())) - } - verifyIdentityInProgress.btnRefresh.setOnClickListener { - store.dispatch(OnboardingSaltPayAction.Update) - } + private fun handleKycWaiting() = with(walletFragment.bindingSaltPay.kycInProgress) { + toolbar.title = getString(R.string.onboarding_navbar_kyc_progress) + showOnlyView(root) + + imvInProgress.setDrawable(R.drawable.ic_in_progress) + tvHeader.text = getString(R.string.onboarding_title_kyc_waiting) + tvBody.text = getString(R.string.onboarding_subtitle_kyc_waiting) + + btnOpenSupportChat.hide() + btnOpenSupportChat.setOnClickListener { + store.dispatch(GlobalAction.OpenChat(SupportInfo())) } + + btnKycAction.text = getString(R.string.onboarding_button_kyc_waiting) + btnKycAction.setOnClickListener { + store.dispatch(OnboardingSaltPayAction.Update) + } + progressButton = SaltPayProgressButton(root) } - private val topContainer: LayoutOnboardingContainerTopBinding by lazy { claimBinding.onboardingTopContainer } - private val actionContainer: LayoutOnboardingContainerBottomBinding by lazy { claimBinding.onboardingActionContainer } - private var previousStep: SaltPayRegistrationStep = SaltPayRegistrationStep.None + private fun handleKycReject() = with(walletFragment.bindingSaltPay.kycInProgress) { + toolbar.title = getString(R.string.onboarding_navbar_kyc_progress) + showOnlyView(root) + + imvInProgress.setDrawable(R.drawable.ic_reject) + tvHeader.text = getString(R.string.onboarding_title_kyc_retry) + tvBody.text = getString(R.string.onboarding_subtitle_kyc_retry) + + btnOpenSupportChat.hide() + btnKycAction.text = getString(R.string.onboarding_button_kyc_start) + btnKycAction.setOnClickListener { + store.dispatch(OnboardingSaltPayAction.Update) + } + progressButton = SaltPayProgressButton(root) + } private fun handleClaim(state: OnboardingSaltPayState) = with(walletFragment.bindingSaltPay) { toolbar.title = getString(R.string.onboarding_navbar_claim) @@ -270,7 +294,7 @@ internal class OnboardingSaltPayView( tvBody.text = getString(R.string.onboarding_subtitle_claim_progress) btnMain.text = getString(R.string.onboarding_button_claim) } - SaltPayRegistrationStep.ClaimSuccess -> { + SaltPayRegistrationStep.Finished -> { tvHeader.setText(R.string.common_success) tvBody.setText(R.string.onboarding_subtitle_success_claim) @@ -350,4 +374,4 @@ internal class OnboardingSaltPayView( } } } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SimpleOkDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SimpleOkDialog.kt index 3fc5187741..d52c26763e 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SimpleOkDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SimpleOkDialog.kt @@ -36,6 +36,23 @@ class SimpleOkDialog { } }.create() } - } + fun create(dialog: AppDialog.SimpleOkErrorDialog, context: Context): AlertDialog = create( + dialog = AppDialog.SimpleOkDialog( + header = context.getString(R.string.common_error), + message = dialog.message, + onOk = dialog.onOk, + ), + context = context, + ) + + fun create(dialog: AppDialog.SimpleOkWarningDialog, context: Context): AlertDialog = create( + dialog = AppDialog.SimpleOkDialog( + header = context.getString(R.string.common_warning), + message = dialog.message, + onOk = dialog.onOk, + ), + context = context, + ) + } } \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_reject.xml b/app/src/main/res/drawable/ic_reject.xml new file mode 100644 index 0000000000..776aa18595 --- /dev/null +++ b/app/src/main/res/drawable/ic_reject.xml @@ -0,0 +1,22 @@ + + + + + + diff --git a/app/src/main/res/layout/layout_onboarding_saltpay_verify_identity_in_progress.xml b/app/src/main/res/layout-h680dp/layout_onboarding_saltpay_kyc_in_progress.xml similarity index 87% rename from app/src/main/res/layout/layout_onboarding_saltpay_verify_identity_in_progress.xml rename to app/src/main/res/layout-h680dp/layout_onboarding_saltpay_kyc_in_progress.xml index c9fb7d611b..4eb63182f8 100644 --- a/app/src/main/res/layout/layout_onboarding_saltpay_verify_identity_in_progress.xml +++ b/app/src/main/res/layout-h680dp/layout_onboarding_saltpay_kyc_in_progress.xml @@ -9,12 +9,19 @@ android:id="@+id/imv_in_progress" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginTop="113dp" + android:layout_marginTop="100dp" android:src="@drawable/ic_in_progress" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + + app:layout_constraintTop_toBottomOf="@+id/guideline1" /> @@ -62,7 +67,7 @@ app:layout_constraintStart_toStartOf="parent"> diff --git a/app/src/main/res/layout/layout_onboarding_main.xml b/app/src/main/res/layout/layout_onboarding_main.xml index ddd34cf81c..29b34f187f 100644 --- a/app/src/main/res/layout/layout_onboarding_main.xml +++ b/app/src/main/res/layout/layout_onboarding_main.xml @@ -1,5 +1,5 @@ - + android:clipChildren="false" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + + android:layout_height="wrap_content" + android:layout_weight="@dimen/home_bottom_weight" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toTopOf="@+id/guideline3" /> - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_onboarding_saltpay.xml b/app/src/main/res/layout/layout_onboarding_saltpay.xml index d837796c42..fbb3534b9b 100644 --- a/app/src/main/res/layout/layout_onboarding_saltpay.xml +++ b/app/src/main/res/layout/layout_onboarding_saltpay.xml @@ -16,7 +16,7 @@ + android:visibility="gone" /> \ No newline at end of file diff --git a/app/src/main/res/layout/layout_onboarding_saltpay_kyc_in_progress.xml b/app/src/main/res/layout/layout_onboarding_saltpay_kyc_in_progress.xml new file mode 100644 index 0000000000..580995fc6f --- /dev/null +++ b/app/src/main/res/layout/layout_onboarding_saltpay_kyc_in_progress.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_onboarding_saltpay_verify_identity.xml b/app/src/main/res/layout/layout_onboarding_saltpay_kyc_start.xml similarity index 100% rename from app/src/main/res/layout/layout_onboarding_saltpay_verify_identity.xml rename to app/src/main/res/layout/layout_onboarding_saltpay_kyc_start.xml diff --git a/app/src/main/res/values-de/strings_final.xml b/app/src/main/res/values-de/strings_final.xml index 1549136e24..60f556f535 100644 --- a/app/src/main/res/values-de/strings_final.xml +++ b/app/src/main/res/values-de/strings_final.xml @@ -149,4 +149,8 @@ Congratulations! Your first payment crypto card has been activated Claiming It will take a few seconds + Something went wrong + Please check you email for further instructions + Do you want to exit the activation process? + In this case, you will need to start from the beginning. \ 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 1549136e24..60f556f535 100644 --- a/app/src/main/res/values-fr/strings_final.xml +++ b/app/src/main/res/values-fr/strings_final.xml @@ -149,4 +149,8 @@ Congratulations! Your first payment crypto card has been activated Claiming It will take a few seconds + Something went wrong + Please check you email for further instructions + Do you want to exit the activation process? + In this case, you will need to start from the beginning. \ 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 1549136e24..60f556f535 100644 --- a/app/src/main/res/values-it/strings_final.xml +++ b/app/src/main/res/values-it/strings_final.xml @@ -149,4 +149,8 @@ Congratulations! Your first payment crypto card has been activated Claiming It will take a few seconds + Something went wrong + Please check you email for further instructions + Do you want to exit the activation process? + In this case, you will need to start from the beginning. \ 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 82ff421103..6ca61fbc79 100644 --- a/app/src/main/res/values-ru/strings_final.xml +++ b/app/src/main/res/values-ru/strings_final.xml @@ -142,9 +142,13 @@ Чат Начало работы Запросить %s - Для начала работы просто запросите начисление xDai на свой кошелек + Для начала работы просто запросите начисление wxDai на свой кошелек Запросить Поздравляем! Ваша платежная крипто карта теперь активирована! Запрашивается Это займет несколько секунд + Что-то пошло не так + Более подробная информация отправлена на ваш адрес электронной почты. + Вы хотите выйти из процесса активации? + В этом случае вам будет необходимо начать процесс заново. diff --git a/app/src/main/res/values/strings_final.xml b/app/src/main/res/values/strings_final.xml index fdbccb6f42..57ee686bed 100644 --- a/app/src/main/res/values/strings_final.xml +++ b/app/src/main/res/values/strings_final.xml @@ -149,4 +149,8 @@ Congratulations! Your first payment crypto card has been activated Claiming It will take a few seconds + Something went wrong + Please check you email for further instructions + Do you want to exit the activation process? + In this case, you will need to start from the beginning. diff --git a/network/src/main/java/com/tangem/network/api/paymentology/Responses.kt b/network/src/main/java/com/tangem/network/api/paymentology/Responses.kt index e70fdd3b1b..260c9e5c9d 100644 --- a/network/src/main/java/com/tangem/network/api/paymentology/Responses.kt +++ b/network/src/main/java/com/tangem/network/api/paymentology/Responses.kt @@ -28,12 +28,23 @@ data class RegistrationResponse( val kycProvider: String?, @Json(name = "kyc_date") val kycDate: String?, + @Json(name = "kyc_status") + val kycStatus: KYCStatus?, @Json(name = "disabled_by_admin") val disabledByAdmin: Boolean?, val error: String?, ) } +enum class KYCStatus { + NOT_STARTED, + STARTED, + WAITING_FOR_APPROVAL, + CORRECTION_REQUESTED, + REJECTED, + APPROVED, +} + data class AttestationResponse( val challenge: ByteArray?, val success: Boolean, From 7ad88abfd8a7b1530b321fb02178e8a19b972c37 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 24 Oct 2022 13:58:38 +0500 Subject: [PATCH 101/113] Updated on 2026-08-14 --- .../wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 6bac5540ee..1f588097d0 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 @@ -147,11 +147,11 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App return@launch } handleInProgress = false - if (state.readyToClaim()) { - withMainContext { store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayRegistrationStep.Claim)) } - } else { - onError(SaltPayRegistrationError.NoFundsToClaim) + val step = when (state.readyToClaim()) { + true -> SaltPayRegistrationStep.Claim + else -> SaltPayRegistrationStep.Finished } + withMainContext { store.dispatch(OnboardingSaltPayAction.SetStep(step)) } } } is OnboardingSaltPayAction.TrySetPin -> { From ec8612af7f24aec9242bda99dae253bc2769a9a1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 24 Oct 2022 13:51:50 +0400 Subject: [PATCH 102/113] Updated on 2026-08-14 --- .../com/tangem/tap/domain/tokens/UserTokensRepository.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt index 721657cdb8..639fd82b98 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt @@ -68,9 +68,11 @@ class UserTokensRepository( } suspend fun loadBlockchainsToDerive(card: Card): List { - return storageService.getUserTokens(card.getUserId())?.toBlockchainNetworks() ?: storageService.getUserTokens( - card, - ).toBlockchainNetworks() + if (DemoHelper.isDemoCardId(card.cardId)) { + return loadDemoCurrencies().toBlockchainNetworks() + } + return storageService.getUserTokens(card.getUserId())?.toBlockchainNetworks() + ?: storageService.getUserTokens(card).toBlockchainNetworks() } private fun loadDemoCurrencies(): List { From 24dde0c9af9c589fa14300ff30eaa0b4a4ebc3ca Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 24 Oct 2022 15:43:35 +0500 Subject: [PATCH 103/113] Updated on 2026-08-14 --- app/src/main/res/values-de/strings_final.xml | 2 +- app/src/main/res/values-fr/strings_final.xml | 2 +- app/src/main/res/values-it/strings_final.xml | 2 +- app/src/main/res/values-ru/strings_final.xml | 2 +- app/src/main/res/values/strings_final.xml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/res/values-de/strings_final.xml b/app/src/main/res/values-de/strings_final.xml index 60f556f535..384c389dbe 100644 --- a/app/src/main/res/values-de/strings_final.xml +++ b/app/src/main/res/values-de/strings_final.xml @@ -121,7 +121,7 @@ In progress Set PIN code Register - Verify + Verify via Utorg Refresh Connect your card Verify your identity diff --git a/app/src/main/res/values-fr/strings_final.xml b/app/src/main/res/values-fr/strings_final.xml index 60f556f535..384c389dbe 100644 --- a/app/src/main/res/values-fr/strings_final.xml +++ b/app/src/main/res/values-fr/strings_final.xml @@ -121,7 +121,7 @@ In progress Set PIN code Register - Verify + Verify via Utorg Refresh Connect your card Verify your identity diff --git a/app/src/main/res/values-it/strings_final.xml b/app/src/main/res/values-it/strings_final.xml index 60f556f535..384c389dbe 100644 --- a/app/src/main/res/values-it/strings_final.xml +++ b/app/src/main/res/values-it/strings_final.xml @@ -121,7 +121,7 @@ In progress Set PIN code Register - Verify + Verify via Utorg Refresh Connect your card Verify your identity diff --git a/app/src/main/res/values-ru/strings_final.xml b/app/src/main/res/values-ru/strings_final.xml index 6ca61fbc79..bcf57b6db6 100644 --- a/app/src/main/res/values-ru/strings_final.xml +++ b/app/src/main/res/values-ru/strings_final.xml @@ -119,7 +119,7 @@ В обработке Установить Код доступа Зарегистрироваться - Подтвердить + Верифицировать (Utorg) Обновить Подключите свою карту Подтвердите свою личность diff --git a/app/src/main/res/values/strings_final.xml b/app/src/main/res/values/strings_final.xml index 57ee686bed..76dc5aa8dc 100644 --- a/app/src/main/res/values/strings_final.xml +++ b/app/src/main/res/values/strings_final.xml @@ -121,7 +121,7 @@ In progress Set PIN code Register - Verify + Verify via Utorg Refresh Connect your card Verify your identity From ae92fd93611fef12142b979155cde3095ebd1973 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 24 Oct 2022 15:33:51 +0400 Subject: [PATCH 104/113] Updated on 2026-08-14 --- .../tap/domain/tokens/UserTokensRepository.kt | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt index 639fd82b98..34330160a0 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt @@ -26,10 +26,11 @@ class UserTokensRepository( private val networkService: UserTokensNetworkService, ) { suspend fun getUserTokens(card: Card): List { - if (DemoHelper.isDemoCardId(card.cardId)) { - return loadDemoCurrencies() - } val userId = card.getUserId() + if (DemoHelper.isDemoCardId(card.cardId)) { + return loadTokensOffline(card, userId).ifEmpty { loadDemoCurrencies() } + } + if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) { return loadTokensOffline(card, userId) } @@ -68,11 +69,15 @@ class UserTokensRepository( } suspend fun loadBlockchainsToDerive(card: Card): List { + val userId = card.getUserId() + val blockchainNetworks = loadTokensOffline(card, userId).toBlockchainNetworks() + if (DemoHelper.isDemoCardId(card.cardId)) { - return loadDemoCurrencies().toBlockchainNetworks() + return blockchainNetworks + .ifEmpty { loadDemoCurrencies().toBlockchainNetworks() } } - return storageService.getUserTokens(card.getUserId())?.toBlockchainNetworks() - ?: storageService.getUserTokens(card).toBlockchainNetworks() + + return blockchainNetworks } private fun loadDemoCurrencies(): List { From 101ea6998ed4b67b521147d683906092f9e0e36d Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 24 Oct 2022 15:52:36 +0400 Subject: [PATCH 105/113] Updated on 2026-08-14 --- .../com/tangem/domain/common/extensions/Blockchain.kt | 11 ++++++++++- .../com/tangem/domain/common/extensions/CardSdk.kt | 5 +++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt b/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt index a720d8bf19..b11840f128 100644 --- a/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt +++ b/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt @@ -137,4 +137,13 @@ fun Blockchain.toCoinId(): String { Blockchain.SaltPay, Blockchain.SaltPayTestnet -> "xdai" Blockchain.Unknown -> "unknown" } -} \ No newline at end of file +} + +fun Blockchain.isSupportedInApp(): Boolean { + return !excludedBlockchains.contains(this) +} + +private val excludedBlockchains = listOf( + Blockchain.Optimism, // TODO: remove when fee calculation is fixed + Blockchain.SaltPay, +) \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/common/extensions/CardSdk.kt b/domain/src/main/java/com/tangem/domain/common/extensions/CardSdk.kt index 204602df42..acc0fc7bf0 100644 --- a/domain/src/main/java/com/tangem/domain/common/extensions/CardSdk.kt +++ b/domain/src/main/java/com/tangem/domain/common/extensions/CardSdk.kt @@ -21,8 +21,9 @@ fun Card.supportedBlockchains(): List { (Blockchain.fromCurve(EllipticCurve.Secp256k1) + Blockchain.fromCurve(EllipticCurve.Ed25519)).distinct() } } - val filtered = supportedBlockchains.filter { isTestCard == it.isTestnet() } - return filtered + return supportedBlockchains + .filter { isTestCard == it.isTestnet() } + .filter { it.isSupportedInApp() } } fun Card.supportedTokens(): List { From fff302e7fb7c332480a2266f2d53b3c9feefb7aa Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 24 Oct 2022 18:41:26 +0500 Subject: [PATCH 106/113] Updated on 2026-08-14 --- .../com/tangem/tap/common/DialogManager.kt | 18 ++++---- .../tangem/tap/common/redux/StateDialog.kt | 1 + .../onboarding/OnboardingSaltPayHelper.kt | 27 ++++-------- .../redux/OnboardingWalletMiddleware.kt | 43 ++++++++++++++----- .../wallet/saltPay/SaltPayExceptionHandler.kt | 4 +- .../saltPay/dialog/RegistrationErrorDialog.kt | 2 +- .../wallet/saltPay/dialog/SaltPayDialog.kt | 8 +++- .../TryToInterruptRegistrationDialog.kt | 26 +++++++++++ .../saltPay/ui/OnboardingSaltPayView.kt | 2 +- 9 files changed, 86 insertions(+), 45 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/TryToInterruptRegistrationDialog.kt diff --git a/app/src/main/java/com/tangem/tap/common/DialogManager.kt b/app/src/main/java/com/tangem/tap/common/DialogManager.kt index 0508230bba..8e36ea5a94 100644 --- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt +++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt @@ -20,6 +20,7 @@ import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.NoFundsForActivationDialog import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.RegistrationErrorDialog import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.SaltPayDialog +import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.TryToInterruptRegistrationDialog 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 @@ -130,16 +131,13 @@ class DialogManager : StoreSubscriber { is BackupDialog.BackupInProgress -> BackupInProgressDialog.create(context) is BackupDialog.UnfinishedBackupFound -> UnfinishedBackupFoundDialog.create(context) is BackupDialog.ConfirmDiscardingBackup -> ConfirmDiscardingBackupDialog.create(context) - is SaltPayDialog.NoFundsForActivation -> NoFundsForActivationDialog.create(context) - is SaltPayDialog.RegistrationError -> RegistrationErrorDialog.create(context, state.dialog) - is WalletDialog.CurrencySelectionDialog -> - CurrencySelectionDialog.create(state.dialog, context) - is WalletDialog.ChooseTradeActionDialog -> - ChooseTradeActionBottomSheetDialog(context) - is WalletDialog.SelectAmountToSendDialog -> - AmountToSendBottomSheetDialog(context, state.dialog) - is WalletDialog.SignedHashesMultiWalletDialog -> - SignedHashesWarningDialog.create(context) + is SaltPayDialog.Activation.NoFunds -> NoFundsForActivationDialog.create(context) + is SaltPayDialog.Activation.TryToInterrupt -> TryToInterruptRegistrationDialog.create(context, state.dialog) + is SaltPayDialog.Activation.OnError -> RegistrationErrorDialog.create(context, state.dialog) + is WalletDialog.CurrencySelectionDialog -> CurrencySelectionDialog.create(state.dialog, context) + is WalletDialog.ChooseTradeActionDialog -> ChooseTradeActionBottomSheetDialog(context) + is WalletDialog.SelectAmountToSendDialog -> AmountToSendBottomSheetDialog(context, state.dialog) + is WalletDialog.SignedHashesMultiWalletDialog -> SignedHashesWarningDialog.create(context) is WalletDialog.TokensAreLinkedDialog -> SimpleAlertDialog.create( title = context.getString(state.dialog.titleRes, state.dialog.currencySymbol), message = context.getString( diff --git a/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt b/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt index 0b30d72ff0..e216493dd6 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt @@ -21,6 +21,7 @@ sealed class AppDialog : StateDialog { ) : AppDialog() object ScanFailsDialog : AppDialog() + data class AddressInfoDialog( val currency: Currency, val addressData: AddressData, 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 2b4ad93ab0..ed11c57eed 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 @@ -34,25 +34,14 @@ class OnboardingSaltPayHelper { } } - suspend fun testProceedToOnboarding(scanResponse: ScanResponse): Result { - return try { - val isBackupOnboardingCase = scanResponse.card.backupStatus?.isActive == false - Result.Success(isBackupOnboardingCase) - } catch (ex: Exception) { - Result.Failure(ex) - } catch (error: SaltPayRegistrationError) { - Result.Failure(error) - } - } + suspend fun testProceedToOnboarding( + scanResponse: ScanResponse, + manager: SaltPayRegistrationManager, + ): Result = Result.Success(true) - suspend fun testProceedToMainScreen(scanResponse: ScanResponse): Result { - return try { - Result.Success(false) - } catch (ex: Exception) { - Result.Failure(ex) - } catch (error: SaltPayRegistrationError) { - Result.Failure(error) - } - } + suspend fun testProceedToMainScreen( + scanResponse: ScanResponse, + manager: SaltPayRegistrationManager, + ): Result = Result.Success(false) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index 745dee84fd..b03729bda9 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -9,6 +9,7 @@ import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.extensions.withMainContext import com.tangem.operations.backup.BackupService import com.tangem.tap.backupService +import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction @@ -18,6 +19,7 @@ import com.tangem.tap.domain.extensions.hasWallets import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.home.redux.HomeAction +import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.SaltPayDialog import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayState import com.tangem.tap.features.wallet.redux.Artwork @@ -173,16 +175,9 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch: newAction?.let { store.dispatch(it) } } OnboardingWalletAction.OnBackPressed -> { - when (onboardingWalletState.backupState.backupStep) { - BackupStep.InitBackup, BackupStep.Finished -> store.dispatch(NavigationAction.PopBackTo()) - BackupStep.ScanOriginCard, BackupStep.AddBackupCards, BackupStep.EnterAccessCode, - BackupStep.ReenterAccessCode, BackupStep.SetAccessCode, BackupStep.WritePrimaryCard, - -> { - store.dispatch(BackupAction.DiscardBackup) - store.dispatch(NavigationAction.PopBackTo()) - } - is BackupStep.WriteBackupCard -> - store.dispatch(GlobalAction.ShowDialog(BackupDialog.BackupInProgress)) + when { + onboardingWalletState.isSaltPay -> handleOnBackPressedSaltPay(onboardingWalletState) + else -> handleOnBackPressed(onboardingWalletState) } } } @@ -365,4 +360,32 @@ private fun initSaltPayOnBackupFinishedIfNeeded( store.dispatchOnMain(OnboardingSaltPayAction.Init.SetDependencies(manager, config)) store.dispatchOnMain(OnboardingSaltPayAction.Update) } +} + +private fun handleOnBackPressedSaltPay(state: OnboardingWalletState) { + when (state.backupState.backupStep) { + BackupStep.Finished -> { + store.dispatchDialogShow( + SaltPayDialog.Activation.TryToInterrupt( + onOk = { store.dispatch(NavigationAction.PopBackTo()) }, + onCancel = { /* do nothing */ }, + ), + ) + } + else -> handleOnBackPressed(state) + } +} + +private fun handleOnBackPressed(state: OnboardingWalletState) { + when (state.backupState.backupStep) { + BackupStep.InitBackup, BackupStep.Finished -> store.dispatch(NavigationAction.PopBackTo()) + BackupStep.ScanOriginCard, BackupStep.AddBackupCards, BackupStep.EnterAccessCode, + BackupStep.ReenterAccessCode, BackupStep.SetAccessCode, BackupStep.WritePrimaryCard, + -> { + store.dispatch(BackupAction.DiscardBackup) + store.dispatch(NavigationAction.PopBackTo()) + } + is BackupStep.WriteBackupCard -> + store.dispatch(GlobalAction.ShowDialog(BackupDialog.BackupInProgress)) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt index 90e35d1bbd..7be228dcde 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt @@ -16,8 +16,8 @@ class SaltPayExceptionHandler { when (throwable) { is SaltPayRegistrationError -> { val dialog = when (throwable) { - is SaltPayRegistrationError.NoGas -> SaltPayDialog.NoFundsForActivation - else -> SaltPayDialog.RegistrationError(throwable) + is SaltPayRegistrationError.NoGas -> SaltPayDialog.Activation.NoFunds + else -> SaltPayDialog.Activation.OnError(throwable) } store.dispatchDialogShow(dialog) } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/RegistrationErrorDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/RegistrationErrorDialog.kt index f7ddfbea66..831d097783 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/RegistrationErrorDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/RegistrationErrorDialog.kt @@ -14,7 +14,7 @@ import com.tangem.wallet.R */ class RegistrationErrorDialog { companion object { - fun create(context: Context, dialog: SaltPayDialog.RegistrationError): Dialog { + fun create(context: Context, dialog: SaltPayDialog.Activation.OnError): Dialog { val convertedMessage = SaltPayErrorConverter(context).convert(dialog.error) as ConvertedDialogMessage return AlertDialog.Builder(context).apply { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/SaltPayDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/SaltPayDialog.kt index 5bd2e1aac0..f28a3a29a2 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/SaltPayDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/SaltPayDialog.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog +import com.tangem.common.extensions.VoidCallback import com.tangem.tap.common.redux.StateDialog import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayRegistrationError @@ -7,6 +8,9 @@ import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPa [REDACTED_AUTHOR] */ sealed class SaltPayDialog : StateDialog { - object NoFundsForActivation : SaltPayDialog() - data class RegistrationError(val error: SaltPayRegistrationError) : SaltPayDialog() + sealed class Activation : SaltPayDialog() { + object NoFunds : SaltPayDialog() + data class OnError(val error: SaltPayRegistrationError) : SaltPayDialog() + data class TryToInterrupt(val onOk: VoidCallback, val onCancel: VoidCallback) : SaltPayDialog() + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/TryToInterruptRegistrationDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/TryToInterruptRegistrationDialog.kt new file mode 100644 index 0000000000..6f02df9120 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/TryToInterruptRegistrationDialog.kt @@ -0,0 +1,26 @@ +package com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog + +import android.app.Dialog +import android.content.Context +import androidx.appcompat.app.AlertDialog +import com.tangem.tap.common.extensions.dispatchDialogHide +import com.tangem.tap.store +import com.tangem.wallet.R + +/** +[REDACTED_AUTHOR] + */ +class TryToInterruptRegistrationDialog { + companion object { + fun create(context: Context, dialog: SaltPayDialog.Activation.TryToInterrupt): Dialog { + return AlertDialog.Builder(context).apply { + setTitle(context.getString(R.string.onboarding_exit_alert_title)) + setMessage(context.getString(R.string.onboarding_exit_alert_message)) + setPositiveButton(R.string.common_ok) { _, _ -> dialog.onOk() } + setNegativeButton(R.string.common_cancel) { _, _ -> dialog.onCancel() } + setOnDismissListener { store.dispatchDialogHide() } + setCancelable(false) + }.create() + } + } +} \ 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 b7840bf99e..b721043f3d 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 @@ -150,7 +150,7 @@ internal class OnboardingSaltPayView( private fun handleNoGas() { // normally this shouldn't happen store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) - store.dispatchDialogShow(SaltPayDialog.NoFundsForActivation) + store.dispatchDialogShow(SaltPayDialog.Activation.NoFunds) } private fun handleNeedPin() = with(walletFragment.bindingSaltPay) { From 4d59925f67a2370c6dcddd820a689ef996f1573e Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 24 Oct 2022 19:12:26 +0500 Subject: [PATCH 107/113] Updated on 2026-08-14 --- app/src/main/java/com/tangem/tap/common/DialogManager.kt | 2 +- .../products/wallet/saltPay/SaltPayExceptionHandler.kt | 2 +- .../onboarding/products/wallet/saltPay/dialog/SaltPayDialog.kt | 2 +- .../products/wallet/saltPay/ui/OnboardingSaltPayView.kt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/DialogManager.kt b/app/src/main/java/com/tangem/tap/common/DialogManager.kt index 8e36ea5a94..5ae8d9ce04 100644 --- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt +++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt @@ -131,7 +131,7 @@ class DialogManager : StoreSubscriber { is BackupDialog.BackupInProgress -> BackupInProgressDialog.create(context) is BackupDialog.UnfinishedBackupFound -> UnfinishedBackupFoundDialog.create(context) is BackupDialog.ConfirmDiscardingBackup -> ConfirmDiscardingBackupDialog.create(context) - is SaltPayDialog.Activation.NoFunds -> NoFundsForActivationDialog.create(context) + is SaltPayDialog.Activation.NoGas -> NoFundsForActivationDialog.create(context) is SaltPayDialog.Activation.TryToInterrupt -> TryToInterruptRegistrationDialog.create(context, state.dialog) is SaltPayDialog.Activation.OnError -> RegistrationErrorDialog.create(context, state.dialog) is WalletDialog.CurrencySelectionDialog -> CurrencySelectionDialog.create(state.dialog, context) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt index 7be228dcde..f31637ea10 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt @@ -16,7 +16,7 @@ class SaltPayExceptionHandler { when (throwable) { is SaltPayRegistrationError -> { val dialog = when (throwable) { - is SaltPayRegistrationError.NoGas -> SaltPayDialog.Activation.NoFunds + is SaltPayRegistrationError.NoGas -> SaltPayDialog.Activation.NoGas else -> SaltPayDialog.Activation.OnError(throwable) } store.dispatchDialogShow(dialog) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/SaltPayDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/SaltPayDialog.kt index f28a3a29a2..4417886278 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/SaltPayDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/SaltPayDialog.kt @@ -9,7 +9,7 @@ import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPa */ sealed class SaltPayDialog : StateDialog { sealed class Activation : SaltPayDialog() { - object NoFunds : SaltPayDialog() + object NoGas : SaltPayDialog() data class OnError(val error: SaltPayRegistrationError) : SaltPayDialog() data class TryToInterrupt(val onOk: VoidCallback, val onCancel: VoidCallback) : SaltPayDialog() } 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 b721043f3d..0bcfb5d36f 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 @@ -150,7 +150,7 @@ internal class OnboardingSaltPayView( private fun handleNoGas() { // normally this shouldn't happen store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) - store.dispatchDialogShow(SaltPayDialog.Activation.NoFunds) + store.dispatchDialogShow(SaltPayDialog.Activation.NoGas) } private fun handleNeedPin() = with(walletFragment.bindingSaltPay) { From 8ef961807d273e87f830077fc3130745b85526ee Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 24 Oct 2022 20:19:22 +0500 Subject: [PATCH 108/113] Updated on 2026-08-14 --- .../tap/common/redux/global/GlobalAction.kt | 14 ++++++- .../wallet/redux/OnboardingWalletAction.kt | 7 +++- .../redux/OnboardingWalletMiddleware.kt | 33 ++++++++------- .../wallet/redux/OnboardingWalletReducer.kt | 40 ++++++++++--------- 4 files changed, 59 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt index 3d596bbce3..a3aba839df 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt @@ -38,8 +38,20 @@ sealed class GlobalAction : Action { object HideDialog : GlobalAction() sealed class Onboarding : GlobalAction() { + /** + * Initiate an onboarding process. + * For SaltPay cards it's additionally checks for unfinished backup. + * For resuming unfinished backup for standard Wallet cards see CheckForUnfinishedBackup and + * StartForUnfinishedBackup + */ data class Start(val scanResponse: ScanResponse, val canSkipBackup: Boolean = true) : Onboarding() - data class StartForUnfinishedBackup(val isSaltPayBackup: Boolean) : Onboarding() + + /** + * Initiate resuming of unfinished backup only for standard Wallet cards. + * For SaltPay cards unfinished backup resumed after scanning the card on HomeScreen through Onboarding.Start. + * See more Onboarding.Start, CheckForUnfinishedBackup + */ + object StartForUnfinishedBackup : Onboarding() object Stop : Onboarding() } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt index ce9a6b6c41..885f57fa99 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt @@ -5,7 +5,7 @@ import org.rekotlin.Action sealed class OnboardingWalletAction : Action { object Init : OnboardingWalletAction() - object StartSaltPayCardActivation: OnboardingWalletAction() + object StartSaltPayCardActivation : OnboardingWalletAction() object GetToCreateWalletStep : OnboardingWalletAction() object GetToSaltPayStep : OnboardingWalletAction() object CreateWallet : OnboardingWalletAction() @@ -30,6 +30,11 @@ sealed class BackupAction : Action { object StartAddingPrimaryCard : BackupAction() object ScanPrimaryCard : BackupAction() + /** + * Check for unfinished backup of standard Wallet cards. + * For SaltPay cards unfinished backup resumed after scanning the card on HomeScreen through Onboarding.Start. + * See more Onboarding.Start, CheckForUnfinishedBackup, StartForUnfinishedBackup + */ object CheckForUnfinishedBackup : BackupAction() object StartAddingBackupCards : BackupAction() diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index b03729bda9..148b1061a5 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -62,24 +62,26 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch: when (action) { OnboardingWalletAction.Init -> { - // if (walletState.onboardingSaltPayState?.isTest == true) { - // store.dispatch(BackupAction.FinishBackup) - // return - // } - when { card == null -> { - // StartForUnfinishedBackup -> navigateTo(OnboardingWallet) -> OnboardingWalletAction.Init + // it's possible when found unfinished backup for standard Wallet cards store.dispatch(OnboardingWalletAction.ResumeBackup) } card.hasWallets() && card.backupStatus == Card.BackupStatus.NoBackup -> { store.dispatch(OnboardingWalletAction.ResumeBackup) } card.hasWallets() && card.backupStatus?.isActive == true -> { - if (scanResponse.isSaltPay()) { - store.dispatch(OnboardingWalletAction.GetToSaltPayStep) + when { + // check for unfinished backup for saltPay cards. See more + scanResponse.isSaltPay() && backupService.hasIncompletedBackup -> { + store.dispatch(OnboardingWalletAction.ResumeBackup) + } + scanResponse.isSaltPay() -> { + store.dispatch(OnboardingWalletAction.GetToSaltPayStep) + store.dispatch(BackupAction.FinishBackup) + } + else -> store.dispatch(BackupAction.FinishBackup) } - store.dispatch(BackupAction.FinishBackup) } else -> { store.dispatch(OnboardingWalletAction.GetToCreateWalletStep) @@ -327,15 +329,12 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) backupService.discardSavedBackup() } is BackupAction.CheckForUnfinishedBackup -> { - if (backupService.hasIncompletedBackup) { + if (backupService.hasIncompletedBackup && !backupService.primaryCardIsSaltPay()) { store.dispatch(GlobalAction.ShowDialog(BackupDialog.UnfinishedBackupFound)) } } is BackupAction.ResumeFoundUnfinishedBackup -> { - val isSaltPay = backupService.primaryCardId?.slice(0..3)?.let { - SaltPayWorkaround.isVisaBatchId(it) - } ?: false - store.dispatch(GlobalAction.Onboarding.StartForUnfinishedBackup(isSaltPay)) + store.dispatch(GlobalAction.Onboarding.StartForUnfinishedBackup) store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet)) } is BackupAction.DismissBackup -> { @@ -345,6 +344,12 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) } } +private fun BackupService.primaryCardIsSaltPay(): Boolean { + return primaryCardId?.slice(0..3)?.let { + SaltPayWorkaround.isVisaBatchId(it) + } ?: false +} + /** * SaltPay state maybe not initialized if backup resumed from ResumeFoundUnfinishedBackup action */ diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt index 0f79ba3da0..e90b69a31a 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt @@ -38,26 +38,28 @@ private fun internalReduce(action: Action, appState: AppState): OnboardingWallet private class ReducerForGlobalAction { companion object { - fun reduce(action: GlobalAction.Onboarding, state: OnboardingWalletState): OnboardingWalletState = when (action) { - is GlobalAction.Onboarding.Start -> { - OnboardingWalletState( - backupState = state.backupState.copy( - maxBackupCards = if (action.scanResponse.isSaltPay()) 1 else 2, - canSkipBackup = if (action.scanResponse.isSaltPay()) false else action.canSkipBackup, - ), - isSaltPay = action.scanResponse.isSaltPay() - ) + fun reduce(action: GlobalAction.Onboarding, state: OnboardingWalletState): OnboardingWalletState { + return when (action) { + is GlobalAction.Onboarding.Start -> { + OnboardingWalletState( + backupState = state.backupState.copy( + maxBackupCards = if (action.scanResponse.isSaltPay()) 1 else 2, + canSkipBackup = if (action.scanResponse.isSaltPay()) false else action.canSkipBackup, + ), + isSaltPay = action.scanResponse.isSaltPay(), + ) + } + is GlobalAction.Onboarding.StartForUnfinishedBackup -> { + OnboardingWalletState( + backupState = state.backupState.copy( + maxBackupCards = 2, + canSkipBackup = false, + ), + isSaltPay = false, + ) + } + else -> state } - is GlobalAction.Onboarding.StartForUnfinishedBackup -> { - OnboardingWalletState( - backupState = state.backupState.copy( - maxBackupCards = if (action.isSaltPayBackup) 1 else 2, - canSkipBackup = false, - ), - isSaltPay = action.isSaltPayBackup, - ) - } - else -> state } } } From ddabdb494482234ecc0151f203f762e80cf27259 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 24 Oct 2022 21:31:05 +0500 Subject: [PATCH 109/113] Updated on 2026-08-14 --- .../onboarding/OnboardingSaltPayHelper.kt | 12 +++++------ ...Manager.kt => SaltPayActivationManager.kt} | 4 ++-- .../saltPay/redux/OnboardingSaltPayAction.kt | 4 ++-- .../redux/OnboardingSaltPayMiddleware.kt | 21 ++++++++----------- .../saltPay/redux/OnboardingSaltPayState.kt | 10 ++++----- 5 files changed, 24 insertions(+), 27 deletions(-) rename app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/{SaltPayRegistrationManager.kt => SaltPayActivationManager.kt} (98%) 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 ed11c57eed..41712c4b65 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.SaltPayActivationManager import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayRegistrationError import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.SaltPayRegistrationStep -import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.updateSaltPayStatus +import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.updateActivationStatus /** [REDACTED_AUTHOR] @@ -16,10 +16,10 @@ class OnboardingSaltPayHelper { suspend fun isOnboardingCase( scanResponse: ScanResponse, - manager: SaltPayRegistrationManager, + manager: SaltPayActivationManager, ): Result { return try { - val status = manager.updateSaltPayStatus( + val status = manager.updateActivationStatus( amountToClaim = null, step = SaltPayRegistrationStep.None, ).successOr { return it } @@ -36,12 +36,12 @@ class OnboardingSaltPayHelper { suspend fun testProceedToOnboarding( scanResponse: ScanResponse, - manager: SaltPayRegistrationManager, + manager: SaltPayActivationManager, ): Result = Result.Success(true) suspend fun testProceedToMainScreen( scanResponse: ScanResponse, - manager: SaltPayRegistrationManager, + manager: SaltPayActivationManager, ): Result = Result.Success(false) } } \ No newline at end of file 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/SaltPayActivationManager.kt similarity index 98% rename from app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayRegistrationManager.kt rename to app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt index 6f4bad5574..f8cf80df90 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/SaltPayActivationManager.kt @@ -27,7 +27,7 @@ import java.math.BigDecimal /** [REDACTED_AUTHOR] */ -class SaltPayRegistrationManager( +class SaltPayActivationManager( val cardId: String, val cardPublicKey: ByteArray, val walletPublicKey: ByteArray, @@ -162,7 +162,7 @@ class SaltPayRegistrationManager( ) companion object { - fun stub(): SaltPayRegistrationManager = SaltPayRegistrationManager( + fun stub(): SaltPayActivationManager = SaltPayActivationManager( kycProvider = SaltPayConfig.stub().kycProvider, paymentologyService = PaymentologyApiService.stub(), gnosisRegistrator = GnosisRegistrator.stub(), 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 8a8fb9cf2e..ecabaaccb2 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,8 @@ 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.SaltPayActivationManager 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 @@ -12,7 +12,7 @@ import java.math.BigDecimal sealed class OnboardingSaltPayAction : Action { sealed class Init : OnboardingSaltPayAction() { data class SetDependencies( - val registrationManager: SaltPayRegistrationManager, + val registrationManager: SaltPayActivationManager, val saltPayConfig: SaltPayConfig, ) : Init() } 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 1f588097d0..e243e4e118 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 @@ -17,8 +17,8 @@ import com.tangem.tap.domain.TangemSigner import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState import com.tangem.tap.features.onboarding.products.wallet.saltPay.AllSymbolsTheSameFilter +import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManager import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayExceptionHandler -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.scope import com.tangem.tap.store @@ -63,7 +63,7 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App val state = getState() scope.launch { - val updateResult = state.saltPayManager.updateSaltPayStatus( + val updateResult = state.saltPayManager.updateActivationStatus( amountToClaim = state.amountToClaim, step = state.step, ).successOr { @@ -146,12 +146,9 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App onException(it.error) return@launch } + handleInProgress = false - val step = when (state.readyToClaim()) { - true -> SaltPayRegistrationStep.Claim - else -> SaltPayRegistrationStep.Finished - } - withMainContext { store.dispatch(OnboardingSaltPayAction.SetStep(step)) } + withMainContext { store.dispatch(OnboardingSaltPayAction.Update) } } } is OnboardingSaltPayAction.TrySetPin -> { @@ -228,7 +225,7 @@ data class UpdateResult( val amountToClaim: Amount? = null, ) -suspend fun SaltPayRegistrationManager.updateSaltPayStatus( +suspend fun SaltPayActivationManager.updateActivationStatus( amountToClaim: Amount?, step: SaltPayRegistrationStep, ): Result { @@ -267,7 +264,7 @@ suspend fun SaltPayRegistrationManager.updateSaltPayStatus( } private suspend fun getAmountToClaimIfNeeded( - saltPayManager: SaltPayRegistrationManager, + saltPayManager: SaltPayActivationManager, amountToClaim: Amount?, ): Amount? { Timber.d("getAmountToClaimIfNeeded: for amount: %s", amountToClaim) @@ -315,7 +312,7 @@ private suspend fun onException(error: Throwable) { } private suspend fun checkGasIfNeeded( - saltPayManager: SaltPayRegistrationManager, + saltPayManager: SaltPayActivationManager, step: SaltPayRegistrationStep, ): Result { Timber.d("checkGasIfNeeded: for step: %s", step) @@ -336,7 +333,7 @@ private suspend fun checkGasIfNeeded( } private suspend fun registerKYCIfNeeded( - saltPayManager: SaltPayRegistrationManager, + saltPayManager: SaltPayActivationManager, step: SaltPayRegistrationStep, ): Result { Timber.d("registerKYCIfNeeded: step: %s", step) @@ -355,7 +352,7 @@ private suspend fun registerKYCIfNeeded( } private suspend fun checkRegistration( - saltPayManager: SaltPayRegistrationManager, + saltPayManager: SaltPayActivationManager, ): Result { Timber.d("checkRegistration") return try { 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 6e9c2c049b..c0dcec2395 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 @@ -14,8 +14,8 @@ import com.tangem.tap.copyToClipboard import com.tangem.tap.domain.extensions.makeSaltPayWalletManager import com.tangem.tap.features.onboarding.products.wallet.saltPay.GnosisRegistrator import com.tangem.tap.features.onboarding.products.wallet.saltPay.KYCProvider +import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManager import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayConfig -import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayRegistrationManager import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.tap.persistence.SaltPayRegistrationStorage import com.tangem.tap.preferencesStorage @@ -27,7 +27,7 @@ import java.math.BigDecimal */ data class OnboardingSaltPayState( @Transient - val saltPayManager: SaltPayRegistrationManager = SaltPayRegistrationManager.stub(), + val saltPayManager: SaltPayActivationManager = SaltPayActivationManager.stub(), val saltPayConfig: SaltPayConfig = SaltPayConfig.stub(), val pinCode: String? = null, val accessCode: String? = null, @@ -47,7 +47,7 @@ data class OnboardingSaltPayState( val pinLength: Int = 4 companion object { - fun initDependency(scanResponse: ScanResponse): Pair { + fun initDependency(scanResponse: ScanResponse): Pair { val globalState = store.state.globalState val saltPayConfig = globalState.configManager?.config?.saltPayConfig.guard { throw NullPointerException("SaltPayConfig is not initialized") @@ -85,7 +85,7 @@ data class OnboardingSaltPayState( paymentologyService: PaymentologyApiService, registrationStorage: SaltPayRegistrationStorage, kycProvider: KYCProvider, - ): SaltPayRegistrationManager { + ): SaltPayActivationManager { if (!scanResponse.isSaltPay()) { throw IllegalArgumentException("Can't initialize the OnboardingSaltPayMiddleware if card is not SalPay") } @@ -93,7 +93,7 @@ data class OnboardingSaltPayState( throw NullPointerException("SaltPay card must have one wallet at least") } - return SaltPayRegistrationManager( + return SaltPayActivationManager( cardId = scanResponse.card.cardId, cardPublicKey = scanResponse.card.cardPublicKey, walletPublicKey = saltPaySingleWallet.publicKey, From 3efe90f0ddfdae575247bea2c5ab97fa51f0e4e1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 24 Oct 2022 21:32:54 +0500 Subject: [PATCH 110/113] Updated on 2026-08-14 --- .../wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 e243e4e118..0a0ac4e937 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 @@ -247,9 +247,10 @@ suspend fun SaltPayActivationManager.updateActivationStatus( val fetchedAmountToClaim = getAmountToClaimIfNeeded(this, amountToClaim) updateResult = when { + // if fetchedAmountToClaim = null -> then it already claimed saltPayStep == SaltPayRegistrationStep.Claim && fetchedAmountToClaim == null -> { updateResult.copy( - step = SaltPayRegistrationStep.KycReject, + step = SaltPayRegistrationStep.Finished, amountToClaim = fetchedAmountToClaim, ) } From c3164eafdbc8eb0c191be98e4ad684d4b7283aa6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 24 Oct 2022 22:28:31 +0500 Subject: [PATCH 111/113] Updated on 2026-08-14 --- .../products/wallet/ui/BackupAnimator.kt | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupAnimator.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupAnimator.kt index dd980fc2cd..3621bbcfa4 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupAnimator.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupAnimator.kt @@ -2,6 +2,7 @@ package com.tangem.tap.features.onboarding.products.wallet.ui import com.google.android.material.button.MaterialButton import com.tangem.tap.common.extensions.show +import com.tangem.tap.common.postUiDelayBg import com.tangem.tap.features.onboarding.products.wallet.redux.BackupState import com.tangem.tap.features.onboarding.products.wallet.ui.BackupCardType.FIRST_BACKUP import com.tangem.tap.features.onboarding.products.wallet.ui.BackupCardType.ORIGIN @@ -32,7 +33,7 @@ class WalletBackupAnimator( else -> throw UnsupportedOperationException() } - private var currentStep = 0 + private var currentStep = -1 override fun updateBackupState(backupState: BackupState) { } @@ -112,13 +113,17 @@ class WalletBackupAnimator( } override fun showWriteBackupCard(state: BackupState, backupCard: Int) { - currentStep = STEP_WRITE_BACKUP_CARD when (viewState) { State.TwoCards -> { - FIRST_BACKUP.alpha(1f) - cardsWidget.leapfrogWidget.leap { - ORIGIN.alpha(0.4f) + if (currentStep == STEP_WRITE_BACKUP_CARD) return + + val delay = if (currentStep == UNDEFINED) 700L else 0 + postUiDelayBg(delay) { + FIRST_BACKUP.alpha(1f) + cardsWidget.leapfrogWidget.leap { + ORIGIN.alpha(0.4f) + } } } State.TreeCards -> { @@ -140,6 +145,7 @@ class WalletBackupAnimator( } } } + currentStep = STEP_WRITE_BACKUP_CARD } override fun showSuccess(onEnd: () -> Unit) { @@ -206,6 +212,7 @@ class WalletBackupAnimator( } companion object { + private const val UNDEFINED = -1 private const val STEP_CREATE_WALLET = 0 private const val STEP_BACKUP_INTRO = 1 private const val STEP_SCAN_ORIGIN_CARD = 2 From 26f476bcd074b5af24db32d20fd43266590794bd Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 25 Oct 2022 11:36:18 +0500 Subject: [PATCH 112/113] Updated on 2026-08-14 --- .../saltPay/SaltPayMessageConverter.kt | 6 +- ....kt => SaltPayActivationErrorConverter.kt} | 18 ++-- .../onboarding/OnboardingSaltPayHelper.kt | 10 +-- .../wallet/redux/OnboardingWalletState.kt | 18 ++-- .../saltPay/SaltPayActivationManager.kt | 22 ++--- .../wallet/saltPay/SaltPayExceptionHandler.kt | 6 +- .../wallet/saltPay/dialog/SaltPayDialog.kt | 4 +- .../saltPay/message/SaltPayModuleMessage.kt | 36 ++++---- .../saltPay/redux/OnboardingSaltPayAction.kt | 2 +- .../redux/OnboardingSaltPayMiddleware.kt | 82 +++++++++---------- .../saltPay/redux/OnboardingSaltPayState.kt | 10 +-- .../saltPay/ui/OnboardingSaltPayView.kt | 32 ++++---- .../tap/persistence/PreferencesStorage.kt | 4 +- ...age.kt => SaltPayActivationPrefStorage.kt} | 24 +++--- 14 files changed, 137 insertions(+), 137 deletions(-) rename app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/converter/{SaltPayRegistrationErrorConverter.kt => SaltPayActivationErrorConverter.kt} (74%) rename app/src/main/java/com/tangem/tap/persistence/{SaltPayRegistrationPrefStorage.kt => SaltPayActivationPrefStorage.kt} (69%) diff --git a/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/SaltPayMessageConverter.kt b/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/SaltPayMessageConverter.kt index 6ad14c42b0..ac8c8cd359 100644 --- a/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/SaltPayMessageConverter.kt +++ b/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/SaltPayMessageConverter.kt @@ -3,10 +3,10 @@ package com.tangem.tap.domain.moduleMessage.saltPay import android.content.Context import com.tangem.common.module.ModuleMessageConverter import com.tangem.tap.domain.moduleMessage.ConvertedMessage -import com.tangem.tap.domain.moduleMessage.saltPay.converter.SaltPayRegistrationErrorConverter +import com.tangem.tap.domain.moduleMessage.saltPay.converter.SaltPayActivationErrorConverter +import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayModuleError import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayModuleMessage -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayRegistrationError /** [REDACTED_AUTHOR] @@ -28,7 +28,7 @@ class SaltPayErrorConverter( ) : ModuleMessageConverter { override fun convert(message: SaltPayModuleError): ConvertedMessage? = when (message) { - is SaltPayRegistrationError -> SaltPayRegistrationErrorConverter(context).convert(message) + is SaltPayActivationError -> SaltPayActivationErrorConverter(context).convert(message) else -> null } } diff --git a/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/converter/SaltPayRegistrationErrorConverter.kt b/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/converter/SaltPayActivationErrorConverter.kt similarity index 74% rename from app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/converter/SaltPayRegistrationErrorConverter.kt rename to app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/converter/SaltPayActivationErrorConverter.kt index 554a0cab44..65de384d44 100644 --- a/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/converter/SaltPayRegistrationErrorConverter.kt +++ b/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/converter/SaltPayActivationErrorConverter.kt @@ -3,40 +3,40 @@ package com.tangem.tap.domain.moduleMessage.saltPay.converter import android.content.Context import com.tangem.common.module.ModuleMessageConverter import com.tangem.tap.domain.moduleMessage.ConvertedDialogMessage -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayRegistrationError +import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError import com.tangem.wallet.R /** [REDACTED_AUTHOR] */ -internal class SaltPayRegistrationErrorConverter( +internal class SaltPayActivationErrorConverter( private val context: Context, -) : ModuleMessageConverter { +) : ModuleMessageConverter { - override fun convert(message: SaltPayRegistrationError): ConvertedDialogMessage? { - val saltPayError = (message as? SaltPayRegistrationError) ?: throw UnsupportedOperationException() + override fun convert(message: SaltPayActivationError): ConvertedDialogMessage? { + val saltPayError = (message as? SaltPayActivationError) ?: throw UnsupportedOperationException() val dialogMessage = when (saltPayError) { - SaltPayRegistrationError.NoGas -> { + SaltPayActivationError.NoGas -> { ConvertedDialogMessage( title = context.getString(R.string.saltpay_error_no_gas_title), message = context.getString(R.string.saltpay_error_no_gas_message), ) } - SaltPayRegistrationError.EmptyBackupCardScanned -> { + SaltPayActivationError.EmptyBackupCardScanned -> { ConvertedDialogMessage( title = context.getString(R.string.saltpay_error_empty_backup_title), message = context.getString(R.string.saltpay_error_empty_backup_message), ) } - SaltPayRegistrationError.WeakPin -> { + SaltPayActivationError.WeakPin -> { ConvertedDialogMessage( title = context.getString(R.string.saltpay_error_pin_weak_title), message = context.getString(R.string.saltpay_error_pin_weak_message), ) } else -> { - val errorMessage = if (saltPayError.message == SaltPayRegistrationError.EMPTY_MESSAGE) { + val errorMessage = if (saltPayError.message == SaltPayActivationError.EMPTY_MESSAGE) { saltPayError::class.java.simpleName } else { saltPayError.message 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 41712c4b65..199b59f22a 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 @@ -4,8 +4,8 @@ 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.SaltPayActivationManager -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayRegistrationError -import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.SaltPayRegistrationStep +import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError +import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.SaltPayActivationStep import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.updateActivationStatus /** @@ -21,15 +21,15 @@ class OnboardingSaltPayHelper { return try { val status = manager.updateActivationStatus( amountToClaim = null, - step = SaltPayRegistrationStep.None, + step = SaltPayActivationStep.None, ).successOr { return it } - val isRegistrationCase = status.step != SaltPayRegistrationStep.Finished + val isRegistrationCase = status.step != SaltPayActivationStep.Finished val isBackupCase = scanResponse.card.backupStatus?.isActive == false Result.Success(isRegistrationCase || isBackupCase) } catch (ex: Exception) { Result.Failure(ex) - } catch (error: SaltPayRegistrationError) { + } catch (error: SaltPayActivationError) { Result.Failure(error) } } 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 3ddaa53339..48d6ef77cf 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 @@ -5,7 +5,7 @@ import com.tangem.common.CardFilter import com.tangem.domain.common.SaltPayWorkaround import com.tangem.tap.common.redux.StateDialog 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.SaltPayActivationStep import org.rekotlin.StateType /** @@ -51,15 +51,15 @@ data class OnboardingWalletState( } step == OnboardingWalletStep.SaltPay && isSaltPay -> { when (onboardingSaltPayState!!.step) { - SaltPayRegistrationStep.None, SaltPayRegistrationStep.NoGas -> 0 - SaltPayRegistrationStep.NeedPin -> 7 - SaltPayRegistrationStep.CardRegistration -> 8 - SaltPayRegistrationStep.KycIntro, SaltPayRegistrationStep.KycStart, - SaltPayRegistrationStep.KycWaiting, SaltPayRegistrationStep.KycReject, + SaltPayActivationStep.None, SaltPayActivationStep.NoGas -> 0 + SaltPayActivationStep.NeedPin -> 7 + SaltPayActivationStep.CardRegistration -> 8 + SaltPayActivationStep.KycIntro, SaltPayActivationStep.KycStart, + SaltPayActivationStep.KycWaiting, SaltPayActivationStep.KycReject, -> 9 - SaltPayRegistrationStep.Claim -> 10 - SaltPayRegistrationStep.ClaimInProgress -> 11 - SaltPayRegistrationStep.Finished -> getMaxProgress() + SaltPayActivationStep.Claim -> 10 + SaltPayActivationStep.ClaimInProgress -> 11 + SaltPayActivationStep.Finished -> getMaxProgress() } } step == OnboardingWalletStep.Done -> getMaxProgress() diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt index f8cf80df90..d7e5ff80f7 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt @@ -20,8 +20,8 @@ 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 +import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError +import com.tangem.tap.persistence.SaltPayActivationStorage import java.math.BigDecimal /** @@ -34,7 +34,7 @@ class SaltPayActivationManager( private val kycProvider: KYCProvider, private val paymentologyService: PaymentologyApiService, private val gnosisRegistrator: GnosisRegistrator, - private val registrationStorage: SaltPayRegistrationStorage, + private val registrationStorage: SaltPayActivationStorage, ) { val kycUrlProvider = KYCUrlProvider(walletPublicKey, kycProvider) @@ -52,9 +52,9 @@ class SaltPayActivationManager( is com.tangem.blockchain.extensions.Result.Success -> if (hasGasResult.data) { Result.Success(Unit) } else { - Result.Failure(SaltPayRegistrationError.NoGas) + Result.Failure(SaltPayActivationError.NoGas) } - is com.tangem.blockchain.extensions.Result.Failure -> Result.Failure(SaltPayRegistrationError.NoGas) + is com.tangem.blockchain.extensions.Result.Failure -> Result.Failure(SaltPayActivationError.NoGas) } } @@ -70,10 +70,10 @@ class SaltPayActivationManager( return try { if (!registrationResponse.success || registrationResponse.results.isEmpty()) { - throw SaltPayRegistrationError.EmptyResponse(registrationResponse.error) + throw SaltPayActivationError.EmptyResponse(registrationResponse.error) } Result.Success(registrationResponse.results[0]) - } catch (ex: SaltPayRegistrationError) { + } catch (ex: SaltPayActivationError) { Result.Failure(ex) } } @@ -114,11 +114,11 @@ class SaltPayActivationManager( suspend fun getAmountToClaim(): Result { val amount = gnosisRegistrator.getAllowance().successOr { - return Result.Failure(SaltPayRegistrationError.FailedToGetFundsToClaim) + return Result.Failure(SaltPayActivationError.FailedToGetFundsToClaim) } return if (amount.value == null || amount.value?.isZero() == true) { - Result.Failure(SaltPayRegistrationError.NoFundsToClaim) + Result.Failure(SaltPayActivationError.NoFundsToClaim) } else { Result.Success(amount) } @@ -126,7 +126,7 @@ class SaltPayActivationManager( suspend fun claim(amountToClaim: BigDecimal, signer: TransactionSigner): Result { val result = gnosisRegistrator.transferFrom(amountToClaim, signer).successOr { - return Result.Failure(SaltPayRegistrationError.ClaimTransactionFailed) + return Result.Failure(SaltPayActivationError.ClaimTransactionFailed) } return Result.Success(Unit) } @@ -166,7 +166,7 @@ class SaltPayActivationManager( kycProvider = SaltPayConfig.stub().kycProvider, paymentologyService = PaymentologyApiService.stub(), gnosisRegistrator = GnosisRegistrator.stub(), - registrationStorage = SaltPayRegistrationStorage.stub(), + registrationStorage = SaltPayActivationStorage.stub(), cardId = "", cardPublicKey = byteArrayOf(), walletPublicKey = byteArrayOf(), diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt index f31637ea10..87d0c2831d 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt @@ -4,7 +4,7 @@ import com.tangem.common.core.TangemSdkError import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.SaltPayDialog -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayRegistrationError +import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError import com.tangem.tap.store /** @@ -14,9 +14,9 @@ class SaltPayExceptionHandler { companion object { fun handle(throwable: Throwable) { when (throwable) { - is SaltPayRegistrationError -> { + is SaltPayActivationError -> { val dialog = when (throwable) { - is SaltPayRegistrationError.NoGas -> SaltPayDialog.Activation.NoGas + is SaltPayActivationError.NoGas -> SaltPayDialog.Activation.NoGas else -> SaltPayDialog.Activation.OnError(throwable) } store.dispatchDialogShow(dialog) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/SaltPayDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/SaltPayDialog.kt index 4417886278..fda1331a37 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/SaltPayDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/SaltPayDialog.kt @@ -2,7 +2,7 @@ package com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog import com.tangem.common.extensions.VoidCallback import com.tangem.tap.common.redux.StateDialog -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayRegistrationError +import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError /** [REDACTED_AUTHOR] @@ -10,7 +10,7 @@ import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPa sealed class SaltPayDialog : StateDialog { sealed class Activation : SaltPayDialog() { object NoGas : SaltPayDialog() - data class OnError(val error: SaltPayRegistrationError) : SaltPayDialog() + data class OnError(val error: SaltPayActivationError) : SaltPayDialog() data class TryToInterrupt(val onOk: VoidCallback, val onCancel: VoidCallback) : SaltPayDialog() } } \ No newline at end of file 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 aaedced27b..cc018b17e2 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 @@ -20,7 +20,7 @@ sealed class SaltPayModuleError( } } -sealed class SaltPayRegistrationError( +sealed class SaltPayActivationError( subCode: Int, message: String? = null, override val data: Any? = null, @@ -28,27 +28,27 @@ sealed class SaltPayRegistrationError( subCode = ERROR_CODE_REGISTRATION + subCode, message = message ?: EMPTY_MESSAGE, ) { - object Unknown : SaltPayRegistrationError(4) - object Empty : SaltPayRegistrationError(4) + object Unknown : SaltPayActivationError(4) + object Empty : SaltPayActivationError(4) // gnosis - object FailedToMakeTxData : SaltPayRegistrationError(1) - object FailedToSendTx : SaltPayRegistrationError(2) + object FailedToMakeTxData : SaltPayActivationError(1) + object FailedToSendTx : SaltPayActivationError(2) - object NeedPin : SaltPayRegistrationError(3) - object NoGas : SaltPayRegistrationError(5) // - class EmptyResponse(message: String?) : SaltPayRegistrationError(6, message) - class CardNotFound(message: String?) : SaltPayRegistrationError(8, message) - 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 NeedPin : SaltPayActivationError(3) + object NoGas : SaltPayActivationError(5) // + class EmptyResponse(message: String?) : SaltPayActivationError(6, message) + class CardNotFound(message: String?) : SaltPayActivationError(8, message) + class CardDisabled(message: String?) : SaltPayActivationError(9, message) + class CardNotPassed(message: String?) : SaltPayActivationError(10, message) + object EmptyDynamicAttestResponse : SaltPayActivationError(11) + object EmptyBackupCardScanned : SaltPayActivationError(12) + object WeakPin : SaltPayActivationError(13) - object FailedToGetFundsToClaim : SaltPayRegistrationError(14) - object NoFundsToClaim : SaltPayRegistrationError(15) - object ClaimTransactionFailed : SaltPayRegistrationError(16) - object NotClaimed : SaltPayRegistrationError(17) + object FailedToGetFundsToClaim : SaltPayActivationError(14) + object NoFundsToClaim : SaltPayActivationError(15) + object ClaimTransactionFailed : SaltPayActivationError(16) + object NotClaimed : SaltPayActivationError(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 ecabaaccb2..3ab770c511 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 @@ -33,7 +33,7 @@ sealed class OnboardingSaltPayAction : Action { data class TrySetPin(val pin: String) : OnboardingSaltPayAction() data class SetPin(val pin: String) : OnboardingSaltPayAction() - data class SetStep(val newStep: SaltPayRegistrationStep? = null) : OnboardingSaltPayAction() + data class SetStep(val newStep: SaltPayActivationStep? = 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 0a0ac4e937..af4a354e4b 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 @@ -19,7 +19,7 @@ import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWallet import com.tangem.tap.features.onboarding.products.wallet.saltPay.AllSymbolsTheSameFilter import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManager import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayExceptionHandler -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayRegistrationError +import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdk @@ -89,7 +89,7 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App } val pinCode = state.pinCode.guard { - onError(SaltPayRegistrationError.NeedPin) + onError(SaltPayActivationError.NeedPin) return@launch } @@ -99,7 +99,7 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App } if (!attestationResponse.success) { - onError(SaltPayRegistrationError.CardNotFound(attestationResponse.error)) + onError(SaltPayActivationError.CardNotFound(attestationResponse.error)) return@launch } @@ -117,7 +117,7 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App } saltPayManager.sendTransactions(registrationResponse.signedTransactions).successOr { - onError(SaltPayRegistrationError.FailedToSendTx) + onError(SaltPayActivationError.FailedToSendTx) return@launch } @@ -128,12 +128,12 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App withMainContext { handleInProgress = false - store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayRegistrationStep.KycIntro)) + store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.KycIntro)) } } } is OnboardingSaltPayAction.OpenUtorgKYC -> { - store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayRegistrationStep.KycStart)) + store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.KycStart)) } is OnboardingSaltPayAction.UtorgKYCRedirectSuccess -> { store.dispatch(OnboardingSaltPayAction.RegisterKYC) @@ -155,8 +155,8 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App try { assertPinValid(action.pin, getState().pinLength) store.dispatch(OnboardingSaltPayAction.SetPin(action.pin)) - store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayRegistrationStep.CardRegistration)) - } catch (error: SaltPayRegistrationError) { + store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.CardRegistration)) + } catch (error: SaltPayActivationError) { SaltPayExceptionHandler.handle(error) } } @@ -166,7 +166,7 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App scope.launch { val amountToClaim = getAmountToClaimIfNeeded(state.saltPayManager, state.amountToClaim).guard { - onException(SaltPayRegistrationError.NoFundsToClaim) + onException(SaltPayActivationError.NoFundsToClaim) return@launch } @@ -187,12 +187,12 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App ) } state.saltPayManager.claim(amountToClaim.value!!, signer).successOr { - dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayRegistrationStep.Claim)) + dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.Claim)) onException(it.error) return@launch } - dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayRegistrationStep.ClaimInProgress)) + dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.ClaimInProgress)) dispatchOnMain(OnboardingSaltPayAction.RefreshClaim) } } @@ -211,7 +211,7 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App handleInProgress = false handleClaimRefreshInProgress = false dispatchOnMain(OnboardingSaltPayAction.SetTokenBalance(balance)) - dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayRegistrationStep.Finished)) + dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.Finished)) } } else -> { @@ -221,13 +221,13 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App } data class UpdateResult( - val step: SaltPayRegistrationStep = SaltPayRegistrationStep.None, + val step: SaltPayActivationStep = SaltPayActivationStep.None, val amountToClaim: Amount? = null, ) suspend fun SaltPayActivationManager.updateActivationStatus( amountToClaim: Amount?, - step: SaltPayRegistrationStep, + step: SaltPayActivationStep, ): Result { Timber.d("updateSaltPayStatus") var updateResult = UpdateResult() @@ -248,9 +248,9 @@ suspend fun SaltPayActivationManager.updateActivationStatus( val fetchedAmountToClaim = getAmountToClaimIfNeeded(this, amountToClaim) updateResult = when { // if fetchedAmountToClaim = null -> then it already claimed - saltPayStep == SaltPayRegistrationStep.Claim && fetchedAmountToClaim == null -> { + saltPayStep == SaltPayActivationStep.Claim && fetchedAmountToClaim == null -> { updateResult.copy( - step = SaltPayRegistrationStep.Finished, + step = SaltPayActivationStep.Finished, amountToClaim = fetchedAmountToClaim, ) } @@ -298,7 +298,7 @@ private var handleInProgress: Boolean = false store.dispatchOnMain(OnboardingSaltPayAction.SetInProgress(value)) } -private suspend fun onError(error: SaltPayRegistrationError) { +private suspend fun onError(error: SaltPayActivationError) { withMainContext { handleInProgress = false SaltPayExceptionHandler.handle(error) @@ -314,12 +314,12 @@ private suspend fun onException(error: Throwable) { private suspend fun checkGasIfNeeded( saltPayManager: SaltPayActivationManager, - step: SaltPayRegistrationStep, + step: SaltPayActivationStep, ): Result { Timber.d("checkGasIfNeeded: for step: %s", step) - if (step == SaltPayRegistrationStep.KycStart || - step == SaltPayRegistrationStep.KycWaiting || - step == SaltPayRegistrationStep.Finished + if (step == SaltPayActivationStep.KycStart || + step == SaltPayActivationStep.KycWaiting || + step == SaltPayActivationStep.Finished ) { Timber.d("checkGasIfNeeded: no need to check for step: %s", step) return Result.Success(Unit) @@ -335,17 +335,17 @@ private suspend fun checkGasIfNeeded( private suspend fun registerKYCIfNeeded( saltPayManager: SaltPayActivationManager, - step: SaltPayRegistrationStep, + step: SaltPayActivationStep, ): Result { Timber.d("registerKYCIfNeeded: step: %s", step) - if (step != SaltPayRegistrationStep.KycStart) { + if (step != SaltPayActivationStep.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)) + store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.KycWaiting)) } Timber.d("saltPayManager.registerKYC()") @@ -354,7 +354,7 @@ private suspend fun registerKYCIfNeeded( private suspend fun checkRegistration( saltPayManager: SaltPayActivationManager, -): Result { +): Result { Timber.d("checkRegistration") return try { val registrationResponseItem = saltPayManager.checkRegistration().successOr { @@ -366,51 +366,51 @@ private suspend fun checkRegistration( val step = registrationResponseItem.toSaltPayStep() Timber.d("checkRegistration: step: %s", step) Result.Success(step) - } catch (ex: SaltPayRegistrationError) { + } catch (ex: SaltPayActivationError) { Timber.e(ex, "checkRegistration: step") Result.Failure(ex) } } -@Throws(SaltPayRegistrationError::class) -fun RegistrationResponse.Item.toSaltPayStep(): SaltPayRegistrationStep { +@Throws(SaltPayActivationError::class) +fun RegistrationResponse.Item.toSaltPayStep(): SaltPayActivationStep { return when { - passed != true -> throw SaltPayRegistrationError.CardNotPassed(this.error) - disabledByAdmin == true -> throw SaltPayRegistrationError.CardDisabled(this.error) + passed != true -> throw SaltPayActivationError.CardNotPassed(this.error) + disabledByAdmin == true -> throw SaltPayActivationError.CardDisabled(this.error) // go to claim screen - active == true -> SaltPayRegistrationStep.Claim + active == true -> SaltPayActivationStep.Claim // pinSet is false, go toPin screen - pinSet == false -> SaltPayRegistrationStep.NeedPin + pinSet == false -> SaltPayActivationStep.NeedPin kycStatus != null -> { when (kycStatus) { - KYCStatus.NOT_STARTED, KYCStatus.STARTED -> SaltPayRegistrationStep.KycStart - KYCStatus.WAITING_FOR_APPROVAL -> SaltPayRegistrationStep.KycWaiting - KYCStatus.CORRECTION_REQUESTED, KYCStatus.REJECTED -> SaltPayRegistrationStep.KycReject - KYCStatus.APPROVED -> SaltPayRegistrationStep.Claim + KYCStatus.NOT_STARTED, KYCStatus.STARTED -> SaltPayActivationStep.KycStart + KYCStatus.WAITING_FOR_APPROVAL -> SaltPayActivationStep.KycWaiting + KYCStatus.CORRECTION_REQUESTED, KYCStatus.REJECTED -> SaltPayActivationStep.KycReject + KYCStatus.APPROVED -> SaltPayActivationStep.Claim null -> throw UnsupportedOperationException() } } // kycDate is set, go to kyc waiting screen - kycDate != null -> SaltPayRegistrationStep.KycWaiting + kycDate != null -> SaltPayActivationStep.KycWaiting - else -> SaltPayRegistrationStep.KycIntro + else -> SaltPayActivationStep.KycIntro } } -@Throws(SaltPayRegistrationError::class) +@Throws(SaltPayActivationError::class) private fun assertPinValid(pin: String, pinLength: Int) { val array = pin.toCharArray() - if (array.size < pinLength) throw SaltPayRegistrationError.WeakPin + if (array.size < pinLength) throw SaltPayActivationError.WeakPin val filters: List> = listOf( AllSymbolsTheSameFilter(), ) if (filters.any { it.filter(pin) }) { - throw SaltPayRegistrationError.WeakPin + throw SaltPayActivationError.WeakPin } } \ No newline at end of file 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 c0dcec2395..419c8fb750 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 @@ -17,7 +17,7 @@ import com.tangem.tap.features.onboarding.products.wallet.saltPay.KYCProvider import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManager import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayConfig import com.tangem.tap.features.wallet.redux.ProgressState -import com.tangem.tap.persistence.SaltPayRegistrationStorage +import com.tangem.tap.persistence.SaltPayActivationStorage import com.tangem.tap.preferencesStorage import com.tangem.tap.store import java.math.BigDecimal @@ -33,7 +33,7 @@ data class OnboardingSaltPayState( val accessCode: String? = null, val amountToClaim: Amount? = null, val tokenAmount: Amount = Amount(SaltPayWorkaround.tokenFrom(Blockchain.SaltPay), BigDecimal.ZERO), - val step: SaltPayRegistrationStep = SaltPayRegistrationStep.None, + val step: SaltPayActivationStep = SaltPayActivationStep.None, val saltPayCardArtworkUrl: String? = null, val inProgress: Boolean = false, val claimInProgress: Boolean = false, @@ -60,7 +60,7 @@ data class OnboardingSaltPayState( scanResponse = scanResponse, gnosisRegistrator = gnosisRegistrator, paymentologyService = store.state.domainNetworks.paymentologyService, - registrationStorage = preferencesStorage.saltPayRegistrationStorage, + registrationStorage = preferencesStorage.saltPayActivationStorage, kycProvider = saltPayConfig.kycProvider, ) test(scanResponse, gnosisRegistrator) @@ -83,7 +83,7 @@ data class OnboardingSaltPayState( scanResponse: ScanResponse, gnosisRegistrator: GnosisRegistrator, paymentologyService: PaymentologyApiService, - registrationStorage: SaltPayRegistrationStorage, + registrationStorage: SaltPayActivationStorage, kycProvider: KYCProvider, ): SaltPayActivationManager { if (!scanResponse.isSaltPay()) { @@ -115,7 +115,7 @@ data class OnboardingSaltPayState( } } -enum class SaltPayRegistrationStep { +enum class SaltPayActivationStep { None, NoGas, NeedPin, 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 0bcfb5d36f..6552fbd9d5 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 @@ -41,7 +41,7 @@ import com.tangem.tap.features.onboarding.products.wallet.saltPay.UtorgWebViewCl import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.SaltPayDialog import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction 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.SaltPayActivationStep import com.tangem.tap.features.onboarding.products.wallet.ui.OnboardingWalletFragment import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.tap.store @@ -67,7 +67,7 @@ internal class OnboardingSaltPayView( } private var progressButton: SaltPayProgressButton? = null - private var previousStep: SaltPayRegistrationStep = SaltPayRegistrationStep.None + private var previousStep: SaltPayActivationStep = SaltPayActivationStep.None fun newState(state: OnboardingWalletState) { handleCardArtworks(state) @@ -133,16 +133,16 @@ internal class OnboardingSaltPayView( walletFragment.bindingSaltPay.onboardingSaltpayContainer.show() when (state.step) { - SaltPayRegistrationStep.NoGas -> handleNoGas() - SaltPayRegistrationStep.NeedPin -> handleNeedPin() - SaltPayRegistrationStep.CardRegistration -> handleCardRegistration() - SaltPayRegistrationStep.KycIntro -> handleKycIntro() - SaltPayRegistrationStep.KycStart -> handleKycStart(state) - SaltPayRegistrationStep.KycWaiting -> handleKycWaiting() - SaltPayRegistrationStep.KycReject -> handleKycReject() - SaltPayRegistrationStep.Claim -> handleClaim(state) - SaltPayRegistrationStep.ClaimInProgress -> handleClaim(state) - SaltPayRegistrationStep.Finished -> handleClaim(state) + SaltPayActivationStep.NoGas -> handleNoGas() + SaltPayActivationStep.NeedPin -> handleNeedPin() + SaltPayActivationStep.CardRegistration -> handleCardRegistration() + SaltPayActivationStep.KycIntro -> handleKycIntro() + SaltPayActivationStep.KycStart -> handleKycStart(state) + SaltPayActivationStep.KycWaiting -> handleKycWaiting() + SaltPayActivationStep.KycReject -> handleKycReject() + SaltPayActivationStep.Claim -> handleClaim(state) + SaltPayActivationStep.ClaimInProgress -> handleClaim(state) + SaltPayActivationStep.Finished -> handleClaim(state) } progressButton?.changeState(state.mainButtonState) } @@ -276,7 +276,7 @@ internal class OnboardingSaltPayView( topContainer.onboardingTvBalance.tvBalanceCurrency.text = "" when (state.step) { - SaltPayRegistrationStep.Claim -> { + SaltPayActivationStep.Claim -> { btnRefreshBalanceWidget.mainView.hide() claimValueString(state)?.let { tvHeader.text = getString(R.string.onboarding_title_claim, it) @@ -288,13 +288,13 @@ internal class OnboardingSaltPayView( store.dispatch(OnboardingSaltPayAction.Claim) } } - SaltPayRegistrationStep.ClaimInProgress -> { + SaltPayActivationStep.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.Finished -> { + SaltPayActivationStep.Finished -> { tvHeader.setText(R.string.common_success) tvBody.setText(R.string.onboarding_subtitle_success_claim) @@ -329,7 +329,7 @@ internal class OnboardingSaltPayView( ) } - private fun updateConstraints(currentStep: SaltPayRegistrationStep, @LayoutRes layoutId: Int) { + private fun updateConstraints(currentStep: SaltPayActivationStep, @LayoutRes layoutId: Int) { if (this.previousStep == currentStep) return with(claimBinding.onboardingTopContainer) { diff --git a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt index 723af0df99..d348cacfb7 100644 --- a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt @@ -15,7 +15,7 @@ class PreferencesStorage(applicationContext: Application) { val appRatingLaunchObserver: AppRatingLaunchObserver val usedCardsPrefStorage: UsedCardsPrefStorage val fiatCurrenciesPrefStorage: FiatCurrenciesPrefStorage - val saltPayRegistrationStorage: SaltPayRegistrationStorage + val saltPayActivationStorage: SaltPayActivationStorage init { incrementLaunchCounter() @@ -24,7 +24,7 @@ class PreferencesStorage(applicationContext: Application) { usedCardsPrefStorage.migrate() fiatCurrenciesPrefStorage = FiatCurrenciesPrefStorage(preferences, MoshiJsonConverter.INSTANCE) fiatCurrenciesPrefStorage.migrate() - saltPayRegistrationStorage = SaltPayRegistrationPrefStorage(applicationContext, MoshiJsonConverter.INSTANCE) + saltPayActivationStorage = SaltPayActivationPrefStorage(applicationContext, MoshiJsonConverter.INSTANCE) } var chatFirstLaunchTime: Long? diff --git a/app/src/main/java/com/tangem/tap/persistence/SaltPayRegistrationPrefStorage.kt b/app/src/main/java/com/tangem/tap/persistence/SaltPayActivationPrefStorage.kt similarity index 69% rename from app/src/main/java/com/tangem/tap/persistence/SaltPayRegistrationPrefStorage.kt rename to app/src/main/java/com/tangem/tap/persistence/SaltPayActivationPrefStorage.kt index 0fd6a6cac6..9bfbddc594 100644 --- a/app/src/main/java/com/tangem/tap/persistence/SaltPayRegistrationPrefStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/SaltPayActivationPrefStorage.kt @@ -10,27 +10,27 @@ import com.tangem.tangem_sdk_new.storage.createEncryptedSharedPreferences /** [REDACTED_AUTHOR] */ -interface SaltPayRegistrationStorage { - var data: SaltPayRegistratorData +interface SaltPayActivationStorage { + var data: SaltPayActivationData fun reset() companion object { - fun stub(): SaltPayRegistrationStorage = object : SaltPayRegistrationStorage { - override var data: SaltPayRegistratorData = SaltPayRegistratorData() + fun stub(): SaltPayActivationStorage = object : SaltPayActivationStorage { + override var data: SaltPayActivationData = SaltPayActivationData() override fun reset() {} } } } -data class SaltPayRegistratorData( +data class SaltPayActivationData( val transactionsSent: Boolean = false, ) -class SaltPayRegistrationPrefStorage( +class SaltPayActivationPrefStorage( context: Context, private val converter: MoshiJsonConverter, -) : SaltPayRegistrationStorage { +) : SaltPayActivationStorage { private val storage: ArmadilloSharedPreferences = SecureStorage.createEncryptedSharedPreferences( context = context, @@ -43,7 +43,7 @@ class SaltPayRegistrationPrefStorage( fetch() } - override var data: SaltPayRegistratorData = SaltPayRegistratorData() + override var data: SaltPayActivationData = SaltPayActivationData() set(value) { field = value if (!isFetching) save() @@ -52,7 +52,7 @@ class SaltPayRegistrationPrefStorage( override fun reset() { storage.edit(true) { this.remove(REGISTRATION_DATA_KEY) - data = SaltPayRegistratorData() + data = SaltPayActivationData() } } @@ -62,14 +62,14 @@ class SaltPayRegistrationPrefStorage( } } - private fun fetch(): SaltPayRegistratorData { + private fun fetch(): SaltPayActivationData { isFetching = true val jsonData = storage.getString(REGISTRATION_DATA_KEY, null) data = if (jsonData == null) { - SaltPayRegistratorData() + SaltPayActivationData() } else { - converter.fromJson(jsonData) ?: SaltPayRegistratorData() + converter.fromJson(jsonData) ?: SaltPayActivationData() } isFetching = false From 7913e52216338b392c770e339a9fdf0d6f7f2ab8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 25 Oct 2022 15:46:12 +0500 Subject: [PATCH 113/113] Updated on 2026-08-14 --- app/src/main/assets/tos.html | 1366 ----------------- .../details/ui/details/DetailsViewModel.kt | 3 +- .../disclaimer/redux/DisclaimerAction.kt | 15 +- .../disclaimer/redux/DisclaimerMiddleware.kt | 61 +- .../disclaimer/redux/DisclaimerReducer.kt | 12 +- .../disclaimer/redux/DisclaimerState.kt | 25 +- .../disclaimer/ui/DisclaimerFragment.kt | 22 +- .../tap/features/home/redux/HomeAction.kt | 1 - .../tap/features/home/redux/HomeMiddleware.kt | 43 +- .../tap/persistence/DisclaimerPrefStorage.kt | 25 + .../tap/persistence/PreferencesStorage.kt | 16 +- .../main/res/layout/fragment_disclaimer.xml | 22 +- 12 files changed, 181 insertions(+), 1430 deletions(-) delete mode 100644 app/src/main/assets/tos.html create mode 100644 app/src/main/java/com/tangem/tap/persistence/DisclaimerPrefStorage.kt diff --git a/app/src/main/assets/tos.html b/app/src/main/assets/tos.html deleted file mode 100644 index 728ab43409..0000000000 --- a/app/src/main/assets/tos.html +++ /dev/null @@ -1,1366 +0,0 @@ - - - - - file_1666291144852 - - - -

SaltPay Oly Card Terms of Service

-


-

We are delighted that you (“you”, - “your”, or the “Cardholder”) have chosen to use the SaltPay Oly Card (the “Card”). The terms - and conditions set out here (“Terms”) govern your use of your Card as operated by SaltPay.

-

When you use the Card, you also agree to - the SaltPay General Terms - which are incorporated into these Terms by - reference, including any amendments made to those from time to time. If you do not understand any provisions of - these Terms, please contact us before using the Card. For information about how we treat your personal data, - please see our Privacy Policy.

-


-
    -
  1. - Definitions

    -

    - Affiliate: means any entity controlling, controlled by or under common control with that party, where “control” means the ownership of more than 50% of (i) the voting securities or (ii) an interest in the assets, profits, or earnings of an entity. -

    -

    - Blockchain Asset: means a digital data stored in and protected by the blockchain network, including but not limited to ownership right and value of cryptocurrency, tokenized assets, non-fungible asset. -

    -

    Cardholder: a person who holds a SaltPay Oly Card. -

    -

    Private Key: a secret cryptographic key which provides full control over a Blockchain Asset. -

    -

    Public - Key: means a cryptographic key, which provides access to information about a Blockchain Asset, including but not limited to Blockchain Address. -

    -

    SaltPay: Salt Pay IIB hf. -

    -

    SaltPay Oly Card / Card: physical card which stores Private Key and Public Key.

    -

    Services: enabling the use of the SaltPay Oly Card at physical point of sale terminals that are enabled for the acceptance of Visa Debit cards. -

    -

    - Tangem: means Tangem AG, a company incorporated in Switzerland with company number 1315048, whose address is at Baarerstrasse 10, 6300 Zug. -

    -

    Tangem - Mobile Application: Third party mobile application, providing interoperability between SaltPay Oly Card and blockchain, and working on NFC-capable smartphones and tablets using Google Android and Apple iOS operating systems. -

    -

    Tangem - Service: means the service provided by Tangem, which provides and administers the Tangem Mobile Application, which enables Cardholders to generate and store Public Keys and Private Keys in a hardware crypto wallet. -

    -

    Transaction: means any payment made by a Cardholder at any physical POS terminal using the Card. -

    -

    Utorg Service: means providing services of exchanging a virtual currency against a fiat currency. -

    -


  2. -
  3. Important Legal - Stuff

    -

    We think - it’s important that we highlight and clearly explain how SaltPay safeguards its interests, while offering - its services to its Cardholders (you!) which includes limitation of liability and disclaimers. The sections - set out below reflect those safeguards and how they might impact you. It’s therefore important that you read - them carefully. If you’re unsure about any of them, please get in touch.

    -
      -
    1. - These Terms of Service (the “Terms”) govern the use of SaltPay Oly Cards to authorised employees - of SaltPay and its Affiliates.

    2. -
    3. - The Cards may be used for storage of Private Key and Public Key to the Cardholder’s Blockchain Assets - and authentication of Card Transactions with the purpose of transfer of Blockchain Assets for goods and - services purchasing through the Visa payments infrastructure.

      -


    4. -
    5. The Cardholder - agrees that these Terms are binding.

    6. -
    7. The Cardholder shall be - the only person having physical access to the Card.

    8. -
    9. - The Cardholder acknowledges and agrees that SaltPay does not provide backup or recovery of Private Key - and Public Key stored by the Card. The Cardholder creates the backup of the SaltPay Card at the point at - which they activate the Card through the Tangem Mobile Application by linking their Card with Tangem - Wallet.

      -


    10. -
    11. The conditions of - use of the Tangem Wallet and Mobile Application are defined in the terms of service for the Tangem - Wallet and Mobile Application, which will also be available upon downloading the application.

      -


    12. -
    -
  4. -
  5. SaltPay’s - obligations

    -
      -
    1. SaltPay agrees to - provide the Cardholder with the Service in accordance with these Terms and within the agreed scope.

      -
    2. -
    3. Under the condition - that the Cardholder exercises their obligations set out under Clause 4 of these Terms, SaltPay shall use - its best endeavours to ensure that the Card will function properly and without restriction until the - date on which the Card expires.

    4. -
    5. SaltPay shall ensure - that the Card prevents duplication of the Private Key, and that the Cardholder has exclusive control - over the Blockchain Asset unless the contrary is imposed by specific blockchain network rules e.g., two - or more private keys can be used to control the same Blockchain Asset.

      -


    6. -
    7. SaltPay - agrees that it will not keep any records of Cardholder information, save for the Cardholder’s full name, - including but not limited to the amount of Blockchain Asset stored on the Card, the Private Key or - personalised history of Card usage.

      -


    8. -
    9. For the - avoidance of doubt, SaltPay does not guarantee that the operation of Card and/or Tangem Mobile - Application will be secure, accurate, complete, uninterrupted, without error or free of viruses, worms, - other harmful components or other program limitations. SaltPay may, at its sole discretion and without - obligation to do so, correct, modify, amend, enhance, improve and make any other changes to Card.

      -


    10. -
    -
  6. -
  7. The Cardholder’s - obligations

    -
      -
    1. By - accepting these Terms, you represent and warrant that (a) you have the authority to execute and perform - the obligations contained here; (b) you (and your employees, directors, contractors and agents) will - comply with all law applicable to your business and the use of the Card; and (c) you will not use the - Card, directly or indirectly, for any fraudulent or illegal undertaking.

    2. -
    3. - Upon receiving the Card, the Cardholder, if they choose to use the Tangem Service, should download the - Tangem Mobile Application in order to determine the amount of Blockchain Asset stored on the Card.

      -


    4. -
    5. When the Cardholder - pays for goods or services using their SaltPay Oly Card through contactless confirmation using NFC, they - confirm that there is a sufficient balance in the Tangem Wallet and thereby agree that amount is - deducted from the account.

    6. -
    7. - The Cardholder shall at all times keep the Card and the means of access (backup card and pin codes) to - the Card separate from each other.

    8. -
    9. - The Cardholder shall at all times be aware of where the Card is and regularly ensure that it is still in - their possession. The Cardholder shall avoid even temporary possession of the Card by any other person. - If the Card is lost, stolen or destroyed, control over the corresponding Blockchain Asset may be - permanently lost.

    10. -
    11. - The Cardholder shall treat the Card with care and protect the Card from mechanical damage, high - temperatures, strong electromagnetic fields, and other harmful factors.

    12. -
    13. - In the event that the Card is stolen or - lost, and/or if a Cardholder becomes aware of an unauthorised or incorrectly initiated transaction, - the Cardholder shall contact Tangem as soon as possible by sending an email to support@tangem.com - or by using the chat function on the Official Mobile Application.

      -


    14. -
    15. - In the event that the Cardholder wishes - to deactivate their account, they shall contact Tangem by sending an email to support@tangem.com - or by using the chat function on the Official Mobile Application.

      -
    16. -
    17. - The Card can be used only with the official Tangem Mobile Application downloaded from Google Play and - App Store.

    18. -
    19. - The Tangem Mobile Application shall be the exclusive source of information concerning the Blockchain - Address of the Blockchain Asset and corresponding Public Key stored on the Card.

    20. -
    21. - The Cardholder shall only use Near-Field Devices (“NFC Devices”) that are capable of running the - Tangem Mobile Application.

    22. -
    23. - The Card shall be used only for physically tapping and holding near the Cardholder’s NFC Device when the - Tangem Mobile Application requests it. For the avoidance of doubt, the Card cannot be used to pay using - any method which involves gateways.

      -


    24. -
    -
  8. -
  9. The - Cardholder’s liabilities

    -
      -
    1. - The Cardholder is liable for all liabilities arising from the use and misuse of the Card. In any case, - the Cardholder is liable for all transactions authorised using a means of access.

    2. -
    3. - Any loss or damage resulting from the forwarding of the Card and/or means of access shall be borne by - the Cardholder.

    4. -
    5. - Loss or damage incurred by the Cardholder in connection with the possession or use of the Card shall be - borne solely by the Cardholder. SaltPay assumes no liability if the Card cannot be used due to a - technical defect or because it has been cancelled or blocked.

      -


    6. -
    7. Cardholder is only - permitted to use the Products for his personal, non-commercial use. Cardholder is not permitted to - resell the Card.

      -


    8. -
    -
  10. -
  11. SaltPay’s right to - suspend and/or reject payments initiated by the Cardholder

    -
      -
    1. - SaltPay may rightfully suspend any payments initiated by you using the Card in any of the following - circumstances:

      -
        -
      1. - in case of any Transactions which SaltPay suspects may not be valid Transactions, we may suspend - payment until we are satisfied that the Transactions are valid and legitimate;

      2. -
      3. - where SaltPay has requested information from the Cardholder, we may suspend all payments - initiated using the Card until such information has been provided to SaltPay’s satisfaction;

        -
      4. -
      5. - in case of the Cardholder’s violation of these Terms or the terms of service entered into - between the Cardholder and Tangem or Utorg, SaltPay may suspend all payments initiated using the - Card until the violation has been remedied by the Cardholder; and

      6. -
      7. - in case of a suspicion of Cardholder fraud, any illegal activity or a potential violation of the - Terms or the terms of service entered into between you and Tangem or Utorg, SaltPay may suspend - all payments initiated using the Card until an investigation has been concluded by us and we are - satisfied that there has been no Cardholder fraud or violation of the Terms.

      8. -
      -
    2. -
    3. To - the extent permitted by law, we shall notify you of any suspended payment and the reason for suspending - such payment. We reserve the right to change or assert additional reasons for withholding payment should - new information come to SaltPay’s attention that would independently give us a right to suspend in - accordance with clause 6.1.

    4. -
    5. - Once the circumstances entitling us to suspend payment have ended, we will enable the Transaction to be - forwarded on to Tangem, provided that there are no circumstances then in existence which entitle us to - withhold payment.

    6. -
    7. In - the event that a Transaction is found to be invalid or illegitimate, we may rightfully reject the - payment initiated by you and the you will not be able to proceed to access the Tangem Service or Utorg - Service for that Transaction.

    8. -
    9. - For the avoidance of doubt, where evidence of any fraud or illegal activity is found, we may rightfully - terminate the Card and you will no longer have access to the Service, Tangem Service or Utorg - Service.

      -


    10. -
    -
  12. -
  13. - Limitation of liability

    -
      -
    1. - The Card is provided as-is, and except as expressly stated in these Terms, SaltPay provides no express - or implied warranties or conditions, and SaltPay disclaims and excludes any implied terms, - representations, warranties, and conditions with respect to the Card, including warranties of - merchantability, fitness for a particular purpose, title, satisfactory quality and non-infringement, as - well as any other implied warranties, such as warranties regarding data loss, availability, accuracy, - functionality and lack of viruses. These disclaimers will apply except to the extent applicable law does - not permit them. Any warranties, guarantees, or conditions that cannot be disclaimed as a matter of law, - but which may be limited in duration, last for one year from the date on which you receive the Card.

      -
    2. -
    3. - SaltPay shall not be liable and does not warrant or make any representations regarding the services - provided by third parties in relation to the Tangem Service, including but not limited to the Tangem - Mobile Application and the Utorg Service. The relationship between the Cardholder and any third party in - relation to the Tangem Service shall be governed by separate agreements provided below.

    4. -
    5. In - no event shall SaltPay and/or any of its Affiliates be liable for any damages whatsoever, including - direct, indirect, extraordinary, incidental or consequential damages of any kind resulting from or - arising out of the use of Card and/or the Tangem Mobile Application or inability to use Card and/or the - Tangem Mobile Application, failure of Card and/or the Tangem Mobile Application to perform as - represented or expected, loss of goodwill or profits.

    6. -
    7. - SaltPay shall not be held liable for any failure to be able to use the Card and/or the Tangem Mobile - Application for any reason whatsoever, nor will SaltPay be held liable for the loss of the Blockchain - Asset resulting from a malfunction or inoperability of the blockchain network hosting Blockchain Asset, - as well as the inaccessibility of its public servers and services.

    8. -
    9. - SaltPay shall not be held liable for the loss of profits, income, value or any indirect, extraordinary, - consequential, exemplary or punitive damages.

    10. -
    11. SaltPay shall not be - held liable for loss or breakdown of the Card and/or the Tangem Mobile Application.

    12. -
    13. - SaltPay shall not be held liable for any loss of Blockchain Asset in the event of loss or total - breakdown of the Card and/or the Tangem Mobile Application.

    14. -
    15. - The Cardholder hereby acknowledges and agrees that these limitations of liability are agreed allocations - of risk constituting in part the consideration for using the Card and/or the Tangem Mobile Application - and such limitations will apply notwithstanding the failure of essential purpose of any limited

      -

      - remedy, and even if SaltPay and/or any SaltPay Affiliates have been advised of the possibility of - such liabilities and/or damages.

      -


    16. -
    -
  14. -
  15. Term and - Termination

    -
      -
    1. - These Terms become effective upon the date (i) you accept these Terms online (or in another manner - expressly approved by us) or (ii) you first access or use Tangem and shall terminate on the date on - which the Card expires.

      -


    2. -
    3. We may terminate - our arrangement with you under these Terms for any reason or no reason, at any time, by giving 2 months’ - prior notice to this effect. We also reserve the right, pursuant to clause 6, to suspend or terminate - the use of the Card effective immediately.

      -


    4. -
    5. Where your - arrangement with us under these Terms terminates for any reason, you will, where applicable, remain - liable for any fees, charges and other payment obligations you owe us.

      -


    6. -
    -
  16. -
  17. General

    -
      -
    1. - Except as expressly provided herein, these Terms are a complete statement of the agreement between you - and us regarding your use of Tangem. In the event of a conflict between these Terms and the SaltPay - General Terms or any other SaltPay agreement or policy, these Terms shall prevail.

      -


    2. -
    3. These Terms do not - limit any rights that SaltPay may have under trade secret, copyright, patent, or other laws. No waiver - of any term of these Terms shall be deemed a further or continuing waiver of such term or any other - term.

    4. -
    5. In - the event any court shall declare any section or sections of this Agreement invalid or void, such - declaration shall not invalidate the entire Agreement and all other paragraphs of the Agreement shall - remain in full force and effect.

    6. -
    7. For the avoidance of doubt, - provisions around general terms not covered in these Terms of Service, including but not limited to - restrictions and unauthorised or illegal use, intellectual property, data, taxes, indemnification, - warranties, severability, force majeure, and disputes, shall be governed by the SaltPay General - Terms.

    8. -
    -
  18. -
-

TANGEM - WALLET AND TANGEM MOBILE APPLICATION

-

Terms of Service

-


-

PLEASE READ THESE TERMS OF SERVICE - CAREFULLY. BY CLICKING TO ACCEPT, OR BY ACCESSING OR USING OUR SERVICES, YOU AGREE THAT YOU HAVE READ, UNDERSTOOD, - AND ACCEPT ALL OF THE TERMS AND CONDITIONS CONTAINED HEREIN. BY PURCHASE OF THE SALTPAY OLY CARD WITH THE TANGEM - WALLET OR BY USING THE SALTPAY OLY CARD OR BY USING TANGEM WALLET OR BY USING TANGEM MOBILE APPLICATION, YOU - DEMONSTRATE YOUR AGREEMENT TO THESE TERMS AND CONDITIONS CONTAINED HEREIN.

-


-
    -
  1. DEFINITIONS

    -


    -


    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    - “Cardholder

    - means a person who owns Tangem Wallet and SaltPay Oly Card.

    - “Blockchain Asset

    - means a digital data stored in and protected by the blockchain network, including but not limited to - ownership right and value of cryptocurrency, tokenized assets, non-fungible asset.

    - “Blockchain Address

    - means a unique identifier that serves as a virtual location of the Blockchain Asset in the - blockchain.

    - “Card Transaction

    - means transfer of Blockchain Asset from the Blockchain Address associated with the Public Key stored - on the SaltPay Oly Card for goods and services purchased through traditional payments rail.

    - “Official Mobile Application

    - means an application developed and distributed by Tangem, providing interoperability between SaltPay - Oly Card or Tangem Wallet and blockchain, and working on NFC-capable smartphones and tablets using - Google Android and Apple iOS operation systems.

    - “Private Key

    - a secret cryptographic key which provides full control over a Blockchain Asset.

    - “Tangem

    - means Tangem AG, Baarerstrasse 10, 6300 Zug.

    - “Public Key

    - means a cryptographic key, which provides access to information about a Blockchain Asset, including - but not limited to Blockchain Address

    - “Services

    - means the purchase and/or use of the Tangem Wallet and the services or any other features, - technologies or functionalities linked to the Card provided or operated by Tangem AG via the website - or Official Mobile Applications.

    - “Tangem Wallet/Tangem Card / Card

    - means physical card which stores Private Key and Public Key and is used as a backup card for SaltPay - Oly Card.

    -


  2. -
  3. GENERAL - PROVISIONS

    -


    -
      -
    1. - These Terms of Service (the “Terms”) govern the use of Tangem Wallet and/or Official Mobile - Application provided by Tangem AG (referred to as "Tangem", "we" or - "us" in this document) and the related services or any other features, technologies or - functionalities linked to the Tangem Wallet. Tangem is a company

      -

      - incorporated under the laws of Switzerland, with a registered address at Baarerstrasse 10, 6300 Zug, - Switzerland.

      -


    2. -
    3. Tangem Wallet - represents a backup to the SaltPay Oly Card, which may be used only for storage of Private Key and - Public Key to Cardholder’s Blockchain Assets. Tangem Wallet, unlike SaltPay Oly Card, can’t be used for - goods and services purchasing through traditional payments rail. The use of SaltPay Oly Card is governed - by a separate Terms of Service, provided above.

      -


    4. -
    5. Official Mobile - Application is intended for usage only with SaltPay Oly and Tangem Wallet, providing interoperability - between the cards and blockchain via NFC interface. Official Mobile Application DOES NOT:

      -
        -
      1. - Generate, store, transmit, or have access to private (secret) cryptographic keys to blockchain - wallets holding Blockchain assets.

      2. -
      3. Generate, - store, transmit, or have access to secret keys, passwords, passphrases, recovery phrases that - can be used to restore or to copy private (secret) keys to blockchain wallets holding Blockchain - assets.

      4. -
      5. Provide - exchange, trading, investment services on behalf of Tangem.

        -


      6. -
      -
    6. -
    -
  4. -
  5. RIGHTS AND - OBLIGATIONS

    -


    -
      -
    1. Cardholder agrees that - these Terms are binding.

      -


    2. -
    3. Cardholder shall be the - only person having physical access to the Tangem Wallet.

      -


    4. -
    5. - Cardholder acknowledges and agrees that Tangem does not provide backup or recovery of Private Key and - Public Key stored by the Tangem Wallet.

      -


    6. -
    7. Tangem does not - keep records of Cardholder’s personal data, the amount of Blockchain Asset stored on the Card, the - Private Key, or personalized history of Card usage.

      -


    8. -
    -
  6. -
  7. COSTS

    -


    -
      -
    1. - Costs, fees and commission (the “Costs”) may be charged in connection with the use of SaltPay Oly Card. - These Costs are disclosed in the Official Mobile Application to the Cardholder.

      -


    2. -
    3. Amendments to Costs - due to changing expenses or market conditions may be made at any time via adjustments to the fee - schedules. Such amendments shall be communicated to the Cardholder in an appropriate manner. Upon - notification and in the event of the objection, the Cardholder may cancel the SaltPay Oly Card with - immediate effect.

    4. -
    -
  8. -
  9. - CARDHOLDER’S DUTIES OF CARE

    -


    -

    In particular, the Cardholder shall exercise the - following duties of care:

    -


    -
      -
    1. - Upon receiving the SaltPay Oly Card and the Tangem Wallet, the Cardholder should download the Official - Mobile Application from Google Play or Apple app store and create the backup of the SaltPay Oly Card by - linking SaltPay Oly card with Tangem backup wallet following the instructions in the Official Mobile - Application.

      -


    2. -
    3. Cardholder - shall keep the SaltPay Oly Card and Tangem Wallet with care and separate from each other.

      -


    4. -
    5. - Cardholder must always know where the Tangem Wallet is and regularly ensure that it is still in his/her - possession. He/she shall avoid even temporary possession of the Tangem Wallet by any other person.

      -


    6. -
    7. Cardholder shall - treat the Tangem Wallet in the same manner as physical money (cash) and keep it safe. If the SaltPay Oly - Card and Tangem Wallet are lost, stolen or destroyed at the same time, control over the corresponding - Blockchain Asset may be permanently lost.

      -


    8. -
    9. Official Mobile - Application shall be the only source of information about the Blockchain Address of the Blockchain Asset - and corresponding Public Key stored on the SaltPay Oly Card/Tangem Wallet.

      -


    10. -
    11. Cardholder shall - only use Near-Field Devices (the “NFC”) devices that are capable of running Official Mobile - Applications. He/she shall avoid leaving the Tangem Wallet in the proximity of the NFC devices of other - persons.

      -


    12. -
    13. Tangem Wallet shall - be used only for physically tapping and holding near Cardholder’s NFC device when Official Mobile - Application requests it.

      -


    14. -
    15. Cardholder shall - keep the Tangem Wallet with care and protect the Tangem Wallet from mechanical damage, high - temperatures, strong electromagnetic fields, and other harmful factors.

      -


    16. -
    -
  10. -
  11. RIGHTS AND - RESPONSIBILITIES OF CARDHOLDER

    -


    -
      -
    1. - The Cardholder is liable for all liabilities arising from the use of the Tangem Wallet and/or Tangem - Official Mobile Application. As a matter of principle, the Cardholder is liable for any risks resulting - from the misuse of the Tangem Wallet and/or Official Mobile Application. In any case, the Cardholder is - liable for all transactions authorized using a means of access.

      -


    2. -
    3. Any loss or damage - resulting from the forwarding of the Tangem Wallet and/or means of access shall be borne by the - Cardholder.

      -


    4. -
    5. Loss or damage - incurred by the Cardholder in connection with the possession or use of the Tangem Wallet and/or Official - Mobile Application shall be borne solely by the Cardholder. Tangem assumes no liability if the Tangem - Wallet and/or Official Mobile Application cannot be used due to a technical defect or because it has - been canceled, blocked or the spending limit has been adjusted.

      -


    6. -
    7. Cardholder is only - permitted to use the Tangem Wallet and Official Mobile Application for his personal, non-commercial use. - Cardholder is not allowed to resell the Tangem Wallet.

    8. -
    9. - Cardholder is solely responsible to determinate what, if any, taxes apply to Card Transactions. Tangem - or contributors to the Official Mobile Application are NOT responsible for determining the taxes that - apply to Card Transactions.

      -


    10. -
    11. Before Cardholder engages in transactions - using an electronic system, Cardholder should carefully review the rules and regulations of the - third-party provider Utorg OÜ (https://utorg.pro/terms/), - who provides exchange services for SaltPay Oly Card. Online trading has inherent risk due to system - response and access times that may vary due to market conditions, system performance, and other factors. - Cardholder should understand, fully accept and take on these and additional risks before executing any - Card Transactions.

      -


    12. -
    13. There is - considerable exposure to risk in the Blockchain Asset exchange transaction. Any transaction involving - the Blockchain Asset involves risks including, but not limited to, the potential for changing economic - conditions that may substantially affect the price or liquidity of the Blockchain Asset. Investments in - the Blockchain Asset exchange speculation may also be susceptible to sharp rises and falls as the - relevant market values fluctuate. It is for this reason that when speculating in such markets it is - advisable to use only risk capital.

      -


    14. -
    -
  12. -
  13. RESPONSIBILITIES AND - LIABILITIES OF TANGEM

    -


    -
      -
    1. - Tangem does not warrant or make any representations regarding the use, the inability to use or operate, - or the results of the use or operation of the Tangem Wallet and/or Tangem Official Mobile - Application.

      -


    2. -
    3. Tangem does not - provide any backup or recovery of Private Key and Public Key stored on the Tangem Wallet.

      -


    4. -
    5. - Tangem does not keep any records of Cardholder information, the amount of Blockchain Asset stored on the - Tangem Wallet or SaltPay Oly Card, the Private Key, or personalized history of cards usage.

      -


    6. -
    7. Tangem shall not be - held liable for any failure to be able to use the Tangem Wallet and/or Official Mobile Application, for - any reason whatsoever, nor will Tangem be held liable for the loss of the Blockchain Asset resulting - from a malfunction or inoperability of the blockchain network hosting Blockchain Asset, as well as the - inaccessibility of its public servers and services.

      -


    8. -
    9. The Tangem does not - guarantee that the operation of Tangem Wallet and/or Official Mobile Application will be secure, - accurate, complete, uninterrupted, without error or free of viruses, worms, other harmful components or - other program limitations. Tangem may, at its sole discretion and without obligation to do so, correct, - modify, amend, enhance, improve and make any other changes to Tangem Wallet and/or Official Mobile - Application.

      -


    10. -
    11. Tangem shall not be - held liable for the loss of profits, income, value or any indirect, extraordinary, consequential, - exemplary or punitive damages.

      -


    12. -
    13. Tangem shall - not be held liable for loss or breakdown of the Tangem Wallet.

      -


    14. -
    15. - Tangem shall not be held liable for any loss of Blockchain Asset in the event of loss or total breakdown - of the Tangem Wallet or SaltPay Oly Card.

    16. -
    -
  14. -
  15. - GUARANTEES

    -


    -
      -
    1. - Under the condition that the Cardholder exercises the duties of care as stated in Clause 5, Tangem - guarantees that the Tangem Wallet will function properly and without restriction for a period of 2 (two) - years.

      -


    2. -
    3. Tangem guarantees - that the Tangem Wallet prevents duplication of the Private Key and that the Cardholder has exclusive - control over the Blockchain Asset unless the contrary is imposed by specific blockchain network - rules,

      -

      e.g. two or more - private keys can be used to control the same Blockchain Asset.

      -


    4. -
    -
  16. -
  17. - LIMITATION OF LIABILITY

    -


    -
      -
    1. - The Tangem Wallet, including without limitation any content, data and information related thereto, is - provided on an “as is” basis and “as available” basis, without any warranties of any kind, express or - implied warranties of use, merchantability or suitability for a certain purpose or use, including - without limitation, the quality of products and services provided by users, third-party services, and/or - exchanges (except for the guarantees set forth in Clause 8).

      -


      -

      Official Mobile - Application is provided on an “as is” basis and “as available” basis without any warranties of any - kind regarding Official Mobile Application and/or any content, data, materials and/or services - provided on Official Mobile Application.

      -


    2. -
    3. Tangem and its - affiliates, including any of their officers, directors, shareholders, employees, sub-contractors, - agents, parent companies, subsidiaries and other affiliates (collectively, the “Tangem Affiliates”), - jointly and severally, disclaim and make no representations or warranties as to the usability, accuracy, - quality, availability, reliability, suitability, completeness, truthfulness, usefulness or effectiveness - of any content, data, results or other information obtained or generated by Tangem and/or any user - related to you or any other user of the Tangem Wallet, and Official Mobile Applications.

      -


    4. -
    5. In no event shall - Tangem and/or any of the Tangem Affiliates be liable for any damages whatsoever, including direct, - indirect, extraordinary, incidental or consequential damages of any kind, but not limited to, resulting - from or arising out of the use of Tangem Wallet and/or Official Mobile Applications or inability to use - Tangem Wallet and/or Official Mobile Applications, failure of Tangem Wallet and/or Official Mobile - Applications to perform as represented or expected, loss of goodwill or profits, or loss of data arising - out of or in any way connected with the use of Tangem Wallet and/or Official Mobile Applications. In no - event shall Tangem and/or any of the Tangem Affiliates be liable for the performance or failure of the - Tangem Wallet and/ or Official Mobile Applications to perform under these Terms of Use and any other act - or omission by Tangem by any cause whatsoever including without limitation damages arising from the - conduct of any users, third party services and/or exchanges. In no way Tangem or contributors to - Official Mobile Application are responsible for the actions, decisions, or other behavior taken or not - taken by Cardholder in reliance upon Tangem Official Mobile Application.

      -


    6. -
    7. You hereby - acknowledge and agree that these limitations of liability are agreed allocations of risk constituting in - part the consideration for using the Tangem Wallet and Official Mobile Applications and such limitations - will apply notwithstanding the failure of essential purpose of any limited remedy, and even if Tangem - and/or any Tangem Affiliates has been advised of the possibility of such liabilities and/or damages.

      -


    8. -
    9. Tangem will not be - responsible for any losses, damages or claims arising from events falling within the scope of the - following five categories:

      -
        -
      1. - Mistakes made by Cardholder, e.g., forgotten passwords, payments sent to wrong addresses, and - accidental deletion of blockchain wallets on Tangem Wallet or SaltPay Oly cards.

      2. -
      3. Problems of - Official Mobile Application and/or any blockchain- or cryptocurrency- related software or - service, e.g., corrupted files, incorrectly constructed transactions, unsafe cryptographic - libraries, malware.

      4. -
      5. Technical - failures in the hardware of Cardholder, including cards, of any blockchain- or - cryptocurrency-related software or service, e.g., data loss due to a faulty or damaged storage - device.

      6. -
      7. Security - problems experienced by Cardholder, e.g., unauthorized - access to Cardholders' wallets and/or accounts.

      8. -
      9. Actions or - inactions of third parties and/or events experienced by third parties, e.g., bankruptcy of - service providers, information security attacks on service providers, and fraud conducted by - third parties.

        -


      10. -
      -
    10. -
    -
  18. -
  19. SEVERABILITY

    -


    -
      -
    1. In - the event any court shall declare any section or sections of this Agreement invalid or void, such - declaration shall not invalidate the entire Agreement and all other paragraphs of the Agreement shall - remain in full force and effect.

      -


    2. -
    -
  20. -
  21. ARBITRATION AND - GOVERNING LAW

    -


    -
      -
    1. - These Terms of Service are governed exclusively by the laws of Switzerland, without regard to its - conflict of law rules. You consent to the exclusive jurisdiction of the court of Zug, Switzerland for - any dispute arising under this Agreement.

    2. -
    -
  22. -
-

Utorg OÜ Terms of Use

-


-

These Terms of Use constitute an - electronic agreement between you (hereinafter the "User") and Utorg OÜ (hereinafter the “Utorg.pro”, - “we”, “us”, “our”), a company incorporated in Estonia with Company No. 14786273, located at - Roosikrantsi tn 2-1068, Kesklinna linnaosa,Tallinn, Harju maakond, 10119, that applies to the User's use of this - website, any and all services, products and content provided by Utorg.pro.

-


-

As used herein, - "Utorg.pro" refers to the company Utorg OÜ, including but not limited to, its owners, directors, - investors, officers, employees, agents or other related parties, unless otherwise provided herein.

-


-

These Terms of Use contain - important provisions, which the User must consider carefully when choosing whether to use the services, products and - content of Utorg.pro. Please read these Terms of Use carefully before agreeing to them.

-


-

The User is solely responsible for - understanding and complying with any and all laws, rules and regulations of his/her specific jurisdiction that may - be applicable to the User in connection with the use of any and all services, products and content of Utorg.pro.

-


-

These Terms of Use incorporate our Privacy Policy - as well as any other policies published on the Site by reference, so by accepting these Terms of Use, the User agrees with and accepts all the policies published on the Site. -

-


-

IF THE USER DOES NOT ACCEPT THESE - TERMS OF USE, THE USER SHALL NOT USE ANY OF UTORG.PRO’S SERVICES, PRODUCTS AND CONTENT.

-


-
    -
  1. Terms and - Definitions

    -


    -

    Account” - shall mean a record created and maintained by the Software that accumulates and stores all information about - the Cardholder, including without limitation Transactional history, Cryptocurrency balance, and the - Cardholder personal information.

    -

    SaltPay - Oly Card” shall mean a plastic card linked to the Account. “Cardholder” shall mean an individual - or entity holding a SaltPay Oly Card.

    -

    "Confidential - Information" shall mean any data or information, oral or written, treated as confidential that - relates to either party’s (or, if either party is bound to protect the confidentiality of any third party’s - information, such third party’s) past, present, or future research, development or business activities, - including any unannounced products and services, any information relating to Services, developments, - relevant documentation (in whatever form or media provided), inventions, processes, plans, financial - information, end-user data, revenue, transaction volume, forecasts, projections, and the financial terms of - this Agreement. Notwithstanding the foregoing, Confidential Information shall not be deemed to include - information if: (i) it was already known to the receiving party prior to the Effective Date of this - Agreement, as established by documentary evidence; (ii) it is in or has entered the public domain through no - breach of this Agreement or other wrongful act of the receiving party; (iii) it has been rightfully received - by the receiving party from a third party and without breach of any obligation of confidentiality of such - third party to the owner of the Confidential Information; (iv) it has been approved for release by written - authorization of the owner of the Confidential Information; or, (v) it has been independently developed by a - party without access to or use of the Confidential Information of the other party.”

    -

    Cryptocurrency” - shall mean any medium of exchange using cryptography to secure transactions and to control the creation of - new units, as bitcoins (http://bitcoin.org), or any other that meet those requirements.

    -

    Force-Majeure - Event” shall mean any act or event beyond a Party’s reasonable control, including without limitation - non-delivery or defective delivery of third party services necessary to provide the Services (including but - not limited to those of our partners, vendors and suppliers), strikes, lock-outs or other industrial action - by third parties, civil commotion, riot, invasion, terrorist attack or threat of terrorist attack, war - (whether declared or not) or threat or

    -

    - preparation for war, fire, explosion, storm, flood, earthquake, subsidence, epidemic or other natural - disaster, failure of public or private telecommunications networks, worldwide Web unavailability or - malfunction, governmental prohibition or other limitation of Cryptocurrencies, or seizing of infrastructure - and operations of Cryptocurrencies.

    -

    Over-the-counter - (OTC) deal” shall mean a direct, off-exchange, Transaction between Utorg and Cardholder. “Price” - shall mean a "price per coin" for which users are willing to purchase or sell Cryptocurrency using - the Services. “Relevant Exchange Rate” shall mean the weighted spot price average at the time of the - transaction.

    -

    Services” shall mean - providing services of exchanging a virtual currency against a fiat currency. “Site” means Utorg.pro’s website at https://Utorg.pro.

    -

    Specified Bank Account” - means the bank account belonging to Salt Pay IIB hf., the details of which shall be provided to Utorg.pro. -

    -

    Software” shall mean a - software owned by Utorg and enabling Transactions’ processing.

    -

    Transaction” - shall mean any of the following: (i) a purchase of the Cryptocurrency by the Cardholder; (ii) a sale of the - Cryptocurrency by the Cardholder; (iii) a transfer of Cryptocurrency to a Cardholder.

    -


  2. -
  3. Scope of the - Services

    -


    -
      -
    1. Utorg undertakes to - provide the Cardholder with Services, including but not limited to OTC deal. For the avoidance of doubt, - Utorg shall use the Relevant Exchange Rate when executing the OTC deal.

      -


    2. -
    3. The Services will - be provided in such a manner that Utorg will be acting on its own behalf strictly under the Cardholder’s - instructions regarding any Transaction as well as other matters which the Cardholder consider to be - necessary hereunder and are accepted by Utorg.

      -


    4. -
    5. Utorg will treat - the each individual Cardholder as its sole client to which it owes obligations and will bear no - responsibility to third parties for whom the Cardholder may be acting as an agent, intermediary or - fiduciary, whether or not the existence or identity of such person(s) has been disclosed to, or is known - by Utorg.

      -


    6. -
    7. Utorg does not - provide banking, or payment Services and does not hold or store the Cardholder’s fiat or crypto - currencies.

      -


    8. -
    -
  4. -
  5. Warranties

    -


    -

    The Cardholder hereby warrants - and represents to Utorg as follows:

    -
      -
    1. It - represents and warrants that all than all outgoing Transactions, cryptocurrency and/or fiat currency, - are made only to the Specified Bank Account, and Utorg shall not be liable for the consequences of any - other incoming Transactions.

      -


    2. -
    3. It also represents - and warrants than all incoming Transactions, cryptocurrency and/or fiat currency, are made only from its - own wallet and/or bank account, and Utorg shall not be liable for the consequences of any other incoming - Transactions.

      -


    4. -
    5. It shall use the - Services in strict conformity with the Terms of Use and for no other purpose and in no other manner. All - other warranties and representations regarding the use of the Services set forth below shall be in - addition to and not in limitation of this general overriding warranty and representation.

      -


    6. -
    7. The Cardholder represents and warrants to accept - and comply with all policies posted on Utorg.pro website: https://utorg.pro, Terms of Use (https://utorg.pro/terms/), Privacy Policy (https://utorg.pro/privacy-policy), KYC/AML Policy (https://utorg.pro/kyc-aml-policy). -

    8. -
    -
  6. -
  7. Rights - and Obligations

    -


    -
      -
    1. The Cardholder - has right to:

      -


      -
        -
      1. ask - Utorg for any type of assistance and support;

        -


      2. -
      3. - request a confirmation letter stating all information on Cardholder’s Transactions; and

        -


      4. -
      5. - be promptly notified of any relevant changes, including but not limited to Commissions, bank details, cryptocurrency wallets, etc. -

        -


      6. -
      -
    2. -
    3. Utorg has the right - to stop providing the Services and terminate the Agreement immediately in case of any breach of the - Agreement or/and the Terms of Use;

      -


    4. -
    5. Utorg - undertakes to provide the Cardholder with full assistant and support regarding the Service - provision.

      -


    6. -
    -
  8. -
  9. KYC Verification

    -


    -
      -
    1. Identification and - verification procedures (also known as “Know Your Customer” or “KYC”) are required for all - Transactions, which shall be undertaken by Utorg. If the User refuses to provide required documents and - information under KYC, including additional information that can be requested after the business - relationships were established, Utorg.pro reserves the right to immediately terminate the Services - provision to the User.

      -


    2. -
    3. The User hereby - authorises Utorg.pro to, directly or indirectly (through third parties), make any inquiries as - considered necessary to check the relevance and accuracy of the information provided for verification - purposes. Personal Data transferred will be limited to strictly the necessary and with security measures - in use to protect the data.

      -


    4. -
    -
  10. -
  11. Regulatory - Compliance

    -


    -
      -
    1. For the purposes of - anti-money laundering prevention and combatting terrorist activities, Utorg performs a thorough due - diligence on each of its customers. Cardholder will have to provide all documents and information - requested by Utorg for KYC purposesin order to be able to use the Services. Utorg will keep records of - the Cardholder’s information and documents safe and confidential in accordance with its Privacy Policy - and the applicable legal and regulatory requirements.

      -


    2. -
    3. Utorg reserves the - right to request any additional information about the Cardholder and/or the Cardholder’s Transaction, - and the Cardholder agrees to provide such documents and information in due time. Utorg has a right to - decline to deliver the Services anytime in case the documents or information provided by the Cardholder - are unsatisfactory or insufficient, or in case such actions are necessary to comply with the applicable - laws and regulations, as Utorg may decide in its sole discretion.

      -


    4. -
    5. The Cardholder will - be able to perform Transactions within certain amounts according to the limits established by Utorg and - the applicable law. Cardholder will be able to provide some additional information as instructed by - Utorg to increase or eliminate these limits.

      -


    6. -
    -
  12. -
  13. Intellectual - Property

    -


    -
      -
    1. All content at - Utorg's website is the property of Utorg and is protected by copyright, patent, trademark and any - other applicable laws, unless otherwise specified hereby.

      -


    2. -
    3. The trademarks, - brands, trade names, service marks and logos of Utorg and others used on the Site (hereinafter, the - "Trademarks") are the property of Utorg and its respective owners. The software, - applications, text, images, graphics, data, prices, trades, charts, graphs, video and audio materials - used on this Site belong to Utorg. The Trademarks and other content on the Platform and at Utorg's - website should not be copied, reproduced, modified, republished, uploaded, posted, transmitted, scraped, - collected or distributed in any form or by any means, whether manual or automated. The use of any - content from the Platform and at Utorg's website on any other site or a networked computer - environment for any other purpose

      -

      is - strictly prohibited; any such unauthorized use may violate copyright, patent, trademark and any - other applicable laws and could result in criminal or civil penalties/liabilities.

      -


    4. -
    -
  14. -
  15. Confidentiality

    -


    -
      -
    1. Any information of - a Party (hereinafter, the “Disclosing Party”) which is not public and which was obtained by the - other Party (hereinafter, the “Receiving Party”) in the course of performance of this Agreement, - whether proprietary to such other Party or any third party, whether expressly marked as confidential or - not and regardless of the form and manner, in which it was obtained, shall be deemed “Confidential - Information”.

      -


    2. -
    3. With respect to - any and all Confidential Information of the Disclosing Party the Receiving Party agrees to:

      -


      -
        -
      1. hold it in - confidence and treat it with the same degree of care and diligence, as it employs with respect - to its own confidential information, but in no case less than a reasonable degree of care and - diligence;

        -


      2. -
      3. use - it solely for the purpose of performance of this Agreement;

        -


      4. -
      5. communicate - it only to the employers and agents of the Receiving Party who have a need to know such - Confidential Information for the purpose of performance by the Receiving Party of this Agreement - and who are subject to confidentiality obligations with respect to such information; and

        -


      6. -
      7. upon - request of the Disclosing Party or following termination of this Agreement promptly return to - the Disclosing Party any and all material embodiments of its Confidential Information and - destroy those material embodiments of Confidential Information return of which is not - practicable and certify such destruction to the Disclosing Party in writing.

        -


      8. -
      -
    4. -
    5. The Parties hereby - agree and acknowledge that restrictions on use of Confidential Information set forth in Clauses 9.1 and - 9.2 above shall not apply to information, which (subject to the burden of proof being on the Receiving - Party):

      -


      -
        -
      1. was known - to the Receiving Party prior to it being obtained from the Disclosing Party in connection with - this Agreement;

        -


      2. -
      3. has been - developed or otherwise obtained by the Receiving Party without the use of the Confidential - Information;

        -


      4. -
      5. has - become public through no fault of the Receiving Party.

        -


      6. -
      -
    6. -
    7. The Parties hereby - agree that limitations on use and disclosure of Confidential Information as per the Agreement shall not - apply to any disclosure made in accordance with a valid law, court order, or other statutory act or - decision of a judicial or governmental authority that has relevant jurisdiction over the Receiving Party - (the ”Act”), provided that:

      -


      -
        -
      1. The - Receiving Party has provided the Disclosing Party with notice of the Act as soon as - practicable;

        -


      2. -
      3. the - Receiving Party has provided all reasonable assistance to the Disclosing Party (at the - Disclosing Party’s expense) in contesting the Act as the Disclosing Party had requested;

        -


      4. -
      5. the - Receiving Party has disclosed only the portion of Confidential Information which it was obliged - to disclose as per the Act (subject to any alterations due to the Disclosing Party contesting - the Act).

        -


      6. -
      -
    8. -
    -
  16. -
  17. Taxation

    -

    Utorg - makes no representations concerning the tax implications of the Transactions or the possession or use the - Cryptocurrency. The Cardholder bears the sole responsibility to determine if the Cryptocurrency or the - potential appreciation or depreciation in the value of the Cryptocurrency over time has tax implications for - the Cardholder in the

    -

    Cardholder's - home jurisdiction. By using the Services, and to the extent permitted by law, the Cardholder agrees not to - hold Utorg liable for any tax liability associated with or arising from the Cardholder's use of the - Services.

    -


  18. -
  19. - Governing Law

    -

    This - Agreement shall be governed by the laws of Republic of Estonia. All disputes and controversies arising out - of or in connection with this Agreement shall be submitted to the relevant Estonian Court in Tallinn, as the - Court of first instance.

    -


  20. -
  21. - Dispute Resolution

    -


    -
      -
    1. The Parties - undertake to make every possible effort to resolve disputes that may arise in the performance of this - Agreement by means of negotiations.

      -


    2. -
    3. Any dispute, - controversy or claim arising out of or relating to this contract, or the breach termination or - invalidity thereof, that cannot be solved in the process of negotiations shall be settled in the - appropriate court according to the Governing Law.

    4. -
    -
  22. -
-
-
-
-
-
-
- - diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt index 502437104c..010b27c362 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt @@ -78,8 +78,7 @@ class DetailsViewModel(private val store: Store) { store.dispatch(DetailsAction.CreateBackup) } SettingsElement.TermsOfService -> { - store.dispatch(DisclaimerAction.ShowAcceptedDisclaimer) - store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer)) + store.dispatch(DisclaimerAction.Show()) } SettingsElement.TermsOfUse -> { store.dispatch(DetailsAction.ShowDisclaimer) diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerAction.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerAction.kt index 0a444895d7..ca82ff1fee 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerAction.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerAction.kt @@ -1,8 +1,17 @@ package com.tangem.tap.features.disclaimer.redux +import com.tangem.common.extensions.VoidCallback import org.rekotlin.Action -sealed class DisclaimerAction: Action { - object AcceptDisclaimer : DisclaimerAction() - object ShowAcceptedDisclaimer : DisclaimerAction() +sealed class DisclaimerAction : Action { + + data class SetDisclaimerType( + val type: DisclaimerType, + ) : DisclaimerAction() + + data class Show(val onAcceptCallback: VoidCallback? = null) : DisclaimerAction() + data class AcceptDisclaimer(val type: DisclaimerType) : DisclaimerAction() + + internal data class UpdateState(val type: DisclaimerType, val accepted: Boolean) : DisclaimerAction() + internal data class SetOnAcceptCallback(val onAcceptCallback: VoidCallback? = null) : DisclaimerAction() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerMiddleware.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerMiddleware.kt index 12a1cfaccd..1b42d7b4fc 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerMiddleware.kt @@ -1,28 +1,65 @@ package com.tangem.tap.features.disclaimer.redux -import com.tangem.tap.common.post import com.tangem.tap.common.redux.AppState +import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.features.home.redux.HomeAction import com.tangem.tap.preferencesStorage import com.tangem.tap.store +import org.rekotlin.Action import org.rekotlin.Middleware class DisclaimerMiddleware { val disclaimerMiddleware: Middleware = { dispatch, state -> { next -> { action -> - when (action) { - is DisclaimerAction.AcceptDisclaimer -> { - preferencesStorage.saveDisclaimerAccepted() - store.dispatch(NavigationAction.PopBackTo()) - - //delayed sending used for better animation - post(550) { store.dispatch(HomeAction.ReadCard) } - } - } + state()?.let { handleDisclaimerMiddleware(action, it) } next(action) } } } -} \ No newline at end of file +} + +private fun handleDisclaimerMiddleware(action: Action, appState: AppState) { + val state = appState.disclaimerState + + when (action) { + is DisclaimerAction.SetDisclaimerType -> { + handleUpdateState = action.type.createUpdateState() + } + is DisclaimerAction.Show -> { + handleUpdateState = state.type.createUpdateState() + store.dispatch(DisclaimerAction.SetOnAcceptCallback(action.onAcceptCallback)) + store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer)) + } + is DisclaimerAction.AcceptDisclaimer -> { + action.type.accept() + handleUpdateState = action.type.createUpdateState() + store.dispatch(NavigationAction.PopBackTo()) + state.onAcceptCallback?.invoke() + store.dispatch(DisclaimerAction.SetOnAcceptCallback(null)) + } + } +} + +private fun DisclaimerType.accept() = when (this) { + DisclaimerType.SaltPay -> preferencesStorage.disclaimerPrefStorage.hasSaltPayTosAccepted = true + DisclaimerType.Tangem -> preferencesStorage.disclaimerPrefStorage.hasTangemTosAccepted = true +} + +fun DisclaimerType.isAccepted(): Boolean = when (this) { + DisclaimerType.SaltPay -> preferencesStorage.disclaimerPrefStorage.hasSaltPayTosAccepted + DisclaimerType.Tangem -> preferencesStorage.disclaimerPrefStorage.hasTangemTosAccepted +} + +private fun DisclaimerType.createUpdateState(): DisclaimerAction.UpdateState { + return DisclaimerAction.UpdateState(this, this.isAccepted()) +} + +private var handleUpdateState: DisclaimerAction.UpdateState = DisclaimerAction.UpdateState( + type = DisclaimerType.Tangem, + accepted = false, +) + set(value) { + field = value + store.dispatch(value) + } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerReducer.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerReducer.kt index 40408ea343..1b282877f2 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerReducer.kt @@ -13,9 +13,15 @@ private fun internalReduce(action: Action, state: AppState): DisclaimerState { if (action !is DisclaimerAction) return state.disclaimerState val disclaimerState = state.disclaimerState + return when (action) { - is DisclaimerAction.AcceptDisclaimer, DisclaimerAction.ShowAcceptedDisclaimer -> { - disclaimerState.copy(accepted = true) - } + is DisclaimerAction.UpdateState -> disclaimerState.copy( + type = action.type, + accepted = action.accepted, + ) + is DisclaimerAction.SetOnAcceptCallback -> disclaimerState.copy( + onAcceptCallback = action.onAcceptCallback, + ) + else -> disclaimerState } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerState.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerState.kt index b92252ae01..530a4afb78 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerState.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerState.kt @@ -1,7 +1,30 @@ package com.tangem.tap.features.disclaimer.redux +import android.net.Uri +import com.tangem.common.card.Card +import com.tangem.common.extensions.VoidCallback +import com.tangem.domain.common.ScanResponse +import com.tangem.domain.common.TapWorkarounds.isSaltPay import org.rekotlin.StateType data class DisclaimerState( - val accepted: Boolean = false + val accepted: Boolean = false, + val type: DisclaimerType = DisclaimerType.Tangem, + val onAcceptCallback: VoidCallback? = null, ) : StateType + +sealed class DisclaimerType( + val uri: Uri, +) { + object Tangem : DisclaimerType(Uri.parse("https://tangem.com/tangem_tos.html")) + object SaltPay : DisclaimerType(Uri.parse("https://tangem.com/soltpay_tos.html")) + + companion object { + fun get(scanResponse: ScanResponse): DisclaimerType = get(scanResponse.card) + + fun get(card: Card): DisclaimerType = when { + card.isSaltPay -> SaltPay + else -> Tangem + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt index abbd7bf8de..b13e5b588b 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt @@ -55,25 +55,25 @@ class DisclaimerFragment : Fragment(R.layout.fragment_disclaimer), binding.toolbar.setNavigationOnClickListener { store.dispatch(NavigationAction.PopBackTo()) } - initAndLoadTOS() - setOnClickListeners() + initViews() } - private fun initAndLoadTOS() { + private fun initViews() { binding.webView.configureSettings() - binding.webView.loadUrl("file:///android_asset/tos.html") } - private fun setOnClickListeners() { - binding.btnAccept.setOnClickListener { store.dispatch(DisclaimerAction.AcceptDisclaimer) } - } - - override fun newState(state: DisclaimerState) { + override fun newState(state: DisclaimerState) = with(binding) { if (activity == null || view == null) return if (state.accepted) { - binding.halfTransparentOverlay.hide() - binding.btnAccept.hide() + halfTransparentOverlay.hide() + btnAccept.hide() } + + btnAccept.setOnClickListener { + store.dispatch(DisclaimerAction.AcceptDisclaimer(state.type)) + } + + webView.loadUrl(state.type.uri.toString()) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt index c22bd6c043..66016aef08 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt @@ -13,6 +13,5 @@ sealed class HomeAction : Action { data class ShouldScanCardOnResume(val shouldScanCard: Boolean) : HomeAction() object Init : HomeAction() - data class SetTermsOfUseState(val isDisclaimerAccepted: Boolean) : HomeAction() data class ChangeScanCardButtonState(val state: IndeterminateProgressButton) : HomeAction() } 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 60eaff1abf..ad67172568 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 @@ -20,6 +20,9 @@ import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.FragmentShareTransition import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.features.disclaimer.redux.DisclaimerAction +import com.tangem.tap.features.disclaimer.redux.DisclaimerType +import com.tangem.tap.features.disclaimer.redux.isAccepted import com.tangem.tap.features.home.BELARUS_COUNTRY_CODE import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE import com.tangem.tap.features.home.redux.HomeMiddleware.Companion.BUY_WALLET_URL @@ -63,7 +66,6 @@ private fun handleHomeAction(appState: () -> AppState?, action: Action, dispatch is HomeAction.Init -> { store.dispatch(GlobalAction.RestoreAppCurrency) store.dispatch(GlobalAction.ExchangeManager.Init) - store.dispatch(HomeAction.SetTermsOfUseState(preferencesStorage.wasDisclaimerAccepted())) store.dispatch(GlobalAction.FetchUserCountry) } is HomeAction.ShouldScanCardOnResume -> { @@ -73,17 +75,16 @@ private fun handleHomeAction(appState: () -> AppState?, action: Action, dispatch } } is HomeAction.ReadCard -> { - if (!preferencesStorage.wasDisclaimerAccepted()) { - store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer)) - } else { - changeButtonState(ButtonState.PROGRESS) - val scanCardAction = GlobalAction.ScanCard( - onSuccess = ::onScanSuccess, - onFailure = ::onScanFailure, - ) - store.dispatch(HomeAction.ScanInProgress(true)) - postUiDelayBg(300) { store.dispatch(scanCardAction) } - } + changeButtonState(ButtonState.PROGRESS) + val scanCardAction = GlobalAction.ScanCard( + onSuccess = { + store.dispatch(HomeAction.ScanInProgress(false)) + checkForDisclaimer(it, ::onScanSuccess) + }, + onFailure = ::onScanFailure, + ) + store.dispatch(HomeAction.ScanInProgress(true)) + postUiDelayBg(300) { store.dispatch(scanCardAction) } } is HomeAction.GoToShop -> { when (action.userCountryCode) { @@ -98,12 +99,28 @@ private fun handleHomeAction(appState: () -> AppState?, action: Action, dispatch } } +private fun checkForDisclaimer(scanResponse: ScanResponse, onDisclaimerAccepted: (ScanResponse) -> Unit) { + val disclaimerType = DisclaimerType.get(scanResponse) + store.dispatch(DisclaimerAction.SetDisclaimerType(disclaimerType)) + + if (disclaimerType.isAccepted()) { + onDisclaimerAccepted((scanResponse)) + } else { + changeButtonState(ButtonState.ENABLED) + store.dispatch( + DisclaimerAction.Show { + changeButtonState(ButtonState.PROGRESS) + onDisclaimerAccepted(scanResponse) + }, + ) + } +} + private fun onScanSuccess(scanResponse: ScanResponse) { val globalState = store.state.globalState val tapWalletManager = globalState.tapWalletManager tapWalletManager.updateConfigManager(scanResponse) - store.dispatch(HomeAction.ScanInProgress(false)) store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse)) if (scanResponse.isSaltPay()) { diff --git a/app/src/main/java/com/tangem/tap/persistence/DisclaimerPrefStorage.kt b/app/src/main/java/com/tangem/tap/persistence/DisclaimerPrefStorage.kt new file mode 100644 index 0000000000..7c016eaa5d --- /dev/null +++ b/app/src/main/java/com/tangem/tap/persistence/DisclaimerPrefStorage.kt @@ -0,0 +1,25 @@ +package com.tangem.tap.persistence + +import android.content.SharedPreferences +import androidx.core.content.edit + +/** +[REDACTED_AUTHOR] + */ +class DisclaimerPrefStorage( + private val preferences: SharedPreferences, +) { + + var hasTangemTosAccepted: Boolean + get() = preferences.getBoolean(KEY_TANGEM_TOS, false) + set(value) = preferences.edit { putBoolean(KEY_TANGEM_TOS, value) } + + var hasSaltPayTosAccepted: Boolean + get() = preferences.getBoolean(KEY_SALT_PAY_TOS, false) + set(value) = preferences.edit { putBoolean(KEY_SALT_PAY_TOS, value) } + + companion object { + private const val KEY_TANGEM_TOS = "tangem_tos_accepted" + private const val KEY_SALT_PAY_TOS = "saltPay_tos_accepted" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt index d348cacfb7..1fad689559 100644 --- a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt @@ -7,15 +7,16 @@ import androidx.core.content.edit import com.tangem.common.json.MoshiJsonConverter import java.util.* - class PreferencesStorage(applicationContext: Application) { - private val preferences: SharedPreferences = applicationContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE) + private val preferences: SharedPreferences = + applicationContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE) val appRatingLaunchObserver: AppRatingLaunchObserver val usedCardsPrefStorage: UsedCardsPrefStorage val fiatCurrenciesPrefStorage: FiatCurrenciesPrefStorage val saltPayActivationStorage: SaltPayActivationStorage + val disclaimerPrefStorage: DisclaimerPrefStorage init { incrementLaunchCounter() @@ -25,6 +26,7 @@ class PreferencesStorage(applicationContext: Application) { fiatCurrenciesPrefStorage = FiatCurrenciesPrefStorage(preferences, MoshiJsonConverter.INSTANCE) fiatCurrenciesPrefStorage.migrate() saltPayActivationStorage = SaltPayActivationPrefStorage(applicationContext, MoshiJsonConverter.INSTANCE) + disclaimerPrefStorage = DisclaimerPrefStorage(preferences) } var chatFirstLaunchTime: Long? @@ -38,14 +40,6 @@ class PreferencesStorage(applicationContext: Application) { return usedCardsPrefStorage.wasScanned(cardId) } - fun saveDisclaimerAccepted() { - preferences.edit { putBoolean(DISCLAIMER_ACCEPTED_KEY, true) } - } - - fun wasDisclaimerAccepted(): Boolean { - return preferences.getBoolean(DISCLAIMER_ACCEPTED_KEY, false) - } - fun saveTwinsOnboardingShown() { preferences.edit { putBoolean(TWINS_ONBOARDING_SHOWN_KEY, true) } } @@ -61,12 +55,10 @@ class PreferencesStorage(applicationContext: Application) { companion object { private const val PREFERENCES_NAME = "tapPrefs" - private const val DISCLAIMER_ACCEPTED_KEY = "disclaimerAccepted" private const val TWINS_ONBOARDING_SHOWN_KEY = "twinsOnboardingShown" private const val APP_LAUNCH_COUNT_KEY = "launchCount" private const val CHAT_FIRST_LAUNCH_KEY = "chatFirstLaunchKey" } - } class AppRatingLaunchObserver( diff --git a/app/src/main/res/layout/fragment_disclaimer.xml b/app/src/main/res/layout/fragment_disclaimer.xml index 556960be96..604410e507 100644 --- a/app/src/main/res/layout/fragment_disclaimer.xml +++ b/app/src/main/res/layout/fragment_disclaimer.xml @@ -22,6 +22,7 @@ android:layout_height="?attr/actionBarSize" app:navigationIcon="@drawable/ic_baseline_arrow_back_24" app:title="@string/disclaimer_title" /> + - + app:layout_constraintTop_toTopOf="parent"> + + + +