Updated on 2026-08-14

This commit is contained in:
Tangem 2025-10-29 13:06:43 +03:00
parent d5cd5f1951
commit 2ee3bce46a
11 changed files with 22 additions and 357 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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<CreateWalletResponse> {
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<CreateWalletResponse> {
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<ScanResponse> {
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(),
)
}
}

View file

@ -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<PropertyValuesHolder> {
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,
)
}
}
}

View file

@ -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