From 2ee3bce46a8b093e19ebaaaae9763e59e7d9498d Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 29 Oct 2025 13:06:43 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../domain/tasks/product/ScanProductTask.kt | 2 +- .../domain/twins/CreateFirstTwinWalletTask.kt | 2 +- .../twins/CreateSecondTwinWalletTask.kt | 2 +- .../tap/domain/twins/TwinCardsManager.kt | 78 -------- .../tap/domain/twins/TwinCardsWidget.kt | 174 ------------------ .../tangem/tap/domain/twins/WrongTwinCard.kt | 2 +- .../domain/card/common}/TwinsHelperTest.kt | 9 +- .../com/tangem/domain/common/TwinsHelper.kt | 90 --------- .../wallets/usecase/GetCardImageUseCase.kt | 4 +- .../v2/twin/impl/model/OnboardingTwinModel.kt | 14 +- tangem-android-tools | 2 +- 11 files changed, 22 insertions(+), 357 deletions(-) delete mode 100644 app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt delete mode 100644 app/src/main/java/com/tangem/tap/domain/twins/TwinCardsWidget.kt rename {domain/card/src/test/java/com/tangem/domain/card => data/account/src/test/java/com/tangem/domain/card/common}/TwinsHelperTest.kt (86%) delete mode 100644 domain/legacy/src/main/java/com/tangem/domain/common/TwinsHelper.kt 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 45653eeeea..93a1c1f2be 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 @@ -19,7 +19,7 @@ import com.tangem.domain.card.common.TapWorkarounds.isNotSupportedInThatRelease import com.tangem.domain.card.common.TapWorkarounds.isStart2Coin import com.tangem.domain.card.common.TapWorkarounds.isTangemTwins import com.tangem.domain.card.common.TapWorkarounds.isVisa -import com.tangem.domain.common.TwinsHelper +import com.tangem.domain.card.common.TwinsHelper import com.tangem.domain.wallets.derivations.derivationStyleProvider import com.tangem.domain.card.common.visa.VisaUtilities import com.tangem.domain.card.configs.CardConfig diff --git a/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt index b7b896682c..659921880b 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt @@ -5,7 +5,7 @@ import com.tangem.common.card.EllipticCurve import com.tangem.common.core.CardSession import com.tangem.common.core.CardSessionRunnable import com.tangem.common.core.CompletionCallback -import com.tangem.domain.common.TwinsHelper +import com.tangem.domain.card.common.TwinsHelper import com.tangem.operations.attestation.AttestationTask import com.tangem.operations.wallet.CreateWalletResponse import com.tangem.operations.wallet.CreateWalletTask diff --git a/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt index b466017130..000cee1a09 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt @@ -8,7 +8,7 @@ import com.tangem.common.core.CardSession import com.tangem.common.core.CardSessionRunnable import com.tangem.common.core.CompletionCallback import com.tangem.common.extensions.hexToBytes -import com.tangem.domain.common.TwinsHelper +import com.tangem.domain.card.common.TwinsHelper import com.tangem.operations.wallet.CreateWalletResponse import com.tangem.operations.wallet.CreateWalletTask import com.tangem.operations.wallet.PurgeWalletCommand diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt deleted file mode 100644 index b503705f7e..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt +++ /dev/null @@ -1,78 +0,0 @@ -package com.tangem.tap.domain.twins - -import com.tangem.Message -import com.tangem.common.CompletionResult -import com.tangem.common.KeyPair -import com.tangem.common.extensions.hexToBytes -import com.tangem.common.extensions.toHexString -import com.tangem.domain.models.scan.CardDTO -import com.tangem.domain.models.scan.ScanResponse -import com.tangem.operations.wallet.CreateWalletResponse -import com.tangem.tap.common.extensions.inject -import com.tangem.tap.proxy.redux.DaggerGraphState -import com.tangem.tap.store -import com.tangem.tap.tangemSdkManager - -class TwinCardsManager(card: CardDTO) { - - private val issuersConfigStorage by lazy(mode = LazyThreadSafetyMode.NONE) { - store.inject(DaggerGraphState::issuersConfigStorage) - } - - private val firstCardId: String = card.cardId - private val publicKey: String = card.issuer.publicKey.toHexString() - - private var currentCardPublicKey: String? = null - private var secondCardPublicKey: String? = null - - suspend fun createFirstWallet(message: Message): CompletionResult { - val response = tangemSdkManager.createFirstTwinWallet(cardId = firstCardId, initialMessage = message) - - if (response is CompletionResult.Success) { - currentCardPublicKey = response.data.wallet.publicKey.toHexString() - } - - return response - } - - suspend fun createSecondWallet( - initialMessage: Message, - preparingMessage: Message, - creatingWalletMessage: Message, - ): CompletionResult { - val response = tangemSdkManager.createSecondTwinWallet( - firstPublicKey = requireNotNull(currentCardPublicKey), - firstCardId = firstCardId, - issuerKeys = getIssuerKeys(), - preparingMessage = preparingMessage, - creatingWalletMessage = creatingWalletMessage, - initialMessage = initialMessage, - ) - - if (response is CompletionResult.Success) { - secondCardPublicKey = response.data.wallet.publicKey.toHexString() - } - - return response - } - - suspend fun complete(message: Message): CompletionResult { - val response = tangemSdkManager.finalizeTwin( - secondCardPublicKey = requireNotNull(secondCardPublicKey).hexToBytes(), - issuerKeyPair = getIssuerKeys(), - cardId = firstCardId, - initialMessage = message, - ) - - return response - } - - private suspend fun getIssuerKeys(): KeyPair { - val issuer = issuersConfigStorage.getConfig().first { it.publicKey == publicKey } - - return KeyPair( - publicKey = issuer.publicKey.hexToBytes(), - privateKey = issuer.privateKey.hexToBytes(), - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsWidget.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsWidget.kt deleted file mode 100644 index ca1f95a650..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsWidget.kt +++ /dev/null @@ -1,174 +0,0 @@ -package com.tangem.tap.domain.twins - -import android.animation.AnimatorSet -import android.animation.ObjectAnimator -import android.animation.PropertyValuesHolder -import android.view.View -import androidx.core.animation.doOnEnd -import com.tangem.common.extensions.VoidCallback -import com.tangem.domain.common.TwinCardNumber -import com.tangem.sdk.ui.widget.leapfrogWidget.LeapView -import com.tangem.sdk.ui.widget.leapfrogWidget.LeapViewState -import com.tangem.sdk.ui.widget.leapfrogWidget.LeapfrogWidget - -/** -[REDACTED_AUTHOR] - */ -class TwinsCardWidget( - val leapfrogWidget: LeapfrogWidget, - private val deviceScaleFactor: Float = 1f, - val getTopOfAnchorViewForActivateState: () -> Float, -) { - - var state: TwinCardsWidgetState? = null - private set - - private val animationDuration = 300L - - init { - if (leapfrogWidget.getViewsCount() != 2) throw UnsupportedOperationException() - } - - fun toWelcome(animate: Boolean = true, onEnd: VoidCallback = {}) { - if (state == TwinCardsWidgetState.Welcome) return - - state = TwinCardsWidgetState.Welcome - val animator = createAnimator(animate) - animator.playTogether( - createAnimator(TwinCardNumber.First, createWelcomeProperties(TwinCardNumber.First)), - createAnimator(TwinCardNumber.Second, createWelcomeProperties(TwinCardNumber.Second)), - ) - animator.doOnEnd { onEnd() } - leapfrogWidget.fold(animate) { animator.start() } - } - - fun toLeapfrog(animate: Boolean = true, onEnd: VoidCallback = {}) { - if (state == TwinCardsWidgetState.Leapfrog) return - - state = TwinCardsWidgetState.Leapfrog - val animator = createAnimator(animate) - animator.playTogether( - createAnimator(TwinCardNumber.First, createLeapfrogProperties(TwinCardNumber.First)), - createAnimator(TwinCardNumber.Second, createLeapfrogProperties(TwinCardNumber.Second)), - ) - animator.doOnEnd { - leapfrogWidget.initViews() - leapfrogWidget.unfold(animate, onEnd) - } - animator.start() - } - - fun toActivate(animate: Boolean = true, onEnd: VoidCallback = {}) { - if (state == TwinCardsWidgetState.Activate) return - - state = TwinCardsWidgetState.Activate - val animator = createAnimator(animate) - animator.playTogether( - createAnimator(TwinCardNumber.First, createActivateProperties(TwinCardNumber.First)), - createAnimator(TwinCardNumber.Second, createActivateProperties(TwinCardNumber.Second)), - ) - animator.doOnEnd { onEnd() } - animator.start() - } - - private fun createAnimator(animate: Boolean): AnimatorSet { - return AnimatorSet().apply { - duration = if (animate) animationDuration else 0 - } - } - - private fun createAnimator(cardNumber: TwinCardNumber, properties: TwinsCardProperties): ObjectAnimator { - val view = getLeapViewByCardNumber(cardNumber).view - val animator = ObjectAnimator.ofPropertyValuesHolder(view, *properties.createValuesHolders().toTypedArray()) - view.elevation = properties.elevation - return animator - } - - @Suppress("MagicNumber") - private fun createWelcomeProperties(cardNumber: TwinCardNumber): TwinsCardProperties { - return when (cardNumber) { - TwinCardNumber.First -> { - TwinsCardProperties( - xTranslation = -120f * deviceScaleFactor, - yTranslation = -220f * deviceScaleFactor, - rotation = -3f, - elevation = 1f, - scale = deviceScaleFactor, - ) - } - TwinCardNumber.Second -> { - TwinsCardProperties( - xTranslation = 170f * deviceScaleFactor, - yTranslation = 170f * deviceScaleFactor, - rotation = -3f, - elevation = 0f, - scale = deviceScaleFactor, - ) - } - } - } - - private fun createLeapfrogProperties(cardNumber: TwinCardNumber): TwinsCardProperties { - val twinProperties = TwinsCardProperties.from(getLeapViewByCardNumber(cardNumber).state) - return twinProperties.copy( - xTranslation = 0f, - yTranslation = 0f, - rotation = 0f, - scale = 1f, - ) - } - - @Suppress("MagicNumber") - private fun createActivateProperties(cardNumber: TwinCardNumber): TwinsCardProperties { - val topOfAnchorView = getTopOfAnchorViewForActivateState() - val twinProperties = TwinsCardProperties.from(getLeapViewByCardNumber(cardNumber).state) - return twinProperties.copy( - xTranslation = twinProperties.xTranslation, - yTranslation = twinProperties.yTranslation - topOfAnchorView, - rotation = 0f, - scale = (twinProperties.scale - 0.5f) * deviceScaleFactor, - ) - } - - private fun getLeapViewByCardNumber(cardNumber: TwinCardNumber): LeapView { - return when (cardNumber) { - TwinCardNumber.First -> leapfrogWidget.getViewByPosition(0) - TwinCardNumber.Second -> leapfrogWidget.getViewByPosition(1) - } - } -} - -enum class TwinCardsWidgetState { - Welcome, Leapfrog, Activate -} - -private data class TwinsCardProperties( - val xTranslation: Float = 0f, - val yTranslation: Float = 0f, - val rotation: Float = 0f, - val elevation: Float = 0f, - val scale: Float = 1.1f, -) { - - fun createValuesHolders(): List { - return listOf( - PropertyValuesHolder.ofFloat(View.TRANSLATION_X, xTranslation), - PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, yTranslation), - PropertyValuesHolder.ofFloat(View.ROTATION, rotation), - PropertyValuesHolder.ofFloat(View.SCALE_X, scale), - PropertyValuesHolder.ofFloat(View.SCALE_Y, scale), - ) - } - - companion object { - fun from(leapViewState: LeapViewState): TwinsCardProperties { - val leapViewProperties = leapViewState.properties - - return TwinsCardProperties( - yTranslation = leapViewProperties.yTranslation, - elevation = leapViewProperties.elevationEnd, - scale = leapViewProperties.scale, - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/twins/WrongTwinCard.kt b/app/src/main/java/com/tangem/tap/domain/twins/WrongTwinCard.kt index d05d28217e..6752cb5cca 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/WrongTwinCard.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/WrongTwinCard.kt @@ -1,7 +1,7 @@ package com.tangem.tap.domain.twins import com.tangem.common.core.TangemError -import com.tangem.domain.common.TwinCardNumber +import com.tangem.domain.card.common.TwinCardNumber import com.tangem.tap.tangemSdkManager import com.tangem.wallet.R diff --git a/domain/card/src/test/java/com/tangem/domain/card/TwinsHelperTest.kt b/data/account/src/test/java/com/tangem/domain/card/common/TwinsHelperTest.kt similarity index 86% rename from domain/card/src/test/java/com/tangem/domain/card/TwinsHelperTest.kt rename to data/account/src/test/java/com/tangem/domain/card/common/TwinsHelperTest.kt index 76e2291eac..a45be46828 100644 --- a/domain/card/src/test/java/com/tangem/domain/card/TwinsHelperTest.kt +++ b/data/account/src/test/java/com/tangem/domain/card/common/TwinsHelperTest.kt @@ -1,9 +1,8 @@ -package com.tangem.domain.card +package com.tangem.domain.card.common -import com.tangem.domain.common.TwinsHelper -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue +import org.junit.Assert import org.junit.Test +import org.junit.jupiter.api.Assertions.* class TwinsHelperTest { @@ -19,7 +18,7 @@ class TwinsHelperTest { @Test fun `twins compatibility pack 1 success`() { - assertTrue(TwinsHelper.isTwinsCompatible(pack1Twins[0], pack1Twins[1])) + Assert.assertTrue(TwinsHelper.isTwinsCompatible(pack1Twins[0], pack1Twins[1])) assertTrue(TwinsHelper.isTwinsCompatible(pack1Twins[1], pack1Twins[0])) } diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/TwinsHelper.kt b/domain/legacy/src/main/java/com/tangem/domain/common/TwinsHelper.kt deleted file mode 100644 index c30c02b77c..0000000000 --- a/domain/legacy/src/main/java/com/tangem/domain/common/TwinsHelper.kt +++ /dev/null @@ -1,90 +0,0 @@ -package com.tangem.domain.common - -import com.tangem.crypto.CryptoUtils -import com.tangem.domain.models.scan.CardDTO - -object TwinsHelper { - - /** - * Card compatibility - * cb61 <-> cb62 - * cb64 <-> cb65 - * - */ - private const val FIRST_CARD_FIRST_SERIES = "CB61" - private const val SECOND_CARD_FIRST_SERIES = "CB62" - private const val FIRST_CARD_SECOND_SERIES = "CB64" - private const val SECOND_CARD_SECOND_SERIES = "CB65" - - @Suppress("MagicNumber") - fun verifyTwinPublicKey(issuerData: ByteArray, cardWalletPublicKey: ByteArray?): Boolean { - if (issuerData.size < 65 || cardWalletPublicKey == null) return false - - val publicKey = issuerData.sliceArray(0 until 65) - val signedKey = issuerData.sliceArray(65 until issuerData.size) - return CryptoUtils.verify(cardWalletPublicKey, publicKey, signedKey) - } - - fun getTwinCardNumber(cardId: String): TwinCardNumber? { - val isFirstCard = cardId.startsWith(FIRST_CARD_FIRST_SERIES) || - cardId.startsWith(FIRST_CARD_SECOND_SERIES) - if (isFirstCard) return TwinCardNumber.First - - val isSecondCard = cardId.startsWith(SECOND_CARD_FIRST_SERIES) || - cardId.startsWith(SECOND_CARD_SECOND_SERIES) - if (isSecondCard) return TwinCardNumber.Second - return null - } - - @Suppress("MagicNumber") - fun getTwinCardIdForUser(cardId: String): String { - if (cardId.length < 16) return cardId - - val twinCardId = cardId.substring(11..14) - val twinCardNumber = getTwinCardNumber(cardId)?.number ?: 1 - return "$twinCardId #$twinCardNumber" - } - - /** - * Twins compatibility - * cb61 <-> cb62 - * cb64 <-> cb65 - */ - fun isTwinsCompatible(firstCardId: String, secondCardId: String): Boolean { - return secondCardId.startsWith(getCompatibleTwinCardId(firstCardId)) - } - - private fun getCompatibleTwinCardId(cardId: String): String { - return when { - cardId.startsWith(FIRST_CARD_FIRST_SERIES) -> SECOND_CARD_FIRST_SERIES - cardId.startsWith(FIRST_CARD_SECOND_SERIES) -> SECOND_CARD_SECOND_SERIES - cardId.startsWith(SECOND_CARD_FIRST_SERIES) -> FIRST_CARD_FIRST_SERIES - cardId.startsWith(SECOND_CARD_SECOND_SERIES) -> FIRST_CARD_SECOND_SERIES - else -> { - "unknown" - } - } - } -} - -enum class TwinCardNumber(val number: Int) { - First(1), Second(2); - - fun pairNumber(): TwinCardNumber = when (this) { - First -> Second - Second -> First - } -} - -@Deprecated("Use ScanResponse.isTangemTwin") -fun CardDTO.isTangemTwin(): Boolean { - return TwinsHelper.getTwinCardNumber(cardId) != null -} - -fun CardDTO.getTwinCardNumber(): TwinCardNumber? { - return TwinsHelper.getTwinCardNumber(this.cardId) -} - -fun CardDTO.getTwinCardIdForUser(): String { - return TwinsHelper.getTwinCardIdForUser(this.cardId) -} \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetCardImageUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetCardImageUseCase.kt index dbee9b5cfb..5e7d46dafe 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetCardImageUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetCardImageUseCase.kt @@ -2,8 +2,8 @@ package com.tangem.domain.wallets.usecase import com.tangem.common.card.FirmwareVersion import com.tangem.common.services.Result -import com.tangem.domain.common.TwinCardNumber -import com.tangem.domain.common.TwinsHelper +import com.tangem.domain.card.common.TwinCardNumber +import com.tangem.domain.card.common.TwinsHelper import com.tangem.domain.models.ArtworkModel import com.tangem.domain.wallets.models.Artwork import com.tangem.operations.attestation.ArtworkSize diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/twin/impl/model/OnboardingTwinModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/twin/impl/model/OnboardingTwinModel.kt index ab86ceed99..f1559c092d 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/twin/impl/model/OnboardingTwinModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/twin/impl/model/OnboardingTwinModel.kt @@ -16,9 +16,9 @@ import com.tangem.core.decompose.ui.UiMessageSender import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.toWrappedList import com.tangem.datasource.local.config.issuers.IssuersConfigStorage +import com.tangem.domain.card.common.TwinCardNumber +import com.tangem.domain.card.common.getTwinCardNumber import com.tangem.domain.card.repository.CardRepository -import com.tangem.domain.common.TwinCardNumber -import com.tangem.domain.common.getTwinCardNumber import com.tangem.domain.feedback.SendFeedbackEmailUseCase import com.tangem.domain.feedback.models.FeedbackEmailType import com.tangem.domain.models.scan.ScanResponse @@ -68,6 +68,7 @@ internal class OnboardingTwinModel @Inject constructor( private val params = paramsContainer.require() private val firstCardTwinNumber = params.scanResponse.card.getTwinCardNumber() ?: error("Not twin") private val cryptoCurrencyStatusJobHolder = JobHolder() + private var twinCardsIds = Pair(null, null) private val _uiState = MutableStateFlow( when (params.mode) { @@ -159,6 +160,7 @@ internal class OnboardingTwinModel @Inject constructor( } is CompletionResult.Success -> { if (params.mode == Mode.CreateWallet) { + twinCardsIds = twinCardsIds.copy(first = result.data.cardId) cardRepository.startCardActivation(result.data.cardId) } @@ -216,6 +218,7 @@ internal class OnboardingTwinModel @Inject constructor( } is CompletionResult.Success -> { if (params.mode == Mode.CreateWallet) { + twinCardsIds = twinCardsIds.copy(second = result.data.cardId) cardRepository.startCardActivation(result.data.cardId) } @@ -299,7 +302,12 @@ internal class OnboardingTwinModel @Inject constructor( return@coroutineScope } - cardRepository.finishCardActivation(params.scanResponse.card.cardId) + twinCardsIds.first?.let { + cardRepository.finishCardActivation(it) + } + twinCardsIds.second?.let { + cardRepository.finishCardActivation(it) + } params.modelCallbacks.onDone() } diff --git a/tangem-android-tools b/tangem-android-tools index b64f8659fc..193db8a024 160000 --- a/tangem-android-tools +++ b/tangem-android-tools @@ -1 +1 @@ -Subproject commit b64f8659fce65e7749d50923b32bfad13e6b8cac +Subproject commit 193db8a0247a1f33574dcdd5f9ec4dab2c28ea73