diff --git a/app/build.gradle b/app/build.gradle index 2a198dd3cb..243194231d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -71,6 +71,9 @@ dependencies { implementation 'com.google.android.material:material:1.2.1' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1' + implementation 'com.tangem:blockchain:1.123.0' + implementation 'com.tangem:core:1.90.0' + implementation 'com.tangem:sdk:1.90.0' implementation 'com.tangem:blockchain:1.123.0' implementation 'com.tangem:core:1.92.0' implementation 'com.tangem:sdk:1.92.0' diff --git a/app/src/main/assets/features_dev.json b/app/src/main/assets/features_dev.json index fd84b700a9..4866065376 100644 --- a/app/src/main/assets/features_dev.json +++ b/app/src/main/assets/features_dev.json @@ -1,5 +1,6 @@ { "isWalletPayIdEnabled": true, "isTopUpEnabled": true, + "isSendingToPayIdEnabled": true, "isCreatingTwinCardsAllowed": false } \ No newline at end of file diff --git a/app/src/main/assets/features_prod.json b/app/src/main/assets/features_prod.json index 9e06a389d0..7c4ebbbb72 100644 --- a/app/src/main/assets/features_prod.json +++ b/app/src/main/assets/features_prod.json @@ -1,5 +1,6 @@ { - "isWalletPayIdEnabled": false, - "isTopUpEnabled": false, + "isWalletPayIdEnabled": true, + "isTopUpEnabled": true, + "isSendingToPayIdEnabled": true, "isCreatingTwinCardsAllowed": true } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index ef059014db..2a20c88628 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -87,7 +87,8 @@ class MainActivity : AppCompatActivity() { val tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG) if (tag != null) { intent.action = null - store.dispatch(NavigationAction.NavigateTo(AppScreen.Home, false)) + store.dispatch(NavigationAction.NavigateTo(AppScreen.Home)) + store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) store.dispatch(HomeAction.ReadCard) } } diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt index 603c004f13..56f0d70061 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt @@ -18,13 +18,9 @@ import com.tangem.wallet.R fun FragmentActivity.openFragment(screen: AppScreen, addToBackStack: Boolean = true) { val transaction = this.supportFragmentManager.beginTransaction() - .replace( - R.id.fragment_container, - fragmentFactory(screen), - screen.name - ) + transaction.replace(R.id.fragment_container, fragmentFactory(screen), screen.name) if (addToBackStack && screen != AppScreen.Home) transaction.addToBackStack(null) - transaction.commit(); + transaction.commitAllowingStateLoss() } fun FragmentActivity.popBackTo(screen: AppScreen?, inclusive: Boolean = false) { diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index e34ddfe8c7..77d26702ab 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -3,6 +3,7 @@ package com.tangem.tap.domain import androidx.activity.ComponentActivity import com.tangem.* import com.tangem.commands.* +import com.tangem.commands.common.card.Card import com.tangem.commands.common.card.CardType import com.tangem.common.CompletionResult import com.tangem.common.extensions.calculateSha256 @@ -12,6 +13,7 @@ import com.tangem.tap.common.analytics.AnalyticsHandler import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask import com.tangem.tap.domain.tasks.ScanNoteResponse import com.tangem.tap.domain.tasks.ScanNoteTask +import com.tangem.tap.domain.twins.isTwinCard import com.tangem.wallet.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -65,7 +67,7 @@ class TangemSdkManager(val activity: ComponentActivity) { } suspend fun runTaskAsync( - runnable: CardSessionRunnable, cardId: String? = null, initialMessage: Message? = null + runnable: CardSessionRunnable, cardId: String? = null, initialMessage: Message? = null, ): CompletionResult = withContext(Dispatchers.IO) { suspendCoroutine { continuation -> @@ -76,9 +78,13 @@ class TangemSdkManager(val activity: ComponentActivity) { } private suspend fun runTaskAsyncReturnOnMain( - runnable: CardSessionRunnable, cardId: String? = null, initialMessage: Message? = null + runnable: CardSessionRunnable, cardId: String? = null, initialMessage: Message? = null, ): CompletionResult { val result = runTaskAsync(runnable, cardId, initialMessage) return withContext(Dispatchers.Main) { result } } + + fun changeDisplayedCardIdNumbersCount(card: Card) { + tangemSdk.config.cardIdDisplayedNumbersCount = if (card.isTwinCard()) 4 else null + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index 11fa152761..36dd3c238c 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -1,10 +1,10 @@ package com.tangem.tap.domain +import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.Wallet import com.tangem.blockchain.common.WalletManager import com.tangem.commands.common.card.CardStatus -import com.tangem.commands.common.card.masks.Product import com.tangem.commands.common.network.Result import com.tangem.common.extensions.toHexString import com.tangem.tap.common.analytics.AnalyticsEvent @@ -16,7 +16,8 @@ import com.tangem.tap.domain.config.ConfigManager import com.tangem.tap.domain.extensions.amountToCreateAccount import com.tangem.tap.domain.extensions.isNoAccountError import com.tangem.tap.domain.tasks.ScanNoteResponse -import com.tangem.tap.features.send.redux.AddressPayIdActionUi +import com.tangem.tap.domain.twins.TwinsHelper +import com.tangem.tap.domain.twins.isTwinCard import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.network.NetworkConnectivity import com.tangem.tap.network.coinmarketcap.CoinMarketCapService @@ -86,23 +87,27 @@ class TapWalletManager { val configManager = store.state.globalState.configManager if (TapWorkarounds.isStart2Coin) { configManager?.turnOff(ConfigManager.isWalletPayIdEnabled) + configManager?.turnOff(ConfigManager.isSendingToPayIdEnabled) configManager?.turnOff(ConfigManager.isTopUpEnabled) + } else if (data.walletManager?.wallet?.blockchain == Blockchain.Bitcoin + || data.card.cardData?.blockchainName == Blockchain.Bitcoin.id){ + configManager?.turnOff(ConfigManager.isWalletPayIdEnabled) + configManager?.resetToDefault(ConfigManager.isSendingToPayIdEnabled) + configManager?.resetToDefault(ConfigManager.isTopUpEnabled) } else { configManager?.resetToDefault(ConfigManager.isWalletPayIdEnabled) + configManager?.resetToDefault(ConfigManager.isSendingToPayIdEnabled) configManager?.resetToDefault(ConfigManager.isTopUpEnabled) } withContext(Dispatchers.Main) { store.dispatch(WalletAction.ResetState) store.dispatch(GlobalAction.SaveScanNoteResponse(data)) - store.dispatch(AddressPayIdActionUi.ChangePayIdState(configManager?.config?.isWalletPayIdEnabled - ?: false)) - if (data.card.cardData?.productMask?.contains(Product.TwinCard) == true) { + if (data.card.isTwinCard()) { val secondCardId = TwinsHelper.getTwinsCardId(data.card.cardId) val cardNumber = TwinsHelper.getTwinCardNumber(data.card.cardId) if (secondCardId != null && cardNumber != null) { store.dispatch(WalletAction.TwinsAction.SetTwinCard( - secondCardId, cardNumber, - configManager?.config?.isCreatingTwinCardsAllowed ?: false + secondCardId, cardNumber, isCreatingTwinCardsAllowed = true )) } } @@ -126,8 +131,7 @@ class TapWalletManager { store.dispatch(WalletAction.LoadArtwork(data.card, artworkId)) store.dispatch(WalletAction.LoadFiatRate) store.dispatch(WalletAction.LoadPayId) - } else if (data.card.status == CardStatus.Empty || - data.card.cardData?.productMask?.contains(Product.TwinCard) == true) { + } else if (data.card.status == CardStatus.Empty || data.card.isTwinCard()) { store.dispatch(WalletAction.EmptyWallet) store.dispatch(WalletAction.LoadArtwork(data.card, artworkId)) } else { diff --git a/app/src/main/java/com/tangem/tap/domain/config/ConfigManager.kt b/app/src/main/java/com/tangem/tap/domain/config/ConfigManager.kt index 75d02fd80b..0808870b86 100644 --- a/app/src/main/java/com/tangem/tap/domain/config/ConfigManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/config/ConfigManager.kt @@ -10,6 +10,7 @@ data class Config( val moonPayApiKey: String = "pk_test_kc90oYTANy7UQdBavDKGfL4K9l6VEPE", val moonPayApiSecretKey: String = "sk_test_V8w4M19LbDjjYOt170s0tGuvXAgyEb1C", val isWalletPayIdEnabled: Boolean = true, + val isSendingToPayIdEnabled: Boolean = true, val isTopUpEnabled: Boolean = false, val isCreatingTwinCardsAllowed: Boolean = false ) @@ -29,15 +30,16 @@ class ConfigManager( setupFeature(config.features) setupKey(config.configValues) } - remoteLoader.loadConfig { config -> - setupFeature(config.features) - onComplete?.invoke() - } +// remoteLoader.loadConfig { config -> +// setupFeature(config.features) +// onComplete?.invoke() +// } } fun turnOff(name: String) { when (name) { isWalletPayIdEnabled -> config = config.copy(isWalletPayIdEnabled = false) + isSendingToPayIdEnabled -> config = config.copy(isSendingToPayIdEnabled = false) isTopUpEnabled -> config = config.copy(isTopUpEnabled = false) isCreatingTwinCardsAllowed -> config = config.copy(isCreatingTwinCardsAllowed = false) } @@ -46,6 +48,7 @@ class ConfigManager( fun resetToDefault(name: String) { when (name) { isWalletPayIdEnabled -> config = config.copy(isWalletPayIdEnabled = defaultConfig.isWalletPayIdEnabled) + isSendingToPayIdEnabled -> config = config.copy(isSendingToPayIdEnabled = defaultConfig.isSendingToPayIdEnabled) isTopUpEnabled -> config = config.copy(isTopUpEnabled = defaultConfig.isTopUpEnabled) isCreatingTwinCardsAllowed -> config = config.copy(isCreatingTwinCardsAllowed = defaultConfig.isCreatingTwinCardsAllowed) @@ -58,11 +61,13 @@ class ConfigManager( config = config.copy( isWalletPayIdEnabled = model.isWalletPayIdEnabled, isTopUpEnabled = model.isTopUpEnabled, + isSendingToPayIdEnabled = model.isSendingToPayIdEnabled, isCreatingTwinCardsAllowed = model.isCreatingTwinCardsAllowed ) defaultConfig = defaultConfig.copy( isWalletPayIdEnabled = model.isWalletPayIdEnabled, isTopUpEnabled = model.isTopUpEnabled, + isSendingToPayIdEnabled = model.isSendingToPayIdEnabled, isCreatingTwinCardsAllowed = model.isCreatingTwinCardsAllowed ) } @@ -83,6 +88,7 @@ class ConfigManager( companion object { const val isWalletPayIdEnabled = "isWalletPayIdEnabled" + const val isSendingToPayIdEnabled = "isSendingToPayIdEnabled" const val isCreatingTwinCardsAllowed = "isCreatingTwinCardsAllowed" const val isTopUpEnabled = "isTopUpEnabled" const val coinMarketCapKey = "coinMarketCapKey" diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt index 1f0c9bcd06..dfa1752929 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt @@ -7,20 +7,17 @@ import com.tangem.TangemSdkError import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.commands.CommandResponse +import com.tangem.commands.ReadIssuerDataCommand import com.tangem.commands.common.card.Card import com.tangem.commands.common.card.CardStatus import com.tangem.commands.common.card.masks.Product -import com.tangem.commands.file.ReadFileDataCommand import com.tangem.commands.verifycard.VerifyCardCommand import com.tangem.commands.verifycard.VerifyCardResponse import com.tangem.common.CompletionResult -import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toHexString -import com.tangem.common.tlv.Tlv -import com.tangem.common.tlv.TlvDecoder -import com.tangem.common.tlv.TlvTag import com.tangem.tap.domain.TapSdkError -import com.tangem.tap.domain.TwinsHelper +import com.tangem.tap.domain.twins.TwinCardsManager +import com.tangem.tap.domain.twins.isTwinCard import com.tangem.tasks.ScanTask data class ScanNoteResponse( @@ -50,7 +47,7 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable { callback(CompletionResult.Success(ScanNoteResponse( - walletManager, card, verifyResult.data + walletManager, card, verifyResult.data, publicKey ))) } is CompletionResult.Failure -> { @@ -89,20 +86,21 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable) -> Unit ) { - ReadFileDataCommand(readPrivateFiles = true).run(session) { filesResult -> - when (filesResult) { + ReadIssuerDataCommand().run(session) { readDataResult -> + when (readDataResult) { is CompletionResult.Success -> { - val decoder = Tlv.deserialize(filesResult.data.fileData)?.let { TlvDecoder(it) } - val name = decoder?.decodeOptional(TlvTag.FileName) - if (name != null && name == TwinsHelper.TWIN_FILE_NAME) { - val publicKey = decoder.decodeOptional(TlvTag.FileData)?.toHexString() + val verified = TwinCardsManager.verifyTwinPublicKey( + readDataResult.data.issuerData, card.walletPublicKey + ) + if (verified) { + val twinPublicKey = readDataResult.data.issuerData.sliceArray(0 until 65) val walletManager = try { - WalletManagerFactory.makeMultisigWalletManager(card, publicKey!!.hexToBytes()) + WalletManagerFactory.makeMultisigWalletManager(card, twinPublicKey) } catch (exception: Exception) { callback(CompletionResult.Success(ScanNoteResponse(null, card))) return@run } - verifyCard(walletManager, card, publicKey, session, callback) + verifyCard(walletManager, card, twinPublicKey.toHexString(), session, callback) return@run } else { callback(CompletionResult.Success(ScanNoteResponse(null, card))) @@ -111,13 +109,13 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable callback(CompletionResult.Success(ScanNoteResponse(null, card))) } - } } private fun getErrorIfExcludedCard(card: Card): TangemError? { - val productMask = card.cardData?.productMask ?: return TangemSdkError.CardError() - if (!productMask.contains(Product.Note) && !productMask.contains(Product.TwinCard)) { + val productMask = card.cardData?.productMask + if (productMask != null && // product mask is on cards v2.30 and later + !productMask.contains(Product.Note) && !productMask.contains(Product.TwinCard)) { return TapSdkError.CardForDifferentApp } if (excludedBatches.contains(card.cardData?.batchId)) { 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 4b008408e6..7296c72a11 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 @@ -7,7 +7,6 @@ import com.tangem.commands.PurgeWalletCommand import com.tangem.commands.common.card.CardStatus import com.tangem.common.CompletionResult import com.tangem.tasks.CreateWalletTask -import com.tangem.tasks.file.DeleteFilesTask class CreateFirstTwinWalletTask : CardSessionRunnable { override val requiresPin2 = false @@ -18,24 +17,13 @@ class CreateFirstTwinWalletTask : CardSessionRunnable { when (response) { is CompletionResult.Success -> { session.environment.card = session.environment.card?.copy(status = CardStatus.Empty) - finishTask(session, callback) + CreateWalletTask().run(session) { callback(it) } } is CompletionResult.Failure -> callback(CompletionResult.Failure(response.error)) } } } else { - finishTask(session, callback) - } - } - - private fun finishTask(session: CardSession, callback: (result: CompletionResult) -> Unit) { - DeleteFilesTask().run(session) { deleteResponse -> - when (deleteResponse) { - is CompletionResult.Success -> - CreateWalletTask().run(session) { callback(it) } - is CompletionResult.Failure -> - callback(CompletionResult.Failure(deleteResponse.error)) - } + CreateWalletTask().run(session) { callback(it) } } } } \ No newline at end of file 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 4885e78f38..5587faa1ab 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 @@ -2,19 +2,24 @@ package com.tangem.tap.domain.twins import com.tangem.CardSession import com.tangem.CardSessionRunnable +import com.tangem.Message import com.tangem.commands.CreateWalletResponse import com.tangem.commands.PurgeWalletCommand import com.tangem.commands.common.card.CardStatus -import com.tangem.commands.file.WriteFileDataCommand import com.tangem.common.CompletionResult +import com.tangem.common.extensions.hexToBytes import com.tangem.tasks.CreateWalletTask -import com.tangem.tasks.file.DeleteFilesTask -class CreateSecondTwinWalletTask(private val firstPublicKey: String) : CardSessionRunnable { +class CreateSecondTwinWalletTask( + private val firstPublicKey: String, + private val preparingMessage: Message, + private val creatingWalletMessage: Message +) : CardSessionRunnable { override val requiresPin2 = true override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { if (session.environment.card?.walletPublicKey != null) { + session.setInitialMessage(preparingMessage) PurgeWalletCommand().run(session) { response -> when (response) { is CompletionResult.Success -> { @@ -31,20 +36,24 @@ class CreateSecondTwinWalletTask(private val firstPublicKey: String) : CardSessi } private fun finishTask(session: CardSession, callback: (result: CompletionResult) -> Unit) { - DeleteFilesTask().run(session) { deleteResponse -> - when (deleteResponse) { + session.setInitialMessage(creatingWalletMessage) + CreateWalletTask().run(session) { result -> + when (result) { is CompletionResult.Success -> { - val file = TwinCardsManager.createFileWithPublicKey(firstPublicKey) - WriteFileDataCommand(file).run(session) { result -> - when (result) { - is CompletionResult.Success -> CreateWalletTask().run(session, callback) - is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + session.environment.card = + session.environment.card?.copy(status = CardStatus.Loaded) + WriteProtectedIssuerDataTask( + firstPublicKey.hexToBytes(), TwinCardsManager.issuerKeys + ).run(session) { writeResult -> + when (writeResult) { + is CompletionResult.Success -> callback(result) + is CompletionResult.Failure -> + callback(CompletionResult.Failure(writeResult.error)) } } } - is CompletionResult.Failure -> - callback(CompletionResult.Failure(deleteResponse.error)) + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt new file mode 100644 index 0000000000..073c47b344 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt @@ -0,0 +1,31 @@ +package com.tangem.tap.domain.twins + +import com.tangem.CardSession +import com.tangem.CardSessionRunnable +import com.tangem.KeyPair +import com.tangem.commands.ReadCommand +import com.tangem.common.CompletionResult +import com.tangem.tap.domain.tasks.ScanNoteResponse +import com.tangem.tap.domain.tasks.ScanNoteTask + +class FinalizeTwinTask( + private val twinPublicKey: ByteArray, private val issuerKeys: KeyPair + ) : CardSessionRunnable { + override val requiresPin2 = true + + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + WriteProtectedIssuerDataTask(twinPublicKey, issuerKeys).run(session) { result -> + when (result) { + is CompletionResult.Success -> { + ReadCommand().run(session) { readResult -> + when (readResult) { + is CompletionResult.Success -> ScanNoteTask(readResult.data).run(session, callback) + is CompletionResult.Failure -> callback(CompletionResult.Failure(readResult.error)) + } + } + } + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + } + } + } +} \ No newline at end of file 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 index 0255ed6f5a..43d313f8a6 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt @@ -1,18 +1,13 @@ package com.tangem.tap.domain.twins +import com.tangem.KeyPair import com.tangem.Message -import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.blockchain.extensions.Result import com.tangem.blockchain.extensions.SimpleResult -import com.tangem.commands.file.FileData -import com.tangem.commands.file.WriteFileDataCommand import com.tangem.common.CompletionResult import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toHexString -import com.tangem.common.tlv.TlvEncoder -import com.tangem.common.tlv.TlvTag -import com.tangem.common.tlv.serialize -import com.tangem.tap.domain.TwinsHelper +import com.tangem.crypto.CryptoUtils import com.tangem.tap.domain.tasks.ScanNoteResponse import com.tangem.tap.tangemSdkManager @@ -39,9 +34,14 @@ class TwinCardsManager(private val scanNoteResponse: ScanNoteResponse) { } - suspend fun createSecondWallet(message: Message): SimpleResult { + suspend fun createSecondWallet( + initialMessage: Message, + preparingMessage: Message, + creatingWalletMessage: Message + ): SimpleResult { val response = tangemSdkManager.runTaskAsync( - CreateSecondTwinWalletTask(currentCardPublicKey!!), secondCardId, message + CreateSecondTwinWalletTask(currentCardPublicKey!!, preparingMessage, creatingWalletMessage), + secondCardId, initialMessage ) when (response) { is CompletionResult.Success -> { @@ -55,29 +55,29 @@ class TwinCardsManager(private val scanNoteResponse: ScanNoteResponse) { suspend fun complete(message: Message): Result { val response = tangemSdkManager.runTaskAsync( - WriteFileDataCommand(createFileWithPublicKey(secondCardPublicKey!!)), + FinalizeTwinTask(secondCardPublicKey!!.hexToBytes(), issuerKeys), currentCardId, message ) return when (response) { - is CompletionResult.Success -> { - val walletManager = WalletManagerFactory.makeMultisigWalletManager( - scanNoteResponse.card, secondCardPublicKey!!.hexToBytes() - ) - return Result.Success(scanNoteResponse.copy( - walletManager = walletManager, - secondTwinPublicKey = secondCardPublicKey - )) - } + is CompletionResult.Success -> Result.Success(response.data) is CompletionResult.Failure -> Result.failure(response.error) } } companion object { - fun createFileWithPublicKey(publicKey: String): FileData { - val nameTlv = TlvEncoder().encode(TlvTag.FileName, "TwinPublicKey") - val pubKey = TlvEncoder().encode(TlvTag.FileData, publicKey.hexToBytes()) - val tlvs = listOf(nameTlv, pubKey).serialize() - return FileData.DataProtectedByPasscode(tlvs) + val issuerKeys = KeyPair( + privateKey = "F9F4C50636C9E6FC65F92655BD5C21C85A5F6A34DCD0F1E75FCEA1980FE242F5".hexToBytes(), + publicKey = ("048196AA4B410AC44A3B9CCE18E7BE226AEA070ACC83A9CF67540F" + + "AC49AF25129F6A538A28AD6341358E3C4F9963064F" + + "7E365372A651D374E5C23CDD37FD099BF2").hexToBytes() + ) + + fun verifyTwinPublicKey(issuerData: ByteArray, cardWalletPublicKey: ByteArray?): Boolean { + if (issuerData.size < 65) return false + val publicKey = issuerData.sliceArray(0 until 65) + val signedKey = issuerData.sliceArray(65 until issuerData.size) + return (cardWalletPublicKey != null && + CryptoUtils.verify(cardWalletPublicKey, publicKey, signedKey)) } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinsHelper.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinsHelper.kt index ddb6d6854b..a1ca7dcc53 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/TwinsHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinsHelper.kt @@ -1,5 +1,7 @@ -package com.tangem.tap.domain +package com.tangem.tap.domain.twins +import com.tangem.commands.common.card.Card +import com.tangem.commands.common.card.masks.Product import com.tangem.tap.common.extensions.isEven class TwinsHelper { @@ -8,10 +10,10 @@ class TwinsHelper { fun getTwinCardNumber(cardId: String): TwinCardNumber? { return when { - firstCardBatches.map { cardId.startsWith(it) }.contains(true) -> { + firstCardSeries.map { cardId.startsWith(it) }.contains(true) -> { TwinCardNumber.First } - secondCardBatches.map { cardId.startsWith(it) }.contains(true) -> { + secondCardSeries.map { cardId.startsWith(it) }.contains(true) -> { TwinCardNumber.Second } else -> { @@ -21,28 +23,36 @@ class TwinsHelper { } fun getTwinsCardId(cardId: String): String? { - val cardIdWithNewBatch = when (getTwinCardNumber(cardId) ?: return null) { + val cardIdWithNewSeries = when (getTwinCardNumber(cardId) ?: return null) { TwinCardNumber.First -> { - val batchIndex = if (cardId.startsWith(firstCardBatches[1])) 1 else 2 - cardId.replace(firstCardBatches[batchIndex], secondCardBatches[batchIndex]) + val index = if (cardId.startsWith(firstCardSeries[0])) 0 else 1 + cardId.replace(firstCardSeries[index], secondCardSeries[index]) } TwinCardNumber.Second -> { - val batchIndex = if (cardId.startsWith(secondCardBatches[1])) 1 else 2 - cardId.replace(secondCardBatches[batchIndex], firstCardBatches[batchIndex]) + val index = if (cardId.startsWith(secondCardSeries[0])) 0 else 1 + cardId.replace(secondCardSeries[index], firstCardSeries[index]) } } - val cardIdWithoutChecksum = cardIdWithNewBatch.dropLast(1) + val cardIdWithoutChecksum = cardIdWithNewSeries.dropLast(1) val checkSum = cardIdWithoutChecksum.calculateLuhn() return cardIdWithoutChecksum + checkSum } - private val firstCardBatches = listOf("CB61", "CB64") - private val secondCardBatches = listOf("CB62", "CB65") + 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" + } + + private val firstCardSeries = listOf("CB61", "CB64") + private val secondCardSeries = listOf("CB62", "CB65") } } private fun String.calculateLuhn(): Int { - return 10 - this.reversed() + val checksum = this.reversed() .mapIndexed { index, c -> val digit = if (c in '0'..'9') c - '0' else c - 'A' if (!index.isEven()) { @@ -51,8 +61,9 @@ private fun String.calculateLuhn(): Int { val newDigit = digit * 2 if (newDigit >= 10) newDigit - 9 else newDigit } - } - .sum() % 10 + }.sum() + .rem(10) + return (10 - checksum) % 10 } enum class TwinCardNumber(val number: Int) { @@ -62,4 +73,12 @@ enum class TwinCardNumber(val number: Int) { First -> Second Second -> First } +} + +fun Card.isTwinCard(): Boolean { + return this.cardData?.productMask?.contains(Product.TwinCard) == true +} + +fun Card.getTwinCardIdForUser(): String { + return TwinsHelper.getTwinCardIdForUser(this.cardId) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/twins/WriteProtectedIssuerDataTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/WriteProtectedIssuerDataTask.kt new file mode 100644 index 0000000000..30d3b688dd --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/twins/WriteProtectedIssuerDataTask.kt @@ -0,0 +1,59 @@ +package com.tangem.tap.domain.twins + +import com.tangem.CardSession +import com.tangem.CardSessionRunnable +import com.tangem.KeyPair +import com.tangem.TangemSdkError +import com.tangem.commands.* +import com.tangem.common.CompletionResult +import com.tangem.common.extensions.calculateSha256 +import com.tangem.common.extensions.guard +import com.tangem.common.files.FileHashHelper + +class WriteProtectedIssuerDataTask( + private val twinPublicKey: ByteArray, private val issuerKeys: KeyPair +) : CardSessionRunnable { + override val requiresPin2 = true + + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + val hashes = arrayOf(twinPublicKey.calculateSha256()) + SignCommand(hashes).run(session) { signResult -> + when (signResult) { + is CompletionResult.Success -> { + ReadIssuerDataCommand().run(session) { readResult -> + when (readResult) { + is CompletionResult.Success -> { + writeIssuerData( + twinPublicKey, issuerKeys, signResult.data.signature, + readResult.data, session, callback + ) + } + is CompletionResult.Failure -> callback(CompletionResult.Failure(readResult.error)) + } + } + } + is CompletionResult.Failure -> callback(CompletionResult.Failure(signResult.error)) + } + } + } + + private fun writeIssuerData( + twinPublicKey: ByteArray, issuerKeys: KeyPair, cardSignature: ByteArray, + readResponse: ReadIssuerDataResponse, + session: CardSession, callback: (result: CompletionResult + ) -> Unit) { + val cardId = session.environment.card?.cardId.guard { + callback(CompletionResult.Failure(TangemSdkError.CardError())) + return + } + val counter = (readResponse.issuerDataCounter ?: 0) + 1 + val data = twinPublicKey + cardSignature + val signedByIssuer = FileHashHelper.prepareHashes( + cardId, data, counter, issuerKeys.privateKey + ) + WriteIssuerDataCommand( + data, signedByIssuer.finalizingSignature!!, + counter, issuerKeys.publicKey + ).run(session, callback) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt index fe8c56708e..2ec73c0f00 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt @@ -5,8 +5,8 @@ import com.tangem.blockchain.common.Wallet import com.tangem.commands.common.card.Card import com.tangem.tap.common.redux.NotificationAction import com.tangem.tap.common.redux.global.FiatCurrencyName -import com.tangem.tap.domain.TwinCardNumber import com.tangem.tap.domain.tasks.ScanNoteResponse +import com.tangem.tap.domain.twins.TwinCardNumber import com.tangem.tap.features.details.redux.twins.CreateTwinWallet import com.tangem.tap.network.coinmarketcap.FiatCurrency import com.tangem.wallet.R @@ -45,12 +45,16 @@ sealed class DetailsAction : Action { } sealed class CreateTwinWalletAction : DetailsAction() { - object ShowWarning : CreateTwinWalletAction() + data class ShowWarning( + val twinCardNumber: TwinCardNumber?, + val createTwinWallet: CreateTwinWallet = CreateTwinWallet.RecreateWallet + ) : CreateTwinWalletAction() + object NotEmpty : CreateTwinWalletAction(), NotificationAction { + override val messageResource = R.string.details_notification_erase_wallet_not_possible + } object ShowAlert : CreateTwinWalletAction() object HideAlert : CreateTwinWalletAction() - data class Proceed( - val twinCardNumber: TwinCardNumber?, val createTwinWallet: CreateTwinWallet - ) : CreateTwinWalletAction() + object Proceed: CreateTwinWalletAction() object Cancel : CreateTwinWalletAction() { object Confirm : CreateTwinWalletAction() @@ -61,7 +65,11 @@ sealed class DetailsAction : Action { object Failure : CreateTwinWalletAction() } - data class LaunchSecondStep(val message: Message) : CreateTwinWalletAction() { + data class LaunchSecondStep( + val initialMessage: Message, + val preparingMessage: Message, + val creatingWalletMessage: Message, + ) : CreateTwinWalletAction() { object Success : CreateTwinWalletAction() object Failure : CreateTwinWalletAction() } diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt index 58997e57b6..593f31d21f 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt @@ -2,12 +2,12 @@ package com.tangem.tap.features.details.redux import com.tangem.commands.common.card.Card -import com.tangem.commands.common.card.masks.Product import com.tangem.commands.common.card.masks.Settings import com.tangem.tap.common.redux.AppState import com.tangem.tap.domain.TapWorkarounds -import com.tangem.tap.domain.TwinsHelper import com.tangem.tap.domain.extensions.toSendableAmounts +import com.tangem.tap.domain.twins.TwinsHelper +import com.tangem.tap.domain.twins.isTwinCard import com.tangem.tap.features.details.redux.twins.CreateTwinWalletReducer import com.tangem.tap.features.details.redux.twins.CreateTwinWalletState import org.rekotlin.Action @@ -55,7 +55,7 @@ private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: Deta SecurityOption.LongTap } } - val twinsState = if (action.card.cardData?.productMask?.contains(Product.TwinCard) == true) { + val twinsState = if (action.card.isTwinCard()) { CreateTwinWalletState( scanResponse = action.scanNoteResponse, twinCardNumber = TwinsHelper.getTwinCardNumber(action.card.cardId), diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletMiddleware.kt index 279ae9a5c0..38cfac39bc 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletMiddleware.kt @@ -2,9 +2,9 @@ package com.tangem.tap.features.details.redux.twins import com.tangem.blockchain.extensions.Result import com.tangem.blockchain.extensions.SimpleResult -import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.domain.extensions.toSendableAmounts import com.tangem.tap.domain.twins.TwinCardsManager import com.tangem.tap.features.details.redux.DetailsAction import com.tangem.tap.scope @@ -17,8 +17,19 @@ class CreateTwinWalletMiddleware { var twinsManager: TwinCardsManager? = null fun handle(action: DetailsAction.CreateTwinWalletAction) { when (action) { - DetailsAction.CreateTwinWalletAction.ShowWarning -> - store.dispatch(NavigationAction.NavigateTo(AppScreen.CreateTwinWalletWarning)) + is DetailsAction.CreateTwinWalletAction.ShowWarning -> { + val wallet = store.state.detailsState.wallet + if (wallet == null) { + store.dispatch(NavigationAction.NavigateTo(AppScreen.CreateTwinWalletWarning)) + return + } + val notEmpty = wallet.recentTransactions.isNotEmpty() || wallet.amounts.toSendableAmounts().isNotEmpty() + if (notEmpty) { + store.dispatch(DetailsAction.CreateTwinWalletAction.NotEmpty) + } else { + store.dispatch(NavigationAction.NavigateTo(AppScreen.CreateTwinWalletWarning)) + } + } is DetailsAction.CreateTwinWalletAction.Proceed -> store.dispatch(NavigationAction.NavigateTo(AppScreen.CreateTwinWallet)) is DetailsAction.CreateTwinWalletAction.Cancel -> { @@ -34,7 +45,7 @@ class CreateTwinWalletMiddleware { } is DetailsAction.CreateTwinWalletAction.Cancel.Confirm -> { twinsManager = null - store.dispatch(NavigationAction.PopBackTo()) + store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) } is DetailsAction.CreateTwinWalletAction.LaunchFirstStep -> { store.state.globalState.scanNoteResponse?.let { @@ -60,7 +71,8 @@ class CreateTwinWalletMiddleware { } is DetailsAction.CreateTwinWalletAction.LaunchSecondStep -> scope.launch { - val result = twinsManager?.createSecondWallet(action.message) + val result = twinsManager?.createSecondWallet(action.initialMessage, + action.preparingMessage, action.creatingWalletMessage) withContext(Dispatchers.Main) { when (result) { SimpleResult.Success -> @@ -93,8 +105,11 @@ class CreateTwinWalletMiddleware { } } is DetailsAction.CreateTwinWalletAction.LaunchThirdStep.Success -> { - store.dispatch(GlobalAction.SaveScanNoteResponse(action.scanNoteResponse)) + scope.launch { + store.state.globalState.tapWalletManager.onCardScanned(action.scanNoteResponse) + } store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) + store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) } DetailsAction.CreateTwinWalletAction.LaunchThirdStep.Failure -> { diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletReducer.kt index 6b9ff37533..7de266d076 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletReducer.kt @@ -9,9 +9,17 @@ class CreateTwinWalletReducer { action: DetailsAction.CreateTwinWalletAction, state: DetailsState ): DetailsState { return when (action) { - DetailsAction.CreateTwinWalletAction.ShowWarning -> { - state + is DetailsAction.CreateTwinWalletAction.ShowWarning -> { + state.copy(createTwinWalletState = CreateTwinWalletState( + scanResponse = null, + twinCardNumber = action.twinCardNumber + ?: state.createTwinWalletState?.twinCardNumber, + createTwinWallet = action.createTwinWallet, + showAlert = false, + allowRecreatingWallet = state.createTwinWalletState?.allowRecreatingWallet + )) } + DetailsAction.CreateTwinWalletAction.NotEmpty -> state DetailsAction.CreateTwinWalletAction.ShowAlert -> { state.copy(createTwinWalletState = state.createTwinWalletState?.copy( showAlert = true @@ -23,14 +31,7 @@ class CreateTwinWalletReducer { )) } is DetailsAction.CreateTwinWalletAction.Proceed -> { - state.copy(createTwinWalletState = CreateTwinWalletState( - scanResponse = null, - twinCardNumber = action.twinCardNumber - ?: state.createTwinWalletState?.twinCardNumber, - createTwinWallet = action.createTwinWallet, - showAlert = false, - step = CreateTwinWalletStep.FirstStep, - )) + state } DetailsAction.CreateTwinWalletAction.Cancel -> state DetailsAction.CreateTwinWalletAction.Cancel.Confirm -> state diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletState.kt index d42808e26e..488ac431bc 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletState.kt @@ -1,7 +1,7 @@ package com.tangem.tap.features.details.redux.twins -import com.tangem.tap.domain.TwinCardNumber import com.tangem.tap.domain.tasks.ScanNoteResponse +import com.tangem.tap.domain.twins.TwinCardNumber data class CreateTwinWalletState( val scanResponse: ScanNoteResponse?, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt index 6ad375022a..262482b360 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt @@ -8,9 +8,12 @@ import androidx.transition.TransitionInflater import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.extensions.show import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.domain.twins.getTwinCardIdForUser +import com.tangem.tap.domain.twins.isTwinCard import com.tangem.tap.features.details.redux.DetailsAction import com.tangem.tap.features.details.redux.DetailsState import com.tangem.tap.features.details.redux.SecurityOption +import com.tangem.tap.features.details.redux.twins.CreateTwinWallet import com.tangem.tap.store import com.tangem.wallet.R import kotlinx.android.synthetic.main.fragment_details.* @@ -61,11 +64,20 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber - store.dispatch(DetailsAction.CreateTwinWalletAction.Cancel.Confirm) - } - .setNegativeButton(R.string.common_cancel) { _, _ -> - store.dispatch(DetailsAction.CreateTwinWalletAction.HideAlert) - } - .create() - dialog?.show() + if (dialog == null) { + dialog = MaterialAlertDialogBuilder(requireContext()) + .setMessage(R.string.details_twins_recreate_alert) + .setPositiveButton(R.string.common_ok) { _, _ -> + store.dispatch(DetailsAction.CreateTwinWalletAction.Cancel.Confirm) + } + .setNegativeButton(R.string.common_cancel) { _, _ -> + dialog?.cancel() + } + .setOnCancelListener { + store.dispatch(DetailsAction.CreateTwinWalletAction.HideAlert) + } + .create() + dialog?.show() + } } else { dialog?.cancel() dialog = null diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/twins/TwinWalletWarningFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/twins/TwinWalletWarningFragment.kt index 9896b988f7..678dddf2bb 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/twins/TwinWalletWarningFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/twins/TwinWalletWarningFragment.kt @@ -5,12 +5,15 @@ import android.view.View import androidx.activity.OnBackPressedCallback import androidx.fragment.app.Fragment import androidx.transition.TransitionInflater +import com.squareup.picasso.Picasso import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.features.details.redux.DetailsAction import com.tangem.tap.features.details.redux.twins.CreateTwinWallet +import com.tangem.tap.features.wallet.redux.Artwork import com.tangem.tap.store import com.tangem.wallet.R import kotlinx.android.synthetic.main.fragment_details_twin_cards_warning.* +import kotlinx.android.synthetic.main.layout_twin_cards_orange.* class TwinWalletWarningFragment : Fragment(R.layout.fragment_details_twin_cards_warning) { @@ -28,13 +31,29 @@ class TwinWalletWarningFragment : Fragment(R.layout.fragment_details_twin_cards_ override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + val createTwinWallet = store.state.detailsState.createTwinWalletState?.createTwinWallet + if (createTwinWallet == CreateTwinWallet.CreateWallet) { + tv_twin_cards_description.text = getText(R.string.details_twins_recreate_subtitle) + } else if (createTwinWallet == CreateTwinWallet.RecreateWallet) { + tv_twin_cards_description.text = getText(R.string.details_twins_recreate_warning) + } + btn_cancel.setOnClickListener { store.dispatch(NavigationAction.PopBackTo()) } btn_start.setOnClickListener { store.dispatch( - DetailsAction.CreateTwinWalletAction.Proceed( - null, createTwinWallet = CreateTwinWallet.RecreateWallet - )) + DetailsAction.CreateTwinWalletAction.Proceed) } + Picasso.get() + .load(Artwork.TWIN_CARD_1) + .placeholder(R.drawable.card_placeholder) + ?.error(R.drawable.card_placeholder) + ?.into(iv_twin_card_1) + + Picasso.get() + .load(Artwork.TWIN_CARD_2) + .placeholder(R.drawable.card_placeholder) + ?.error(R.drawable.card_placeholder) + ?.into(iv_twin_card_2) } diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index 7f4f67329c..aaf52f3564 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -34,6 +34,7 @@ class HomeMiddleware { withContext(Dispatchers.Main) { when (result) { is CompletionResult.Success -> { + tangemSdkManager.changeDisplayedCardIdNumbersCount(result.data.card) store.dispatch(GlobalAction.RestoreAppCurrency) store.state.globalState.tapWalletManager.onCardScanned(result.data) showDisclaimerOrNavigateToWallet() diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt index 6cbe7787d9..bcb227de3b 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt @@ -23,7 +23,7 @@ object ReleaseSendState : Action data class PrepareSendScreen( val coinAmount: Amount?, - val tokenAmount: Amount? = null + val tokenAmount: Amount? = null, ) : SendScreenAction // Address or PayId @@ -34,7 +34,7 @@ sealed class AddressPayIdActionUi : SendScreenActionUi { object CheckAddressPayId : AddressPayIdActionUi() data class SetTruncateHandler(val handler: (String) -> String) : AddressPayIdActionUi() data class TruncateOrRestore(val truncate: Boolean) : AddressPayIdActionUi() - data class ChangePayIdState(val walletPayIdEnabled: Boolean) : AddressPayIdActionUi() + data class ChangePayIdState(val sendingToPayIdEnabled: Boolean) : AddressPayIdActionUi() } @@ -48,7 +48,7 @@ sealed class TransactionExtrasAction : SendScreenActionUi { object Release : TransactionExtrasAction() sealed class XlmMemo : TransactionExtrasAction() { -// data class ChangeSelectedMemo(val memoType: XlmMemoType) : XlmMemo() + // data class ChangeSelectedMemo(val memoType: XlmMemoType) : XlmMemo() data class HandleUserInput(val data: String) : XlmMemo() } diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressPayIdMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressPayIdMiddleware.kt index d82a747a4c..988613d3f1 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressPayIdMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressPayIdMiddleware.kt @@ -124,7 +124,7 @@ internal class AddressPayIdMiddleware { private fun isValidBlockchainAddressAndNotTheSameAsWallet(wallet: Wallet, address: String): Error? { return if (wallet.blockchain.validateAddress(address)) { - if (wallet.address != address) { + if (wallet.addresses.all { it.value != address } ) { null } else { Error.ADDRESS_SAME_AS_WALLET @@ -167,6 +167,6 @@ internal class AddressPayIdMiddleware { } private fun isPayIdEnabled(): Boolean { - return store.state.globalState.configManager?.config?.isWalletPayIdEnabled ?: false + return store.state.globalState.configManager?.config?.isSendingToPayIdEnabled ?: false } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt index 52735189af..65b9f4d9e6 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt @@ -42,6 +42,7 @@ val sendMiddleware: Middleware = { dispatch, appState -> is RequestFee -> RequestFeeMiddleware().handle(appState(), dispatch) is SendActionUi.SendAmountToRecipient -> verifyAndSendTransaction(action, appState(), dispatch) + is PrepareSendScreen -> setIfSendingToPayIdEnabled(appState(), dispatch) } nextDispatch(action) } @@ -195,3 +196,9 @@ fun createValidateTransactionError(errorList: EnumSet, walletM return TapError.ValidateTransactionErrors(tapErrors) { it.joinToString("\r\n") } } +private fun setIfSendingToPayIdEnabled(appState: AppState?, dispatch: (Action) -> Unit) { + val isSendingToPayIdEnabled = + appState?.globalState?.configManager?.config?.isSendingToPayIdEnabled ?: false + dispatch(AddressPayIdActionUi.ChangePayIdState(isSendingToPayIdEnabled)) +} + diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AddressPayIdReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AddressPayIdReducer.kt index 3402e48d71..faf6f1b4b0 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AddressPayIdReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AddressPayIdReducer.kt @@ -30,7 +30,7 @@ class AddressPayIdReducer : SendInternalReducer { is AddressPayIdActionUi.PasteAddressPayId -> return sendState is AddressPayIdActionUi.CheckClipboard -> return sendState is AddressPayIdActionUi.CheckAddressPayId -> return sendState - is AddressPayIdActionUi.ChangePayIdState -> state.copy(walletPayIdEnabled = action.walletPayIdEnabled) + is AddressPayIdActionUi.ChangePayIdState -> state.copy(sendingToPayIdEnabled = action.sendingToPayIdEnabled) } return updateLastState(sendState.copy(addressPayIdState = result), result) } diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt index 52cf87c4fa..68ead3b454 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt @@ -10,7 +10,7 @@ data class AddressPayIdState( val destinationWalletAddress: String? = null, val error: AddressPayIdVerifyAction.Error? = null, val truncateHandler: ((String) -> String)? = null, - val walletPayIdEnabled: Boolean = false, + val sendingToPayIdEnabled: Boolean = false, val pasteIsEnabled: Boolean = false ) : SendScreenState { diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt index 439375e766..6d13047f73 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt @@ -126,7 +126,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber val til = fg.tilAddressOrPayId val parsedError = parseError(til.context, state.error) - val hintResId = if (state.walletPayIdEnabled) { + val hintResId = if (state.sendingToPayIdEnabled) { R.string.send_destination_hint_address_payid } else { R.string.send_destination_hint_address diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt index 58ffdb8acc..0656a9583f 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt @@ -9,7 +9,7 @@ import com.tangem.tap.common.redux.ErrorAction import com.tangem.tap.common.redux.NotificationAction import com.tangem.tap.common.redux.global.CryptoCurrencyName import com.tangem.tap.domain.TapError -import com.tangem.tap.domain.TwinCardNumber +import com.tangem.tap.domain.twins.TwinCardNumber import com.tangem.wallet.R import org.rekotlin.Action import java.math.BigDecimal diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt index bdd1a724cb..2f2c041924 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt @@ -20,8 +20,9 @@ import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.PayIdManager import com.tangem.tap.domain.TapError import com.tangem.tap.domain.TopUpHelper -import com.tangem.tap.domain.TwinsHelper import com.tangem.tap.domain.extensions.toSendableAmounts +import com.tangem.tap.domain.twins.TwinsHelper +import com.tangem.tap.domain.twins.isTwinCard import com.tangem.tap.features.details.redux.DetailsAction import com.tangem.tap.features.details.redux.twins.CreateTwinWallet import com.tangem.tap.features.send.redux.PrepareSendScreen @@ -64,7 +65,7 @@ class WalletMiddleware { } is WalletAction.CreateWallet -> { if (store.state.walletState.twinCardsState != null) { - store.dispatch(DetailsAction.CreateTwinWalletAction.Proceed( + store.dispatch(DetailsAction.CreateTwinWalletAction.ShowWarning( store.state.globalState.scanNoteResponse?.card?.cardId?.let { TwinsHelper.getTwinCardNumber(it) }, @@ -124,8 +125,15 @@ class WalletMiddleware { val result = tangemSdkManager.scanNote(FirebaseAnalyticsHandler) when (result) { is CompletionResult.Success -> { + tangemSdkManager.changeDisplayedCardIdNumbersCount(result.data.card) store.state.globalState.tapWalletManager .onCardScanned(result.data, true) + if (store.state.walletState.twinCardsState != null) { + val showOnboarding = !preferencesStorage.wasTwinsOnboardingShown() + if (showOnboarding) { + store.dispatch(NavigationAction.NavigateTo(AppScreen.TwinsOnboarding)) + } + } } } } @@ -228,6 +236,7 @@ class WalletMiddleware { if (card.getType() != CardType.Release) { return WarningType.DevCard } + if (card.isTwinCard()) return null return if (signatureCountValidator == null) { if (card.walletSignedHashes ?: 0 > 0) { @@ -249,6 +258,8 @@ class WalletMiddleware { val card = store.state.globalState.scanNoteResponse?.card if (card == null || preferencesStorage.wasCardScannedBefore(card.cardId)) return + if (card.isTwinCard()) return + val validator = store.state.globalState.scanNoteResponse?.walletManager as? SignatureCountValidator scope.launch { @@ -276,9 +287,10 @@ private class TopUpMiddleware { when (action) { is WalletAction.TopUpAction.TopUp -> { val config = store.state.globalState.configManager?.config ?: return + val defaultAddress = store.state.walletState.walletAddresses!!.list[0].address val url = TopUpHelper.getUrl( store.state.walletState.currencyData.currencySymbol!!, - store.state.walletState.walletAddresses!!.selectedAddress.address, + defaultAddress, config.moonPayApiKey, config.moonPayApiSecretKey ) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt index 05436a495a..533121bbe8 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt @@ -12,6 +12,7 @@ import com.tangem.tap.common.extensions.toQrCode import com.tangem.tap.common.redux.AppState import com.tangem.tap.domain.TapError import com.tangem.tap.domain.getFirstToken +import com.tangem.tap.domain.twins.TwinCardNumber import com.tangem.tap.features.wallet.models.removeUnknownTransactions import com.tangem.tap.features.wallet.models.toPendingTransactions import com.tangem.tap.features.wallet.ui.BalanceStatus @@ -164,6 +165,12 @@ private fun internalReduce(action: Action, state: AppState): WalletState { Artwork.SERGIO_CARD_URL } else if (action.card.cardId.startsWith(Artwork.MARTA_CARD_ID)) { Artwork.MARTA_CARD_URL + } else if (newState.twinCardsState?.cardNumber != null) { + when (newState.twinCardsState?.cardNumber) { + TwinCardNumber.First -> Artwork.TWIN_CARD_1 + TwinCardNumber.Second -> Artwork.TWIN_CARD_2 + null -> Artwork.DEFAULT_IMG_URL + } } else { Artwork.DEFAULT_IMG_URL } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt index 36106df1a6..23157af53e 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt @@ -7,7 +7,7 @@ import com.tangem.blockchain.common.Wallet import com.tangem.blockchain.common.address.AddressType import com.tangem.tap.common.entities.Button import com.tangem.tap.common.redux.global.CryptoCurrencyName -import com.tangem.tap.domain.TwinCardNumber +import com.tangem.tap.domain.twins.TwinCardNumber import com.tangem.tap.features.wallet.models.PendingTransaction import com.tangem.tap.features.wallet.ui.BalanceWidgetData import org.rekotlin.StateType @@ -94,6 +94,8 @@ data class Artwork( const val MARTA_CARD_URL = "https://app.tangem.com/cards/card_tg083.png" const val SERGIO_CARD_ID = "BC01" const val MARTA_CARD_ID = "BC02" + const val TWIN_CARD_1 = "https://app.tangem.com/cards/card_tg085.png" + const val TWIN_CARD_2 = "https://app.tangem.com/cards/card_tg086.png" } } @@ -104,8 +106,8 @@ data class TopUpState( ) data class TwinCardsState( - val secondCardId: String?, - val cardNumber: TwinCardNumber?, - val showTwinOnboarding: Boolean, - val isCreatingTwinCardsAllowed: Boolean + val secondCardId: String?, + val cardNumber: TwinCardNumber?, + val showTwinOnboarding: Boolean, + val isCreatingTwinCardsAllowed: Boolean ) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt index cc65bff46d..ce7fec6959 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt @@ -40,6 +40,7 @@ data class TokenData( class BalanceWidget( val fragment: Fragment, val data: BalanceWidgetData, + val isTwinCard: Boolean, ) { fun setup() { @@ -100,8 +101,13 @@ class BalanceWidget( BalanceStatus.EmptyCard -> { fragment.l_balance.hide() fragment.l_balance_error.show() - fragment.tv_error_title.text = fragment.getText(R.string.wallet_error_empty_card) - fragment.tv_error_descriptions.text = fragment.getText(R.string.wallet_error_empty_card_subtitle) + if (isTwinCard) { + fragment.tv_error_title.text = fragment.getText(R.string.wallet_error_empty_twin_card) + fragment.tv_error_descriptions.text = fragment.getText(R.string.wallet_error_empty_twin_card_subtitle) + } else { + fragment.tv_error_title.text = fragment.getText(R.string.wallet_error_empty_card) + fragment.tv_error_descriptions.text = fragment.getText(R.string.wallet_error_empty_card_subtitle) + } } BalanceStatus.NoAccount -> { fragment.l_balance.hide() diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index 4addb8b567..d08504c50b 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -16,7 +16,7 @@ import com.tangem.blockchain.common.address.AddressType import com.tangem.tap.common.extensions.* import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.domain.TwinCardNumber +import com.tangem.tap.domain.twins.TwinCardNumber import com.tangem.tap.features.details.redux.DetailsAction import com.tangem.tap.features.wallet.redux.* import com.tangem.tap.features.wallet.ui.dialogs.AmountToSendDialog @@ -130,7 +130,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber R.string.wallet_button_send - is WalletMainButton.CreateWalletButton -> R.string.wallet_button_create_wallet + is WalletMainButton.CreateWalletButton -> { + if (state.twinCardsState == null) { + R.string.wallet_button_create_wallet + } else { + R.string.wallet_button_create_twin_wallet + } + } } btnConfirm.text = getString(buttonTitle) btnConfirm.isEnabled = state.mainButton.enabled diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/TwinsOnboardingFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/TwinsOnboardingFragment.kt index 799b4e0789..a20b06aa72 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/TwinsOnboardingFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/TwinsOnboardingFragment.kt @@ -5,12 +5,16 @@ import android.view.View import androidx.activity.OnBackPressedCallback import androidx.fragment.app.Fragment import androidx.transition.TransitionInflater +import com.squareup.picasso.Picasso import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.domain.twins.TwinsHelper +import com.tangem.tap.features.wallet.redux.Artwork import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.store import com.tangem.wallet.R import kotlinx.android.synthetic.main.fragment_twin_cards.* +import kotlinx.android.synthetic.main.layout_twin_cards.* class TwinsOnboardingFragment : Fragment(R.layout.fragment_twin_cards) { @@ -31,11 +35,24 @@ class TwinsOnboardingFragment : Fragment(R.layout.fragment_twin_cards) { store.dispatch(WalletAction.TwinsAction.SetOnboardingShown) - val secondCardId = store.state.walletState.twinCardsState?.secondCardId - val text = getString(R.string.twins_onboarding_description_format, secondCardId) + val secondCardId = store.state.walletState.twinCardsState?.secondCardId ?: "" + val secondTwinCardId = TwinsHelper.getTwinCardIdForUser(secondCardId) + val text = getString(R.string.twins_onboarding_description_format, secondTwinCardId) tv_twin_cards_description_1.text = text setOnClickListeners() + + Picasso.get() + .load(Artwork.TWIN_CARD_1) + .placeholder(R.drawable.card_placeholder) + ?.error(R.drawable.card_placeholder) + ?.into(iv_twin_card_1) + + Picasso.get() + .load(Artwork.TWIN_CARD_2) + .placeholder(R.drawable.card_placeholder) + ?.error(R.drawable.card_placeholder) + ?.into(iv_twin_card_2) } private fun setOnClickListeners() { diff --git a/app/src/main/java/com/tangem/tap/network/Retrofit.kt b/app/src/main/java/com/tangem/tap/network/Retrofit.kt index 169baa076b..dd16923173 100644 --- a/app/src/main/java/com/tangem/tap/network/Retrofit.kt +++ b/app/src/main/java/com/tangem/tap/network/Retrofit.kt @@ -12,6 +12,7 @@ import retrofit2.Converter import retrofit2.Retrofit import retrofit2.converter.moshi.MoshiConverterFactory import java.math.BigDecimal +import java.util.concurrent.TimeUnit fun createRetrofitInstance( baseUrl: String, @@ -19,6 +20,7 @@ fun createRetrofitInstance( ): Retrofit { val okHttpBuilder = OkHttpClient.Builder() interceptors.forEach { okHttpBuilder.addInterceptor(it) } + addTimeOuts(okHttpBuilder) if (BuildConfig.DEBUG) okHttpBuilder.addInterceptor(createHttpLoggingInterceptor()) return Retrofit.Builder() @@ -28,6 +30,13 @@ fun createRetrofitInstance( .build() } +private fun addTimeOuts(okHttpBuilder: OkHttpClient.Builder) { + okHttpBuilder.callTimeout(1, TimeUnit.SECONDS) + okHttpBuilder.connectTimeout(20, TimeUnit.SECONDS) + okHttpBuilder.readTimeout(20, TimeUnit.SECONDS) + okHttpBuilder.writeTimeout(20, TimeUnit.SECONDS) +} + fun createMoshiConverterFactory(): Converter.Factory = MoshiConverterFactory.create(createMoshi()) fun createMoshi(): Moshi = Moshi.Builder() diff --git a/app/src/main/res/layout/fragment_details.xml b/app/src/main/res/layout/fragment_details.xml index 65db1c16db..7a99598875 100644 --- a/app/src/main/res/layout/fragment_details.xml +++ b/app/src/main/res/layout/fragment_details.xml @@ -47,7 +47,7 @@ android:id="@+id/tv_card_id_title" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:paddingBottom="14dp" + android:paddingBottom="7dp" android:text="@string/details_row_title_cid" android:textColor="@color/darkGray6" android:textSize="16sp" @@ -59,7 +59,7 @@ android:id="@+id/tv_card_id" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:paddingBottom="14dp" + android:paddingBottom="7dp" android:textColor="@color/darkGray1" android:textSize="16sp" app:layout_constraintEnd_toEndOf="parent" @@ -70,7 +70,8 @@ android:id="@+id/tv_issuer_title" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:paddingBottom="14dp" + android:paddingTop="7dp" + android:paddingBottom="7dp" android:text="@string/details_row_title_issuer" android:textColor="@color/darkGray6" android:textSize="16sp" @@ -81,7 +82,8 @@ android:id="@+id/tv_issuer" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:paddingBottom="14dp" + android:paddingTop="7dp" + android:paddingBottom="7dp" android:textColor="@color/darkGray1" android:textSize="16sp" app:layout_constraintEnd_toEndOf="parent" @@ -92,6 +94,7 @@ android:id="@+id/tv_signed_hashes_title" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:paddingTop="7dp" android:paddingBottom="7dp" android:text="@string/details_row_title_signed_hashes" android:textColor="@color/darkGray6" diff --git a/app/src/main/res/layout/fragment_details_twin_cards.xml b/app/src/main/res/layout/fragment_details_twin_cards.xml index 02b5a83991..7549f32998 100644 --- a/app/src/main/res/layout/fragment_details_twin_cards.xml +++ b/app/src/main/res/layout/fragment_details_twin_cards.xml @@ -38,12 +38,12 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="8dp" - android:gravity="center" + android:gravity="start" android:text="@string/details_twins_recreate_step_format" android:textColor="@color/colorSecondary" android:textSize="20sp" + android:textStyle="bold" app:layout_constraintEnd_toEndOf="@+id/v_step_1" - app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="@id/v_step_1" app:layout_constraintTop_toTopOf="parent" /> diff --git a/app/src/main/res/layout/fragment_details_twin_cards_warning.xml b/app/src/main/res/layout/fragment_details_twin_cards_warning.xml index 35a4fdcf5a..1343fbe116 100644 --- a/app/src/main/res/layout/fragment_details_twin_cards_warning.xml +++ b/app/src/main/res/layout/fragment_details_twin_cards_warning.xml @@ -13,14 +13,22 @@ android:layout_height="match_parent" android:layout_marginBottom="33dp"> + + + + + app:layout_constraintStart_toStartOf="parent"/> + app:layout_constraintTop_toBottomOf="@id/v_payid_divider" + app:layout_constraintBottom_toBottomOf="parent"/> + tools:text="romafdffdfdfn$payid.tangem.com" /> Tap to scan Tap the card - You balance on this wallet is not zero, or you have unconfirmed transactions + Your balance on this wallet is not zero, or you have unconfirmed transactions This is the currently active option diff --git a/app/src/main/res/values/strings_untranslated.xml b/app/src/main/res/values/strings_untranslated.xml index 46a07e670f..ea6b9f43bf 100644 --- a/app/src/main/res/values/strings_untranslated.xml +++ b/app/src/main/res/values/strings_untranslated.xml @@ -4,6 +4,10 @@ Compatibility Legacy + Create twin wallet + Create twin wallet + Generate wallet keys on both cards to start using your Twins + Tangem Twin One wallet. Two cards. This one that you are holding in your hands @@ -14,11 +18,13 @@ this wallet. Card %s of 2 Re-create twin wallet - This action is irreversible. If, after deleting the old wallet, someone sends funds to it, then you will not be able to withdraw them. + This action is irreversible. You will not have access to the old wallet.\nThe wallet re-creation procedure consists of three steps. You must complete it to the end, otherwise you will have to start from the beginning. Re-create wallet Step %s Tap the #%s twin card - If the process of re-creating the wallet gets interrupted in any way, you’ll have to start over. + Preparing card + Creating wallet + The wallet creation procedure consists of three steps. You must complete it to the end, otherwise you will have to start from the beginning. Tap the card #%s You have already started the process of recreating twin wallet. If you interrupt it, you won\'t be able to use your twin cards until you start it again and complete recreating the wallet.