Updated on 2026-08-14
This commit is contained in:
commit
037c6e24dc
37 changed files with 592 additions and 237 deletions
|
|
@ -77,9 +77,9 @@ dependencies {
|
|||
implementation 'com.google.android.play:core-ktx:1.8.1'
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||
|
||||
implementation 'com.tangem:blockchain:develop-51'
|
||||
implementation 'com.tangem.tangem-sdk-kotlin:core:develop-106'
|
||||
implementation 'com.tangem.tangem-sdk-kotlin:android:develop-106'
|
||||
implementation 'com.tangem:blockchain:develop-52'
|
||||
implementation 'com.tangem.tangem-sdk-kotlin:core:develop-115'
|
||||
implementation 'com.tangem.tangem-sdk-kotlin:android:develop-115'
|
||||
|
||||
// WebView
|
||||
implementation "androidx.browser:browser:1.3.0"
|
||||
|
|
|
|||
|
|
@ -778,5 +778,16 @@
|
|||
"curve" : "secp256k1",
|
||||
"testnet" : false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount" : 18,
|
||||
"symbol" : "VOYRME",
|
||||
"name" : "VOYR",
|
||||
"contractAddress" : "0x33a887Cf76383be39DC51786e8f641c9D6665D84",
|
||||
"blockchain" : {
|
||||
"key" : "bsc",
|
||||
"curve" : "secp256k1",
|
||||
"testnet" : false
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -32,6 +32,9 @@ fun Picasso.loadCurrenciesIcon(
|
|||
token?.symbol == QCX -> {
|
||||
this.load(R.drawable.ic_qcx)?.into(imageView)
|
||||
}
|
||||
token?.symbol == VOYR -> {
|
||||
this.load(R.drawable.ic_voyr)?.into(imageView)
|
||||
}
|
||||
url != null -> {
|
||||
if (token != null) {
|
||||
setTokenImage(imageView, textView, token)
|
||||
|
|
@ -60,6 +63,7 @@ fun Picasso.loadCurrenciesIcon(
|
|||
}
|
||||
|
||||
private const val QCX = "QCX"
|
||||
private const val VOYR = "VOYRME"
|
||||
|
||||
private fun setOfflineCurrencyImage(
|
||||
imageView: ImageView,
|
||||
|
|
|
|||
|
|
@ -94,8 +94,8 @@ private val globalMiddlewareHandler: Middleware<AppState> = { dispatch, appState
|
|||
action.shouldDeriveWC,
|
||||
action.messageResId
|
||||
)
|
||||
store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result))
|
||||
withMainContext {
|
||||
store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result))
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
tangemSdkManager.changeDisplayedCardIdNumbersCount(result.data)
|
||||
|
|
|
|||
|
|
@ -4,5 +4,6 @@ package com.tangem.tap.domain
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
enum class ProductType {
|
||||
Note, Wallet, Twins, Other
|
||||
Note, Twins, Wallet
|
||||
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.tap.domain
|
||||
|
||||
import CreateProductWalletTask
|
||||
import CreateProductWalletTaskResponse
|
||||
import android.content.Context
|
||||
import com.tangem.Message
|
||||
import com.tangem.TangemSdk
|
||||
|
|
@ -9,20 +10,22 @@ import com.tangem.common.CompletionResult
|
|||
import com.tangem.common.SuccessResponse
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.card.FirmwareVersion
|
||||
import com.tangem.common.core.CardIdDisplayFormat
|
||||
import com.tangem.common.core.CardSessionRunnable
|
||||
import com.tangem.common.core.Config
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.hdWallet.DerivationPath
|
||||
import com.tangem.operations.CommandResponse
|
||||
import com.tangem.operations.derivation.DeriveWalletPublicKeysTask
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeyList
|
||||
import com.tangem.operations.pins.CheckUserCodesCommand
|
||||
import com.tangem.operations.pins.CheckUserCodesResponse
|
||||
import com.tangem.operations.pins.SetUserCodeCommand
|
||||
import com.tangem.tap.common.analytics.AnalyticsEvent
|
||||
import com.tangem.tap.common.analytics.AnalyticsHandler
|
||||
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
|
||||
import com.tangem.tap.common.extensions.ByteArrayKey
|
||||
import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask
|
||||
import com.tangem.tap.domain.tasks.DerivationTask
|
||||
import com.tangem.tap.domain.tasks.DerivationTaskResponse
|
||||
import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask
|
||||
import com.tangem.tap.domain.tasks.product.ScanProductTask
|
||||
import com.tangem.tap.domain.tasks.product.ScanResponse
|
||||
|
|
@ -44,11 +47,13 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
|
|||
analyticsHandler.triggerEvent(AnalyticsEvent.READY_TO_SCAN, null)
|
||||
|
||||
val message = Message(context.getString(messageRes ?: R.string.initial_message_scan_header))
|
||||
return runTaskAsyncReturnOnMain(ScanProductTask(null, currenciesRepository, shouldDeriveWC), null, message)
|
||||
return runTaskAsyncReturnOnMain(ScanProductTask(null, currenciesRepository), null, message)
|
||||
.also { sendScanFailuresToAnalytics(analyticsHandler, it) }
|
||||
}
|
||||
|
||||
suspend fun createProductWallet(scanResponse: ScanResponse): CompletionResult<Card> {
|
||||
suspend fun createProductWallet(
|
||||
scanResponse: ScanResponse
|
||||
): CompletionResult<CreateProductWalletTaskResponse> {
|
||||
return runTaskAsync(
|
||||
CreateProductWalletTask(scanResponse.productType),
|
||||
scanResponse.card.cardId,
|
||||
|
|
@ -74,10 +79,9 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
|
|||
|
||||
suspend fun derivePublicKeys(
|
||||
cardId: String,
|
||||
walletPublicKey: ByteArray,
|
||||
derivationPaths: List<DerivationPath>
|
||||
): CompletionResult<ExtendedPublicKeyList> {
|
||||
return runTaskAsyncReturnOnMain(DeriveWalletPublicKeysTask(walletPublicKey, derivationPaths), cardId)
|
||||
derivations: Map<ByteArrayKey, List<DerivationPath>>
|
||||
): CompletionResult<DerivationTaskResponse> {
|
||||
return runTaskAsyncReturnOnMain(DerivationTask(derivations), cardId)
|
||||
}
|
||||
|
||||
suspend fun resetToFactorySettings(card: Card): CompletionResult<Card> {
|
||||
|
|
@ -134,7 +138,11 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
|
|||
}
|
||||
|
||||
fun changeDisplayedCardIdNumbersCount(scanResponse: ScanResponse) {
|
||||
tangemSdk.config.cardIdDisplayedNumbersCount = if (scanResponse.isTangemTwins()) 4 else null
|
||||
tangemSdk.config.cardIdDisplayFormat = if (scanResponse.isTangemTwins()) {
|
||||
CardIdDisplayFormat.LastLunh(4)
|
||||
} else {
|
||||
CardIdDisplayFormat.Full
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -35,8 +35,7 @@ object TapWorkarounds {
|
|||
@Deprecated("Use ScanResponse.isTangemNote")
|
||||
fun isTangemNote(card: Card): Boolean = tangemNoteBatches.contains(card.batchId)
|
||||
|
||||
@Deprecated("Use ScanResponse.isTangemWallet")
|
||||
fun isTangemWallet(card: Card): Boolean = tangemWalletBatches.contains(card.batchId)
|
||||
fun isTangemWalletBatch(card: Card): Boolean = tangemWalletBatches.contains(card.batchId)
|
||||
|
||||
fun getTangemNoteBlockchain(card: Card): Blockchain? = tangemNoteBatches[card.batchId]
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.tap.domain.extensions
|
|||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import java.math.BigDecimal
|
||||
|
||||
|
||||
|
|
@ -25,4 +26,18 @@ fun Blockchain.minimalAmount(): BigDecimal {
|
|||
return 1.toBigDecimal().movePointLeft(decimals())
|
||||
}
|
||||
|
||||
fun Blockchain.getPrimaryCurve(): EllipticCurve? {
|
||||
return when {
|
||||
getSupportedCurves().contains(EllipticCurve.Secp256k1) -> {
|
||||
EllipticCurve.Secp256k1
|
||||
}
|
||||
getSupportedCurves().contains(EllipticCurve.Ed25519) -> {
|
||||
EllipticCurve.Ed25519
|
||||
}
|
||||
else -> {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val NODL = "NODL"
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package com.tangem.tap.domain.tasks
|
||||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.CardSession
|
||||
import com.tangem.common.core.CardSessionRunnable
|
||||
import com.tangem.common.core.CompletionCallback
|
||||
import com.tangem.common.hdWallet.DerivationPath
|
||||
import com.tangem.operations.CommandResponse
|
||||
import com.tangem.operations.derivation.DeriveWalletPublicKeysTask
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeyList
|
||||
import com.tangem.tap.common.extensions.ByteArrayKey
|
||||
|
||||
class DerivationTaskResponse(
|
||||
val entries: Map<ByteArrayKey, ExtendedPublicKeyList>
|
||||
): CommandResponse
|
||||
|
||||
class DerivationTask(
|
||||
private val derivations: Map<ByteArrayKey, List<DerivationPath>>
|
||||
) : CardSessionRunnable<DerivationTaskResponse> {
|
||||
|
||||
val response: MutableMap<ByteArrayKey, ExtendedPublicKeyList> = mutableMapOf()
|
||||
|
||||
override fun run(session: CardSession, callback: CompletionCallback<DerivationTaskResponse>) {
|
||||
derive(keys = derivations.keys.toList(), index = 0, session = session, callback = callback)
|
||||
}
|
||||
|
||||
private fun derive(
|
||||
keys: List<ByteArrayKey>,
|
||||
index: Int,
|
||||
session: CardSession,
|
||||
callback: CompletionCallback<DerivationTaskResponse>
|
||||
) {
|
||||
if (index == keys.count()) {
|
||||
callback(CompletionResult.Success(DerivationTaskResponse(response.toMap())))
|
||||
return
|
||||
}
|
||||
|
||||
val key = keys[index]
|
||||
val paths = derivations[key]!!
|
||||
DeriveWalletPublicKeysTask(key.bytes, paths).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
response[key] = result.data
|
||||
derive(keys = keys, index = index + 1, session = session, callback = callback)
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,33 +1,41 @@
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.card.CardWallet
|
||||
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.TangemSdkError
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.operations.derivation.DeriveWalletPublicKeysTask
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeyList
|
||||
import com.tangem.common.hdWallet.ExtendedPublicKey
|
||||
import com.tangem.operations.CommandResponse
|
||||
import com.tangem.operations.backup.PrimaryCard
|
||||
import com.tangem.operations.backup.StartPrimaryCardLinkingTask
|
||||
import com.tangem.operations.wallet.CreateWalletResponse
|
||||
import com.tangem.operations.wallet.CreateWalletTask
|
||||
import com.tangem.tap.common.extensions.toMapKey
|
||||
import com.tangem.tap.domain.ProductType
|
||||
import com.tangem.tap.domain.TapWorkarounds.getTangemNoteBlockchain
|
||||
import com.tangem.tap.domain.tasks.DerivationTask
|
||||
import com.tangem.tap.domain.tasks.product.CreateWalletsTask
|
||||
import com.tangem.tap.domain.tasks.product.KeyWalletPublicKey
|
||||
import com.tangem.tap.domain.tasks.product.ProductCommandProcessor
|
||||
import com.tangem.tap.domain.tasks.product.getCurvesForNonCreatedWallets
|
||||
import com.tangem.tap.store
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
||||
data class CreateProductWalletTaskResponse(
|
||||
val card: Card,
|
||||
val derivedKeys: Map<KeyWalletPublicKey, List<ExtendedPublicKey>> = mapOf(),
|
||||
val primaryCard: PrimaryCard? = null
|
||||
) : CommandResponse
|
||||
|
||||
class CreateProductWalletTask(
|
||||
private val type: ProductType,
|
||||
) : CardSessionRunnable<Card> {
|
||||
) : CardSessionRunnable<CreateProductWalletTaskResponse> {
|
||||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<Card>) -> Unit) {
|
||||
override fun run(
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<CreateProductWalletTaskResponse>) -> Unit
|
||||
) {
|
||||
val card = session.environment.card.guard {
|
||||
callback(CompletionResult.Failure(TangemSdkError.CardError()))
|
||||
return
|
||||
|
|
@ -36,12 +44,19 @@ class CreateProductWalletTask(
|
|||
val commandProcessor = when (type) {
|
||||
ProductType.Note -> CreateWalletTangemNote()
|
||||
ProductType.Twins -> throw UnsupportedOperationException("Use the TwinCardsManager to create a wallet")
|
||||
ProductType.Wallet -> CreateWalletTangemWallet()
|
||||
else -> CreateWalletOtherCards()
|
||||
else -> CreateWalletTangemWallet()
|
||||
}
|
||||
commandProcessor.proceed(card, session) {
|
||||
when (it) {
|
||||
is CompletionResult.Success -> callback(CompletionResult.Success(session.environment.card!!))
|
||||
is CompletionResult.Success -> {
|
||||
val result = when (commandProcessor) {
|
||||
is CreateWalletTangemWallet -> {
|
||||
it.data as CreateProductWalletTaskResponse
|
||||
}
|
||||
else -> CreateProductWalletTaskResponse(card = session.environment.card!!)
|
||||
}
|
||||
callback(CompletionResult.Success(result))
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(it.error))
|
||||
}
|
||||
}
|
||||
|
|
@ -79,65 +94,98 @@ private class CreateWalletTangemNote : ProductCommandProcessor<CreateWalletRespo
|
|||
}
|
||||
}
|
||||
|
||||
private class CreateWalletTangemWallet : ProductCommandProcessor<CreateWalletResponse> {
|
||||
private class CreateWalletTangemWallet : ProductCommandProcessor<CreateProductWalletTaskResponse> {
|
||||
|
||||
private var primaryCard: PrimaryCard? = null
|
||||
private var createWalletResponse: CreateWalletResponse? = null
|
||||
|
||||
override fun proceed(
|
||||
card: Card,
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<CreateWalletResponse>) -> Unit,
|
||||
callback: (result: CompletionResult<CreateProductWalletTaskResponse>) -> Unit,
|
||||
) {
|
||||
val supportedCurves = setOf(EllipticCurve.Secp256k1, EllipticCurve.Ed25519)
|
||||
val curves = card.getCurvesForNonCreatedWallets().intersect(supportedCurves).toList()
|
||||
val curves = card.getCurvesForNonCreatedWallets()
|
||||
CreateWalletsTask(curves).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val response = result.data.createWalletResponses[0]
|
||||
val derivationPaths = listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
|
||||
.mapNotNull { it.derivationPath() }
|
||||
|
||||
DeriveWalletPublicKeysTask(response.wallet.publicKey, derivationPaths).run(session) {
|
||||
when (it) {
|
||||
is CompletionResult.Success -> {
|
||||
updateDerivedKeys(response.wallet, it.data)
|
||||
callback(CompletionResult.Success(response))
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(it.error))
|
||||
createWalletResponse = result.data.createWalletResponses[0]
|
||||
when {
|
||||
card.settings.isBackupAllowed -> {
|
||||
linkPrimaryCard(session, callback)
|
||||
}
|
||||
card.settings.isHDWalletAllowed -> {
|
||||
deriveKeys(session, callback)
|
||||
}
|
||||
else -> {
|
||||
callback(
|
||||
CompletionResult.Success(
|
||||
CreateProductWalletTaskResponse(card = session.environment.card!!)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: updating derived wallet public keys - make it better later
|
||||
private fun updateDerivedKeys(wallet: CardWallet, derivedKeys: ExtendedPublicKeyList) {
|
||||
val onboardingManager = store.state.globalState.onboardingState.onboardingManager ?: return
|
||||
onboardingManager.scanResponse = onboardingManager.scanResponse.copy(
|
||||
derivedKeys = mapOf(wallet.publicKey.toMapKey() to derivedKeys)
|
||||
)
|
||||
}
|
||||
|
||||
private class CreateWalletOtherCards : ProductCommandProcessor<CreateWalletResponse> {
|
||||
|
||||
override fun proceed(
|
||||
card: Card,
|
||||
private fun linkPrimaryCard(
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<CreateWalletResponse>) -> Unit,
|
||||
callback: (result: CompletionResult<CreateProductWalletTaskResponse>) -> Unit,
|
||||
) {
|
||||
val firmwareVersion = card.firmwareVersion
|
||||
val task = if (firmwareVersion < FirmwareVersion.MultiWalletAvailable) {
|
||||
CreateWalletsTask(listOf(card.supportedCurves.first()))
|
||||
} else {
|
||||
CreateWalletsTask(card.getCurvesForNonCreatedWallets())
|
||||
}
|
||||
|
||||
task.run(session) { result ->
|
||||
StartPrimaryCardLinkingTask().run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> callback(CompletionResult.Success(result.data.createWalletResponses[0]))
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
is CompletionResult.Success -> {
|
||||
primaryCard = result.data
|
||||
deriveKeys(session, callback)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun deriveKeys(
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<CreateProductWalletTaskResponse>) -> Unit,
|
||||
) {
|
||||
val derivationPaths = listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
|
||||
.mapNotNull { it.derivationPath() }
|
||||
val response = createWalletResponse.guard {
|
||||
callback(CompletionResult.Failure(TangemSdkError.UnknownError()))
|
||||
return
|
||||
}
|
||||
|
||||
if (derivationPaths.isNullOrEmpty()) {
|
||||
callback(
|
||||
CompletionResult.Success(
|
||||
CreateProductWalletTaskResponse(
|
||||
card = session.environment.card!!, primaryCard = primaryCard
|
||||
)
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
DerivationTask(mapOf(response.wallet.publicKey.toMapKey() to derivationPaths))
|
||||
.run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
callback(
|
||||
CompletionResult.Success(
|
||||
CreateProductWalletTaskResponse(
|
||||
card = session.environment.card!!,
|
||||
derivedKeys = result.data.entries,
|
||||
primaryCard = primaryCard
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,7 +20,8 @@ import com.tangem.operations.CommandResponse
|
|||
import com.tangem.operations.PreflightReadMode
|
||||
import com.tangem.operations.PreflightReadTask
|
||||
import com.tangem.operations.ScanTask
|
||||
import com.tangem.operations.derivation.DeriveWalletPublicKeysTask
|
||||
import com.tangem.operations.backup.PrimaryCard
|
||||
import com.tangem.operations.backup.StartPrimaryCardLinkingTask
|
||||
import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand
|
||||
import com.tangem.tap.common.extensions.ByteArrayKey
|
||||
import com.tangem.tap.common.extensions.toMapKey
|
||||
|
|
@ -30,20 +31,25 @@ import com.tangem.tap.domain.TapWorkarounds
|
|||
import com.tangem.tap.domain.TapWorkarounds.getTangemNoteBlockchain
|
||||
import com.tangem.tap.domain.TapWorkarounds.isExcluded
|
||||
import com.tangem.tap.domain.TapWorkarounds.isNotSupportedInThatRelease
|
||||
import com.tangem.tap.domain.extensions.getPrimaryCurve
|
||||
import com.tangem.tap.domain.extensions.getSingleWallet
|
||||
import com.tangem.tap.domain.tasks.DerivationTask
|
||||
import com.tangem.tap.domain.tokens.CurrenciesRepository
|
||||
import com.tangem.tap.domain.twins.TwinsHelper
|
||||
import com.tangem.tap.preferencesStorage
|
||||
|
||||
data class ScanResponse(
|
||||
val card: Card,
|
||||
val productType: ProductType,
|
||||
val walletData: WalletData?,
|
||||
val secondTwinPublicKey: String? = null,
|
||||
val derivedKeys: Map<KeyWalletPublicKey, List<ExtendedPublicKey>> = mapOf()
|
||||
val derivedKeys: Map<KeyWalletPublicKey, List<ExtendedPublicKey>> = mapOf(),
|
||||
val primaryCard: PrimaryCard? = null
|
||||
) : CommandResponse {
|
||||
|
||||
fun getBlockchain(): Blockchain {
|
||||
if (productType == ProductType.Note) return getTangemNoteBlockchain(card) ?: return Blockchain.Unknown
|
||||
if (productType == ProductType.Note) return getTangemNoteBlockchain(card)
|
||||
?: return Blockchain.Unknown
|
||||
val blockchainName: String = walletData?.blockchain ?: return Blockchain.Unknown
|
||||
return Blockchain.fromId(blockchainName)
|
||||
}
|
||||
|
|
@ -62,17 +68,19 @@ data class ScanResponse(
|
|||
fun isTangemNote(): Boolean = productType == ProductType.Note
|
||||
fun isTangemWallet(): Boolean = productType == ProductType.Wallet
|
||||
fun isTangemTwins(): Boolean = productType == ProductType.Twins
|
||||
fun isTangemOtherCards(): Boolean = productType == ProductType.Other
|
||||
|
||||
fun twinsIsTwinned(): Boolean = card.isTangemTwins() && walletData != null && secondTwinPublicKey != null
|
||||
fun supportsHdWallet(): Boolean = card.settings.isHDWalletAllowed
|
||||
fun supportsBackup(): Boolean = card.settings.isBackupAllowed
|
||||
|
||||
fun twinsIsTwinned(): Boolean =
|
||||
card.isTangemTwins() && walletData != null && secondTwinPublicKey != null
|
||||
}
|
||||
|
||||
typealias KeyWalletPublicKey = ByteArrayKey
|
||||
|
||||
class ScanProductTask(
|
||||
val card: Card? = null,
|
||||
private val currenciesRepository: CurrenciesRepository?,
|
||||
private val shouldDeriveWC: Boolean
|
||||
private val currenciesRepository: CurrenciesRepository?
|
||||
) : CardSessionRunnable<ScanResponse> {
|
||||
|
||||
override fun run(
|
||||
|
|
@ -93,15 +101,22 @@ class ScanProductTask(
|
|||
val commandProcessor = when {
|
||||
TapWorkarounds.isTangemNote(card) -> ScanNoteProcessor()
|
||||
card.isTangemTwins() -> ScanTwinProcessor()
|
||||
TapWorkarounds.isTangemWallet(card) -> ScanWalletProcessor(currenciesRepository, shouldDeriveWC)
|
||||
else -> ScanOtherCardsProcessor()
|
||||
else -> ScanWalletProcessor(currenciesRepository)
|
||||
}
|
||||
commandProcessor.proceed(card, session) { processorResult ->
|
||||
when (processorResult) {
|
||||
is CompletionResult.Success -> ScanTask().run(session) { scanTaskResult ->
|
||||
when (scanTaskResult) {
|
||||
is CompletionResult.Success -> callback(CompletionResult.Success(processorResult.data))
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(scanTaskResult.error))
|
||||
is CompletionResult.Success -> callback(
|
||||
CompletionResult.Success(
|
||||
processorResult.data
|
||||
)
|
||||
)
|
||||
is CompletionResult.Failure -> callback(
|
||||
CompletionResult.Failure(
|
||||
scanTaskResult.error
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(processorResult.error))
|
||||
|
|
@ -125,37 +140,128 @@ private class ScanNoteProcessor : ProductCommandProcessor<ScanResponse> {
|
|||
session: CardSession,
|
||||
callback: (result: CompletionResult<ScanResponse>) -> Unit
|
||||
) {
|
||||
callback(CompletionResult.Success(ScanResponse(card, ProductType.Note, session.environment.walletData)))
|
||||
callback(
|
||||
CompletionResult.Success(
|
||||
ScanResponse(
|
||||
card,
|
||||
ProductType.Note,
|
||||
session.environment.walletData
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class ScanWalletProcessor(
|
||||
private val currenciesRepository: CurrenciesRepository?,
|
||||
private val shouldDeriveWC: Boolean
|
||||
private val currenciesRepository: CurrenciesRepository?
|
||||
) : ProductCommandProcessor<ScanResponse> {
|
||||
|
||||
var primaryCard: PrimaryCard? = null
|
||||
|
||||
override fun proceed(
|
||||
card: Card,
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<ScanResponse>) -> Unit
|
||||
) {
|
||||
val derivationPaths = collectDerivationPaths(card)?.distinct()
|
||||
val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 }
|
||||
createMissingWalletsIfNeeded(card, session, callback)
|
||||
}
|
||||
|
||||
if (derivationPaths.isNullOrEmpty() || wallet == null || wallet.chainCode == null) {
|
||||
callback(CompletionResult.Success(ScanResponse(card, ProductType.Wallet, session.environment.walletData)))
|
||||
private fun createMissingWalletsIfNeeded(
|
||||
card: Card,
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<ScanResponse>) -> Unit
|
||||
) {
|
||||
if (card.wallets.isEmpty() || card.firmwareVersion < FirmwareVersion.MultiWalletAvailable) {
|
||||
startLinkingForBackupIfNeeded(card, session, callback)
|
||||
return
|
||||
}
|
||||
|
||||
DeriveWalletPublicKeysTask(wallet.publicKey, derivationPaths).run(session) { result ->
|
||||
val curvesToCreate = card.getCurvesForNonCreatedWallets()
|
||||
if (curvesToCreate.isEmpty()) {
|
||||
startLinkingForBackupIfNeeded(card, session, callback)
|
||||
return
|
||||
}
|
||||
|
||||
CreateWalletsTask(curvesToCreate).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
PreflightReadTask(
|
||||
PreflightReadMode.FullCardRead,
|
||||
card.cardId
|
||||
).run(session) { readResult ->
|
||||
when (readResult) {
|
||||
is CompletionResult.Success -> {
|
||||
startLinkingForBackupIfNeeded(card, session, callback)
|
||||
}
|
||||
is CompletionResult.Failure -> callback(
|
||||
CompletionResult.Failure(
|
||||
readResult.error
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startLinkingForBackupIfNeeded(
|
||||
card: Card,
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<ScanResponse>) -> Unit
|
||||
) {
|
||||
val activationIsFinished =
|
||||
preferencesStorage.usedCardsPrefStorage.isActivationFinished(card.cardId)
|
||||
|
||||
if (card.backupStatus == Card.BackupStatus.NoBackup &&
|
||||
!activationIsFinished && card.wallets.isNotEmpty()
|
||||
) {
|
||||
StartPrimaryCardLinkingTask().run(session) { linkingResult ->
|
||||
when (linkingResult) {
|
||||
is CompletionResult.Success -> {
|
||||
primaryCard = linkingResult.data
|
||||
deriveKeysIfNeeded(card, session, callback)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
deriveKeysIfNeeded(card, session, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
deriveKeysIfNeeded(card, session, callback)
|
||||
}
|
||||
}
|
||||
|
||||
private fun deriveKeysIfNeeded(
|
||||
card: Card,
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<ScanResponse>) -> Unit
|
||||
) {
|
||||
val derivations = collectDerivations(card)
|
||||
if (derivations.isEmpty()) {
|
||||
callback(
|
||||
CompletionResult.Success(
|
||||
ScanResponse(
|
||||
card = card,
|
||||
productType = ProductType.Wallet,
|
||||
walletData = session.environment.walletData,
|
||||
primaryCard = primaryCard
|
||||
)
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
DerivationTask(derivations).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val derivedKeys = mapOf(wallet.publicKey.toMapKey() to result.data)
|
||||
val response = ScanResponse(
|
||||
card,
|
||||
ProductType.Wallet,
|
||||
session.environment.walletData,
|
||||
derivedKeys = derivedKeys
|
||||
card = card,
|
||||
productType = ProductType.Wallet,
|
||||
walletData = session.environment.walletData,
|
||||
derivedKeys = result.data.entries,
|
||||
primaryCard = primaryCard
|
||||
)
|
||||
callback(CompletionResult.Success(response))
|
||||
|
||||
|
|
@ -165,8 +271,8 @@ private class ScanWalletProcessor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun collectDerivationPaths(card: Card): List<DerivationPath>? {
|
||||
val currenciesRepository = currenciesRepository ?: return null
|
||||
private fun getBlockchainsToDerive(card: Card): List<Blockchain> {
|
||||
val currenciesRepository = currenciesRepository ?: return emptyList()
|
||||
val cardCurrencies = currenciesRepository.loadCardCurrencies(card.cardId)
|
||||
|
||||
val blockchainsToDerive = if (cardCurrencies == null) {
|
||||
|
|
@ -176,13 +282,40 @@ private class ScanWalletProcessor(
|
|||
(cardCurrencies.blockchains + tokenBlockchains).toMutableList()
|
||||
}
|
||||
|
||||
if (shouldDeriveWC) {
|
||||
blockchainsToDerive.addAll(listOf(Blockchain.Ethereum, Blockchain.Binance, Blockchain.EthereumTestnet))
|
||||
if (card.settings.isHDWalletAllowed) {
|
||||
blockchainsToDerive.addAll(
|
||||
listOf(
|
||||
Blockchain.Ethereum,
|
||||
Blockchain.Binance,
|
||||
Blockchain.EthereumTestnet
|
||||
)
|
||||
)
|
||||
}
|
||||
return blockchainsToDerive.distinct()
|
||||
}
|
||||
|
||||
return blockchainsToDerive.toSet()
|
||||
.filter { it.getSupportedCurves()?.contains(EllipticCurve.Secp256k1) == true }
|
||||
.mapNotNull { it.derivationPath() }
|
||||
private fun collectDerivations(card: Card): Map<ByteArrayKey, List<DerivationPath>> {
|
||||
val blockchains = getBlockchainsToDerive(card)
|
||||
val derivations = mutableMapOf<ByteArrayKey, List<DerivationPath>>()
|
||||
|
||||
blockchains.forEach { blockchain ->
|
||||
val curve = blockchain.getPrimaryCurve()
|
||||
|
||||
val wallet = card.wallets.firstOrNull { it.curve == curve } ?: return@forEach
|
||||
if (wallet.chainCode == null) return@forEach
|
||||
|
||||
val key = wallet.publicKey.toMapKey()
|
||||
val path = blockchain.derivationPath()
|
||||
if (path != null) {
|
||||
val addedDerivations = derivations[key]
|
||||
if (addedDerivations != null) {
|
||||
derivations[key] = addedDerivations + path
|
||||
} else {
|
||||
derivations[key] = listOf(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
return derivations
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -197,17 +330,39 @@ private class ScanTwinProcessor : ProductCommandProcessor<ScanResponse> {
|
|||
is CompletionResult.Success -> {
|
||||
val publicKey = card.getSingleWallet()?.publicKey
|
||||
if (publicKey == null) {
|
||||
callback(CompletionResult.Success(ScanResponse(card, ProductType.Twins, null)))
|
||||
callback(
|
||||
CompletionResult.Success(
|
||||
ScanResponse(
|
||||
card,
|
||||
ProductType.Twins,
|
||||
null
|
||||
)
|
||||
)
|
||||
)
|
||||
return@run
|
||||
}
|
||||
val verified = TwinsHelper.verifyTwinPublicKey(readDataResult.data.issuerData, publicKey)
|
||||
val verified =
|
||||
TwinsHelper.verifyTwinPublicKey(readDataResult.data.issuerData, publicKey)
|
||||
if (verified) {
|
||||
val twinPublicKey = readDataResult.data.issuerData.sliceArray(0 until 65)
|
||||
val walletData = session.environment.walletData
|
||||
val response = ScanResponse(card, ProductType.Twins, walletData, twinPublicKey.toHexString())
|
||||
val response = ScanResponse(
|
||||
card,
|
||||
ProductType.Twins,
|
||||
walletData,
|
||||
twinPublicKey.toHexString()
|
||||
)
|
||||
callback(CompletionResult.Success(response))
|
||||
} else {
|
||||
callback(CompletionResult.Success(ScanResponse(card, ProductType.Twins, null)))
|
||||
callback(
|
||||
CompletionResult.Success(
|
||||
ScanResponse(
|
||||
card,
|
||||
ProductType.Twins,
|
||||
null
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure ->
|
||||
|
|
@ -218,43 +373,9 @@ private class ScanTwinProcessor : ProductCommandProcessor<ScanResponse> {
|
|||
|
||||
}
|
||||
|
||||
private class ScanOtherCardsProcessor : ProductCommandProcessor<ScanResponse> {
|
||||
|
||||
override fun proceed(card: Card, session: CardSession, callback: (result: CompletionResult<ScanResponse>) -> Unit) {
|
||||
val walletData = session.environment.walletData
|
||||
|
||||
if (card.wallets.isEmpty() || card.firmwareVersion < FirmwareVersion.MultiWalletAvailable) {
|
||||
callback(CompletionResult.Success(ScanResponse(card, ProductType.Other, walletData)))
|
||||
return
|
||||
}
|
||||
|
||||
val curvesToCreate = card.getCurvesForNonCreatedWallets()
|
||||
if (curvesToCreate.isEmpty()) {
|
||||
callback(CompletionResult.Success(ScanResponse(card, ProductType.Other, walletData)))
|
||||
return
|
||||
}
|
||||
|
||||
CreateWalletsTask(curvesToCreate).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
PreflightReadTask(PreflightReadMode.FullCardRead, card.cardId).run(session) { readResult ->
|
||||
when (readResult) {
|
||||
is CompletionResult.Success -> {
|
||||
val response = ScanResponse(readResult.data, ProductType.Other, walletData)
|
||||
callback(CompletionResult.Success(response))
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(readResult.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Card.getCurvesForNonCreatedWallets(): List<EllipticCurve> {
|
||||
val curvesPresent = wallets.map { it.curve }
|
||||
val curvesForNonCreatedWallets = supportedCurves.subtract(curvesPresent)
|
||||
val curvesPresent = wallets.map { it.curve }.toSet()
|
||||
val curvesForNonCreatedWallets = supportedCurves
|
||||
.subtract(curvesPresent + EllipticCurve.Secp256r1)
|
||||
return curvesForNonCreatedWallets.toList()
|
||||
}
|
||||
|
|
@ -38,6 +38,8 @@ class TradeCryptoHelper {
|
|||
|
||||
private const val TRANSACTION_RECEIPT_PATH = "transaction_receipt?transactionId="
|
||||
|
||||
private const val BASE_CURRENCY_USD = "USD"
|
||||
|
||||
fun getUrl(
|
||||
action: Action,
|
||||
blockchain: Blockchain?,
|
||||
|
|
@ -56,6 +58,7 @@ class TradeCryptoHelper {
|
|||
baseUrl = BASE_URL_BUY
|
||||
originalQuery = API_KEY_PATH + apiKey.urlEncode() +
|
||||
CURRENCY_PATH + cryptoCurrencyName.urlEncode() +
|
||||
BASE_CURRENCY_PATH + BASE_CURRENCY_USD.urlEncode() +
|
||||
WALLET_ADDRESS_PATH + walletAddress.urlEncode() +
|
||||
REDIRECT_URL_PATH + REDIRECT_URL_BUY.urlEncode()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ class FinalizeTwinTask(
|
|||
PreflightReadTask(PreflightReadMode.FullCardRead).run(session) { readResult ->
|
||||
when (readResult) {
|
||||
is CompletionResult.Success ->
|
||||
ScanProductTask(readResult.data, null, false).run(session, callback)
|
||||
ScanProductTask(readResult.data, null)
|
||||
.run(session, callback)
|
||||
is CompletionResult.Failure ->
|
||||
callback(CompletionResult.Failure(readResult.error))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ private fun handlePrepareScreen(
|
|||
cardInfo = action.scanResponse.card.toCardInfo(),
|
||||
appCurrencyState = AppCurrencyState(action.fiatCurrencyName),
|
||||
cardTermsOfUseUrl = action.cardTou.getUrl(action.scanResponse.card),
|
||||
createBackupAllowed = action.scanResponse.isTangemWallet() && !backupIsActive,
|
||||
createBackupAllowed = action.scanResponse.card.backupStatus == Card.BackupStatus.NoBackup,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -65,7 +65,7 @@ private fun handleEraseWallet(
|
|||
val notAllowedByAnyWallet = card?.wallets?.any { it.settings.isPermanent } ?: false
|
||||
val notAllowedByCard = notAllowedByAnyWallet ||
|
||||
(card?.isWalletDataSupported == true &&
|
||||
(!state.scanResponse.isTangemNote() && !state.scanResponse.isTangemWallet()))
|
||||
(!state.scanResponse.isTangemNote() && !state.scanResponse.supportsBackup()))
|
||||
|
||||
val notEmpty = state.wallets.any {
|
||||
it.hasPendingTransactions() || it.amounts.toSendableAmounts().isNotEmpty()
|
||||
|
|
@ -139,7 +139,7 @@ private fun handleSecurityAction(
|
|||
state.scanResponse?.isTangemNote() == true -> {
|
||||
EnumSet.of(SecurityOption.LongTap)
|
||||
}
|
||||
state.scanResponse?.isTangemWallet() == true -> {
|
||||
state.scanResponse?.supportsBackup() == true -> {
|
||||
EnumSet.of(state.securityScreenState?.currentOption)
|
||||
}
|
||||
else -> prepareAllowedSecurityOptions(
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import android.view.View
|
|||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.transition.TransitionInflater
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
|
|
@ -72,6 +73,9 @@ class DetailsSecurityFragment : Fragment(R.layout.fragment_details_security),
|
|||
?: EnumSet.noneOf(SecurityOption::class.java)
|
||||
)
|
||||
}
|
||||
tv_access_code_unavailable_disclaimer.show(
|
||||
state.scanResponse?.card?.backupStatus == Card.BackupStatus.NoBackup
|
||||
)
|
||||
}
|
||||
|
||||
private fun selectSecurityOption(securityOption: SecurityOption?) {
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ class FeedbackManager(
|
|||
val fileLog = if (emailData is ScanFailsEmail) createLogFile() else null
|
||||
activity.sendEmail(
|
||||
email = getSupportEmail(),
|
||||
subject = emailData.subject, message = emailData.joinTogether(infoHolder),
|
||||
subject = emailData.subject, message = emailData.joinTogether(infoHolder, emailData !is ScanFailsEmail),
|
||||
file = fileLog,
|
||||
onFail = onFail
|
||||
)
|
||||
|
|
@ -216,9 +216,14 @@ interface EmailData {
|
|||
|
||||
fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String
|
||||
|
||||
fun joinTogether(infoHolder: AdditionalEmailInfo): String {
|
||||
fun joinTogether(infoHolder: AdditionalEmailInfo, allowToErasePhoneInfo: Boolean): String {
|
||||
val allowToErasePhoneInfoDisclaimer = if (allowToErasePhoneInfo) {
|
||||
"Following information is optional. You can erase it if you don’t want to share it.\n\n"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
return "$mainMessage\n\n\n\n" +
|
||||
"Following information is optional. You can erase it if you don’t want to share it.\n" +
|
||||
allowToErasePhoneInfoDisclaimer +
|
||||
createOptionalMessage(infoHolder)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import com.tangem.tap.common.transitions.FrontCardExitTransition
|
|||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
import com.tangem.tap.features.home.redux.HomeState
|
||||
import com.tangem.tap.features.onboarding.products.BaseOnboardingFragment
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.android.synthetic.main.fragment_onboarding_main.*
|
||||
|
|
@ -36,6 +37,8 @@ class HomeFragment : BaseOnboardingFragment<HomeState>() {
|
|||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
store.dispatch(BackupAction.CheckForUnfinishedBackup)
|
||||
|
||||
toolbar.hide()
|
||||
val shareTransition = FragmentShareTransition(
|
||||
listOf(
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import com.tangem.tap.domain.DELAY_SDK_DIALOG_CLOSE
|
|||
import com.tangem.tap.features.onboarding.OnboardingHelper
|
||||
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.redux.BackupAction
|
||||
import com.tangem.tap.features.send.redux.states.ButtonState
|
||||
import com.tangem.tap.preferencesStorage
|
||||
import com.tangem.tap.scope
|
||||
|
|
@ -39,7 +38,6 @@ private val homeMiddleware: Middleware<AppState> = { dispatch, state ->
|
|||
store.dispatch(GlobalAction.RestoreAppCurrency)
|
||||
store.dispatch(GlobalAction.GetMoonPayStatus)
|
||||
store.dispatch(HomeAction.SetTermsOfUseState(preferencesStorage.wasDisclaimerAccepted()))
|
||||
store.dispatch(BackupAction.CheckForUnfinishedBackup)
|
||||
}
|
||||
is HomeAction.ShouldScanCardOnResume -> {
|
||||
if (action.shouldScanCard) {
|
||||
|
|
|
|||
|
|
@ -32,9 +32,12 @@ class OnboardingHelper {
|
|||
fun whereToNavigate(scanResponse: ScanResponse): AppScreen {
|
||||
return when (scanResponse.productType) {
|
||||
ProductType.Note -> AppScreen.OnboardingNote
|
||||
ProductType.Wallet -> AppScreen.OnboardingWallet
|
||||
ProductType.Wallet -> if (scanResponse.card.settings.isBackupAllowed) {
|
||||
AppScreen.OnboardingWallet
|
||||
} else {
|
||||
AppScreen.OnboardingOther
|
||||
}
|
||||
ProductType.Twins -> AppScreen.OnboardingTwins
|
||||
ProductType.Other -> AppScreen.OnboardingOther
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ private fun handleNoteAction(action: Action, dispatch: DispatchFunction) {
|
|||
withMainContext {
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val updatedResponse = scanResponse.copy(card = result.data)
|
||||
val updatedResponse = scanResponse.copy(card = result.data.card)
|
||||
onboardingManager.scanResponse = updatedResponse
|
||||
onboardingManager.activationStarted(updatedResponse.card.cardId)
|
||||
store.dispatch(OnboardingNoteAction.SetStepOfScreen(OnboardingNoteStep.TopUpWallet))
|
||||
|
|
|
|||
|
|
@ -67,7 +67,9 @@ private fun handleOtherCardsAction(action: Action, dispatch: DispatchFunction) {
|
|||
withMainContext {
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val updatedResponse = onboardingManager.scanResponse.copy(card = result.data)
|
||||
val updatedResponse = onboardingManager.scanResponse.copy(
|
||||
card = result.data.card
|
||||
)
|
||||
onboardingManager.scanResponse = updatedResponse
|
||||
onboardingManager.activationStarted(updatedResponse.card.cardId)
|
||||
|
||||
|
|
|
|||
|
|
@ -22,9 +22,11 @@ sealed class OnboardingWalletAction : Action {
|
|||
sealed class BackupAction : Action {
|
||||
|
||||
object DetermineBackupStep : BackupAction()
|
||||
object IntroduceBackup : BackupAction()
|
||||
data class IntroduceBackup(val buyCardsUrl: String? = null) : BackupAction()
|
||||
object StartBackup : BackupAction()
|
||||
object DismissBackup : BackupAction()
|
||||
|
||||
object StartAddingPrimaryCard : BackupAction()
|
||||
object ScanPrimaryCard : BackupAction()
|
||||
|
||||
object CheckForUnfinishedBackup : BackupAction()
|
||||
|
|
|
|||
|
|
@ -76,7 +76,11 @@ private fun handleWalletAction(action: Action) {
|
|||
is CompletionResult.Success -> {
|
||||
//here we must use updated scanResponse after createWallet & derivation
|
||||
val updatedResponse =
|
||||
globalState.onboardingState.onboardingManager.scanResponse.copy(card = result.data)
|
||||
globalState.onboardingState.onboardingManager.scanResponse.copy(
|
||||
card = result.data.card,
|
||||
derivedKeys = result.data.derivedKeys,
|
||||
primaryCard = result.data.primaryCard
|
||||
)
|
||||
onboardingManager.scanResponse = updatedResponse
|
||||
onboardingManager.activationStarted(updatedResponse.card.cardId)
|
||||
store.dispatch(OnboardingWalletAction.ProceedBackup)
|
||||
|
|
@ -106,7 +110,14 @@ private fun handleWalletAction(action: Action) {
|
|||
BackupService.State.FinalizingPrimaryCard -> BackupAction.PrepareToWritePrimaryCard
|
||||
is BackupService.State.FinalizingBackupCard ->
|
||||
BackupAction.PrepareToWriteBackupCard(backupState.index)
|
||||
else -> BackupAction.IntroduceBackup
|
||||
else -> {
|
||||
val url = if (card?.issuer?.name?.lowercase()?.contains("tangem") == true) {
|
||||
BUY_WALLET_URL
|
||||
} else {
|
||||
null
|
||||
}
|
||||
BackupAction.IntroduceBackup(url)
|
||||
}
|
||||
}
|
||||
store.dispatch(newAction)
|
||||
}
|
||||
|
|
@ -164,6 +175,13 @@ private fun handleBackupAction(action: BackupAction) {
|
|||
when (action) {
|
||||
is BackupAction.StartBackup -> {
|
||||
backupService.discardSavedBackup()
|
||||
val primaryCard = scanResponse?.primaryCard
|
||||
if (primaryCard != null) {
|
||||
backupService.setPrimaryCard(primaryCard)
|
||||
store.dispatch(BackupAction.StartAddingBackupCards)
|
||||
} else {
|
||||
store.dispatch(BackupAction.StartAddingPrimaryCard)
|
||||
}
|
||||
}
|
||||
is BackupAction.ScanPrimaryCard -> {
|
||||
backupService.readPrimaryCard(cardId = card?.cardId) { result ->
|
||||
|
|
|
|||
|
|
@ -39,12 +39,13 @@ class BackupReducer {
|
|||
): BackupState {
|
||||
|
||||
return when (action) {
|
||||
BackupAction.IntroduceBackup -> BackupState(
|
||||
is BackupAction.IntroduceBackup -> BackupState(
|
||||
backupStep = BackupStep.InitBackup,
|
||||
canSkipBackup = state.canSkipBackup
|
||||
canSkipBackup = state.canSkipBackup,
|
||||
buyAdditionalCardsUrl = action.buyCardsUrl
|
||||
)
|
||||
|
||||
BackupAction.StartBackup -> state.copy(backupStep = BackupStep.ScanOriginCard)
|
||||
BackupAction.StartAddingPrimaryCard -> state.copy(backupStep = BackupStep.ScanOriginCard)
|
||||
|
||||
BackupAction.StartAddingBackupCards -> {
|
||||
state.copy(backupStep = BackupStep.AddBackupCards)
|
||||
|
|
@ -127,6 +128,7 @@ class BackupReducer {
|
|||
BackupAction.DiscardBackup -> state
|
||||
BackupAction.ResumeBackup -> state
|
||||
BackupAction.DiscardSavedBackup -> state
|
||||
BackupAction.StartBackup -> state
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,7 +52,8 @@ data class BackupState(
|
|||
val accessCodeError: AccessCodeError? = null,
|
||||
val backupStep: BackupStep = BackupStep.InitBackup,
|
||||
val maxBackupCards: Int = 2,
|
||||
val canSkipBackup: Boolean = true
|
||||
val canSkipBackup: Boolean = true,
|
||||
val buyAdditionalCardsUrl: String? = null
|
||||
)
|
||||
|
||||
enum class AccessCodeError {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,13 @@ class BackupCardsWidget(
|
|||
val getTopOfAnchorViewForActivateState: () -> Float,
|
||||
) {
|
||||
|
||||
var currentState: WidgetState? = null
|
||||
|
||||
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)),
|
||||
|
|
@ -26,6 +32,10 @@ class BackupCardsWidget(
|
|||
}
|
||||
|
||||
fun toFolded(animate: Boolean = true, onEnd: () -> Unit = {}) {
|
||||
if (currentState == WidgetState.FOLDED) return
|
||||
|
||||
currentState = WidgetState.FOLDED
|
||||
|
||||
val animator = createAnimator(animate, onEnd)
|
||||
animator.playTogether(
|
||||
createAnimator(BackupCardType.ORIGIN, createLeapfrogProperties(BackupCardType.ORIGIN)),
|
||||
|
|
@ -36,6 +46,10 @@ class BackupCardsWidget(
|
|||
}
|
||||
|
||||
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)),
|
||||
|
|
@ -46,6 +60,8 @@ class BackupCardsWidget(
|
|||
}
|
||||
|
||||
fun toLeapfrog(animate: Boolean = true, onEnd: () -> Unit = {}) {
|
||||
currentState = WidgetState.LEAPFROG
|
||||
|
||||
val animator = createAnimator(animate, onEnd)
|
||||
animator.playTogether(
|
||||
createAnimator(BackupCardType.ORIGIN, createLeapfrogProperties(BackupCardType.ORIGIN)),
|
||||
|
|
@ -60,15 +76,6 @@ class BackupCardsWidget(
|
|||
animator.start()
|
||||
}
|
||||
}
|
||||
//
|
||||
// fun toActivate(animate: Boolean = true, onEnd: () -> Unit = {}) {
|
||||
// val animator = createAnimator(animate, onEnd)
|
||||
// animator.playTogether(
|
||||
// createAnimator(TwinCardNumber.First, createActivateProperties(TwinCardNumber.First)),
|
||||
// createAnimator(TwinCardNumber.Second, createActivateProperties(TwinCardNumber.Second))
|
||||
// )
|
||||
// animator.start()
|
||||
// }
|
||||
|
||||
private fun createAnimator(animate: Boolean, onEnd: () -> Unit): AnimatorSet {
|
||||
return AnimatorSet().apply {
|
||||
|
|
@ -196,6 +203,8 @@ class BackupCardsWidget(
|
|||
fun getSecondBackupCardView(): ImageView {
|
||||
return getLeapViewByCardNumber(BackupCardType.SECOND_BACKUP).view as ImageView
|
||||
}
|
||||
|
||||
enum class WidgetState { WELCOME, FOLDED, FAN, LEAPFROG }
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -148,19 +148,10 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
|
|||
}
|
||||
|
||||
private fun showScanOriginCard(state: BackupState) {
|
||||
|
||||
toolbar.title = getText(R.string.onboarding_navbar_title_creating_backup)
|
||||
prepareBackupView()
|
||||
|
||||
cardsWidget.toFolded()
|
||||
|
||||
tv_header.show()
|
||||
tv_body.show()
|
||||
view_pager_backup_info.hide()
|
||||
tab_layout_backup_info.hide()
|
||||
|
||||
imv_first_backup_card.show()
|
||||
imv_second_backup_card.show()
|
||||
|
||||
tv_header.text = getText(R.string.onboarding_title_scan_origin_card)
|
||||
tv_body.text = getString(
|
||||
R.string.onboarding_subtitle_scan_origin_card,
|
||||
|
|
@ -171,9 +162,20 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
|
|||
btn_main_action.setOnClickListener { store.dispatch(BackupAction.ScanPrimaryCard) }
|
||||
}
|
||||
|
||||
private fun showAddBackupCards(state: BackupState) {
|
||||
private fun prepareBackupView() {
|
||||
toolbar.title = getText(R.string.onboarding_navbar_title_creating_backup)
|
||||
|
||||
tv_header.show()
|
||||
tv_body.show()
|
||||
view_pager_backup_info.hide()
|
||||
tab_layout_backup_info.hide()
|
||||
|
||||
imv_first_backup_card.show()
|
||||
imv_second_backup_card.show()
|
||||
}
|
||||
|
||||
private fun showAddBackupCards(state: BackupState) {
|
||||
prepareBackupView()
|
||||
|
||||
accessCodeDialog?.dismiss()
|
||||
accessCodeDialog = null
|
||||
|
|
@ -190,11 +192,12 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
|
|||
|
||||
when (state.backupCardsNumber) {
|
||||
0 -> {
|
||||
cardsWidget.toFan()
|
||||
cardsWidget.toFan() {
|
||||
cardsWidget.getFirstBackupCardView().animate().alpha(0.6f).setDuration(200)
|
||||
cardsWidget.getSecondBackupCardView().animate().alpha(0.2f).setDuration(200)
|
||||
}
|
||||
tv_header.text = getText(R.string.onboarding_title_no_backup_cards)
|
||||
tv_body.text = getText(R.string.onboarding_subtitle_no_backup_cards)
|
||||
cardsWidget.getFirstBackupCardView().alpha = 0.6f
|
||||
cardsWidget.getSecondBackupCardView().alpha = 0.2f
|
||||
}
|
||||
1 -> {
|
||||
tv_header.text = getText(R.string.onboarding_title_one_backup_card)
|
||||
|
|
@ -283,10 +286,6 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
|
|||
|
||||
cardsWidget.toLeapfrog()
|
||||
|
||||
// imv_front_card.alpha = 1f
|
||||
// imv_first_backup_card.alpha = 1f
|
||||
// imv_second_backup_card.alpha = 1f
|
||||
|
||||
btn_alternative_action.hide()
|
||||
}
|
||||
|
||||
|
|
@ -373,9 +372,12 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
|
|||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.shop, menu)
|
||||
|
||||
val backupStep = store.state.onboardingWalletState.backupState.backupStep
|
||||
val backupState = store.state.onboardingWalletState.backupState
|
||||
val backupStep = backupState.backupStep
|
||||
|
||||
val shopMenuShouldBeVisible =
|
||||
backupStep == BackupStep.ScanOriginCard || backupStep == BackupStep.AddBackupCards
|
||||
(backupStep == BackupStep.ScanOriginCard || backupStep == BackupStep.AddBackupCards) &&
|
||||
backupState.buyAdditionalCardsUrl != null
|
||||
menu.getItem(0).isVisible = shopMenuShouldBeVisible
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import com.tangem.wallet.R
|
|||
|
||||
sealed class CurrencyListItem {
|
||||
var isAdded: Boolean = false
|
||||
var isLock: Boolean = false
|
||||
var isLocked: Boolean = false
|
||||
|
||||
data class TokenListItem(val token: Token) : CurrencyListItem()
|
||||
data class BlockchainListItem(val blockchain: Blockchain) : CurrencyListItem()
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.hdWallet.DerivationPath
|
||||
import com.tangem.common.hdWallet.ExtendedPublicKey
|
||||
import com.tangem.tap.common.extensions.ByteArrayKey
|
||||
import com.tangem.tap.common.extensions.dispatchErrorNotification
|
||||
import com.tangem.tap.common.extensions.toMapKey
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
|
|
@ -13,6 +16,7 @@ import com.tangem.tap.currenciesRepository
|
|||
import com.tangem.tap.domain.DELAY_SDK_DIALOG_CLOSE
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.TapWorkarounds.isTestCard
|
||||
import com.tangem.tap.domain.tasks.product.KeyWalletPublicKey
|
||||
import com.tangem.tap.domain.tasks.product.ScanResponse
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.scope
|
||||
|
|
@ -45,27 +49,8 @@ class TokensMiddleware {
|
|||
cardFirmware = scanResponse.card.firmwareVersion,
|
||||
isTestNet = isTestcard
|
||||
)
|
||||
val currencies = CurrencyListItem.createListOfCurrencies(blockchains, tokens).toMutableList()
|
||||
if (scanResponse.isTangemWallet()) {
|
||||
currencies.forEach {
|
||||
when (it) {
|
||||
is CurrencyListItem.TitleListItem -> {
|
||||
}
|
||||
is CurrencyListItem.BlockchainListItem -> {
|
||||
val firstCurve = it.blockchain.getSupportedCurves()?.firstOrNull()
|
||||
if (firstCurve == EllipticCurve.Ed25519) {
|
||||
it.isLock = true
|
||||
}
|
||||
}
|
||||
is CurrencyListItem.TokenListItem -> {
|
||||
val firstCurve = it.token.blockchain.getSupportedCurves()?.firstOrNull()
|
||||
if (firstCurve == EllipticCurve.Ed25519) {
|
||||
it.isLock = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val currencies =
|
||||
CurrencyListItem.createListOfCurrencies(blockchains, tokens).toMutableList()
|
||||
store.dispatch(TokensAction.LoadCurrencies.Success(currencies))
|
||||
}
|
||||
|
||||
|
|
@ -81,7 +66,7 @@ class TokensMiddleware {
|
|||
.map { it.token }
|
||||
if (blockchains.isEmpty() && tokens.isEmpty()) return
|
||||
|
||||
if (scanResponse.isTangemWallet()) {
|
||||
if (scanResponse.supportsHdWallet()) {
|
||||
deriveMissingBlockchains(scanResponse, blockchains, tokens)
|
||||
} else {
|
||||
submitAdd(blockchains, tokens)
|
||||
|
|
@ -94,26 +79,33 @@ class TokensMiddleware {
|
|||
blockchains: List<Blockchain>,
|
||||
tokens: List<Token>
|
||||
) {
|
||||
val wallet = scanResponse.card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: return
|
||||
|
||||
val tokenBlockchains = tokens.map { it.blockchain }
|
||||
val derivationPathsCandidates = (blockchains + tokenBlockchains).distinct()
|
||||
.mapNotNull { it.derivationPath() }
|
||||
|
||||
val mapKeyOfWalletPublicKey = wallet.publicKey.toMapKey()
|
||||
val alreadyDerivedKeys = scanResponse.derivedKeys[mapKeyOfWalletPublicKey]?.toMutableList() ?: mutableListOf()
|
||||
val alreadyDerivedPaths = alreadyDerivedKeys.map { it.derivationPath }
|
||||
|
||||
val toDerive = derivationPathsCandidates.filterNot { alreadyDerivedPaths.contains(it) }
|
||||
val derivationDataList = listOfNotNull(
|
||||
getDerivations(EllipticCurve.Secp256k1, scanResponse, blockchains, tokens),
|
||||
getDerivations(EllipticCurve.Ed25519, scanResponse, blockchains, tokens)
|
||||
)
|
||||
val derivations = derivationDataList.map { it.derivations }.toMap()
|
||||
|
||||
scope.launch {
|
||||
val result = tangemSdkManager.derivePublicKeys(scanResponse.card.cardId, wallet.publicKey, toDerive)
|
||||
val result = tangemSdkManager.derivePublicKeys(
|
||||
scanResponse.card.cardId,
|
||||
derivations
|
||||
)
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val newDerivedKeys = result.data
|
||||
alreadyDerivedKeys.addAll(newDerivedKeys)
|
||||
val newDerivedKeys = result.data.entries
|
||||
val updatedDerivedKeys =
|
||||
mutableMapOf<KeyWalletPublicKey, List<ExtendedPublicKey>>()
|
||||
|
||||
newDerivedKeys.forEach { entry ->
|
||||
val derivationData = derivationDataList.find {
|
||||
it.mapKeyOfWalletPublicKey == entry.key
|
||||
} ?: return@forEach
|
||||
updatedDerivedKeys[entry.key] =
|
||||
derivationData.alreadyDerivedKeys + entry.value.toList()
|
||||
}
|
||||
|
||||
val updatedScanResponse = scanResponse.copy(
|
||||
derivedKeys = mapOf(mapKeyOfWalletPublicKey to alreadyDerivedKeys.toList())
|
||||
derivedKeys = updatedDerivedKeys
|
||||
)
|
||||
store.dispatch(GlobalAction.SaveScanNoteResponse(updatedScanResponse))
|
||||
submitAdd(blockchains, tokens)
|
||||
|
|
@ -128,6 +120,37 @@ class TokensMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private fun getDerivations(
|
||||
curve: EllipticCurve,
|
||||
scanResponse: ScanResponse,
|
||||
blockchains: List<Blockchain>,
|
||||
tokens: List<Token>
|
||||
): DerivationData? {
|
||||
val wallet = scanResponse.card.wallets.firstOrNull { it.curve == curve } ?: return null
|
||||
|
||||
val tokenBlockchains = tokens.map { it.blockchain }
|
||||
val derivationPathsCandidates = (blockchains + tokenBlockchains).distinct()
|
||||
.mapNotNull { it.derivationPath() }
|
||||
|
||||
val mapKeyOfWalletPublicKey = wallet.publicKey.toMapKey()
|
||||
val alreadyDerivedKeys =
|
||||
scanResponse.derivedKeys[mapKeyOfWalletPublicKey]?.toMutableList() ?: mutableListOf()
|
||||
val alreadyDerivedPaths = alreadyDerivedKeys.map { it.derivationPath }
|
||||
|
||||
val toDerive = derivationPathsCandidates.filterNot { alreadyDerivedPaths.contains(it) }
|
||||
return DerivationData(
|
||||
derivations = mapKeyOfWalletPublicKey to toDerive,
|
||||
alreadyDerivedKeys = alreadyDerivedKeys,
|
||||
mapKeyOfWalletPublicKey = mapKeyOfWalletPublicKey
|
||||
)
|
||||
}
|
||||
|
||||
private class DerivationData(
|
||||
val derivations: Pair<ByteArrayKey, List<DerivationPath>>,
|
||||
val alreadyDerivedKeys: List<ExtendedPublicKey>,
|
||||
val mapKeyOfWalletPublicKey: ByteArrayKey
|
||||
)
|
||||
|
||||
private fun submitAdd(blockchains: List<Blockchain>, tokens: List<Token>) {
|
||||
(blockchains.map {
|
||||
WalletAction.MultiWallet.AddBlockchain(it)
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ class CurrenciesAdapter : ListAdapter<CurrencyListItem, RecyclerView.ViewHolder>
|
|||
}
|
||||
|
||||
private fun modifyAddTokenButton(currency: CurrencyListItem) {
|
||||
if (currency.isLock) {
|
||||
if (currency.isLocked) {
|
||||
view.btn_add_token.setText(R.string.common_add)
|
||||
view.btn_add_token.isEnabled = false
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ class MultiWalletMiddleware {
|
|||
}
|
||||
is WalletAction.MultiWallet.FindBlockchainsInUse -> {
|
||||
val scanResponse = globalState.scanResponse ?: return
|
||||
if (scanResponse.isTangemWallet()) return
|
||||
if (scanResponse.supportsHdWallet()) return
|
||||
|
||||
val cardFirmware = scanResponse.card.firmwareVersion
|
||||
val blockchains = currenciesRepository.getBlockchains(cardFirmware)
|
||||
|
|
@ -125,7 +125,7 @@ class MultiWalletMiddleware {
|
|||
}
|
||||
is WalletAction.MultiWallet.FindTokensInUse -> {
|
||||
val scanResponse = globalState.scanResponse ?: return
|
||||
if (scanResponse.isTangemWallet()) return
|
||||
if (scanResponse.supportsHdWallet()) return
|
||||
|
||||
val walletFactory = tapWalletManager.walletManagerFactory
|
||||
val card = scanResponse.card
|
||||
|
|
|
|||
|
|
@ -58,12 +58,12 @@ class TradeCryptoMiddleware {
|
|||
}
|
||||
|
||||
val url = TradeCryptoHelper.getUrl(
|
||||
exchangeAction,
|
||||
currency?.blockchain,
|
||||
currencySymbol,
|
||||
defaultAddress,
|
||||
config.moonPayApiKey,
|
||||
config.moonPayApiSecretKey
|
||||
action = exchangeAction,
|
||||
blockchain = currency?.blockchain,
|
||||
cryptoCurrencyName = currencySymbol,
|
||||
walletAddress = defaultAddress,
|
||||
apiKey = config.moonPayApiKey,
|
||||
secretKey = config.moonPayApiSecretKey
|
||||
)
|
||||
Timber.d("Moonpay $exchangeAction URL: $url")
|
||||
store.dispatchOnMain(NavigationAction.OpenUrl(url))
|
||||
|
|
|
|||
|
|
@ -44,6 +44,10 @@ class UsedCardsPrefStorage(
|
|||
save(foundItem, restoredList)
|
||||
}
|
||||
|
||||
fun isActivationFinished(cardId: String): Boolean {
|
||||
return !(findCardInfo(cardId)?.isActivationStarted ?: true)
|
||||
}
|
||||
|
||||
fun activationIsStarted(cardId: String): Boolean {
|
||||
return findCardInfo(cardId)?.isActivationStarted ?: false
|
||||
}
|
||||
|
|
|
|||
BIN
app/src/main/res/drawable/ic_voyr.png
Normal file
BIN
app/src/main/res/drawable/ic_voyr.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
|
|
@ -183,6 +183,23 @@
|
|||
app:layout_constraintBottom_toBottomOf="@id/tv_access_code_description"
|
||||
app:layout_constraintTop_toTopOf="@id/tv_access_code_title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_access_code_unavailable_disclaimer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_access_code_description"
|
||||
android:text="@string/details_manage_security_access_code_disclaimer"
|
||||
android:textColor="@color/darkGray1"
|
||||
android:textSize="16sp"
|
||||
android:textAlignment="textStart"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@
|
|||
<string name="alert_old_device_this_card">You may experience NFC problems with some iPhone 7/7+ during the extraction</string>
|
||||
<string name="alert_card_signed_transactions">This card has been already topped up and signed transactions in the past. Consider immediate withdrawal of all funds if you have received this card from an untrusted source.</string>
|
||||
<string name="alert_title">Warning</string>
|
||||
<string name="alert_unsupported_card">This card it is not designed to work with Tangem</string>
|
||||
<string name="alert_unsupported_card">This card is not designed to work with this app</string>
|
||||
<string name="alert_developer_card">The card you scanned is a development card. Don’t accept it as a payment</string>
|
||||
<string name="alert_old_card">Tangem cards manufactured before September 2019 cannot currently be extracted with an iPhone. We’re working hard with Apple to make it possible in future versions of iOS.</string>
|
||||
|
||||
|
|
|
|||
|
|
@ -292,5 +292,6 @@
|
|||
<string name="details_row_title_reset_factory_settings" translatable="false">Reset to factory settings</string>
|
||||
<string name="details_row_title_reset_factory_settings_warning" translatable="false">This action is irreversible. If, after resetting the card, someone sends funds to it, then you will not be able to withdraw them.</string>
|
||||
|
||||
<string name="details_manage_security_access_code_disclaimer" translatable="false">Access code is available only for cards with a backup.</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue