Updated on 2026-08-14
This commit is contained in:
commit
7bb49ef13a
45 changed files with 484 additions and 196 deletions
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"isWalletPayIdEnabled": true,
|
||||
"isTopUpEnabled": true,
|
||||
"isSendingToPayIdEnabled": true,
|
||||
"isCreatingTwinCardsAllowed": false
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"isWalletPayIdEnabled": false,
|
||||
"isTopUpEnabled": false,
|
||||
"isWalletPayIdEnabled": true,
|
||||
"isTopUpEnabled": true,
|
||||
"isSendingToPayIdEnabled": true,
|
||||
"isCreatingTwinCardsAllowed": true
|
||||
}
|
||||
|
|
@ -87,7 +87,8 @@ class MainActivity : AppCompatActivity() {
|
|||
val tag = intent.getParcelableExtra<Tag>(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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 <T : CommandResponse> runTaskAsync(
|
||||
runnable: CardSessionRunnable<T>, cardId: String? = null, initialMessage: Message? = null
|
||||
runnable: CardSessionRunnable<T>, cardId: String? = null, initialMessage: Message? = null,
|
||||
): CompletionResult<T> =
|
||||
withContext(Dispatchers.IO) {
|
||||
suspendCoroutine { continuation ->
|
||||
|
|
@ -76,9 +78,13 @@ class TangemSdkManager(val activity: ComponentActivity) {
|
|||
}
|
||||
|
||||
private suspend fun <T : CommandResponse> runTaskAsyncReturnOnMain(
|
||||
runnable: CardSessionRunnable<T>, cardId: String? = null, initialMessage: Message? = null
|
||||
runnable: CardSessionRunnable<T>, cardId: String? = null, initialMessage: Message? = null,
|
||||
): CompletionResult<T> {
|
||||
val result = runTaskAsync(runnable, cardId, initialMessage)
|
||||
return withContext(Dispatchers.Main) { result }
|
||||
}
|
||||
|
||||
fun changeDisplayedCardIdNumbersCount(card: Card) {
|
||||
tangemSdk.config.cardIdDisplayedNumbersCount = if (card.isTwinCard()) 4 else null
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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<ScanNoteRespons
|
|||
return@run
|
||||
}
|
||||
|
||||
if (card.cardData?.productMask?.contains(Product.TwinCard) == true) {
|
||||
if (card.isTwinCard()) {
|
||||
dealWithTwinCard(card, session, callback)
|
||||
return@run
|
||||
}
|
||||
|
|
@ -75,7 +72,7 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable<ScanNoteRespons
|
|||
when (verifyResult) {
|
||||
is CompletionResult.Success -> {
|
||||
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<ScanNoteRespons
|
|||
card: Card, session: CardSession,
|
||||
callback: (result: CompletionResult<ScanNoteResponse>) -> 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<String>(TlvTag.FileName)
|
||||
if (name != null && name == TwinsHelper.TWIN_FILE_NAME) {
|
||||
val publicKey = decoder.decodeOptional<ByteArray>(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<ScanNoteRespons
|
|||
is CompletionResult.Failure ->
|
||||
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)) {
|
||||
|
|
|
|||
|
|
@ -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<CreateWalletResponse> {
|
||||
override val requiresPin2 = false
|
||||
|
|
@ -18,24 +17,13 @@ class CreateFirstTwinWalletTask : CardSessionRunnable<CreateWalletResponse> {
|
|||
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<CreateWalletResponse>) -> 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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<CreateWalletResponse> {
|
||||
class CreateSecondTwinWalletTask(
|
||||
private val firstPublicKey: String,
|
||||
private val preparingMessage: Message,
|
||||
private val creatingWalletMessage: Message
|
||||
) : CardSessionRunnable<CreateWalletResponse> {
|
||||
override val requiresPin2 = true
|
||||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<CreateWalletResponse>) -> 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<CreateWalletResponse>) -> 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<ScanNoteResponse> {
|
||||
override val requiresPin2 = true
|
||||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<ScanNoteResponse>) -> 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ScanNoteResponse> {
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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<WriteIssuerDataResponse> {
|
||||
override val requiresPin2 = true
|
||||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<WriteIssuerDataResponse>) -> 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<WriteIssuerDataResponse>
|
||||
) -> 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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 -> {
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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?,
|
||||
|
|
|
|||
|
|
@ -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<Det
|
|||
|
||||
|
||||
if (state.cardInfo != null) {
|
||||
tv_card_id.text = state.cardInfo.cardId
|
||||
val cardId = if (state.card?.isTwinCard() == true) {
|
||||
state.card.getTwinCardIdForUser()
|
||||
} else {
|
||||
state.cardInfo.cardId
|
||||
}
|
||||
tv_card_id.text = cardId
|
||||
tv_issuer.text = state.cardInfo.issuer
|
||||
tv_signed_hashes.text = state.cardInfo.signedHashes.toString()
|
||||
}
|
||||
|
||||
tv_signed_hashes.show(state.card?.isTwinCard() != true)
|
||||
tv_signed_hashes_title.show(state.card?.isTwinCard() != true)
|
||||
|
||||
|
||||
tv_disclaimer.setOnClickListener { store.dispatch(DetailsAction.ShowDisclaimer) }
|
||||
|
||||
if (state.createTwinWalletState != null) {
|
||||
|
|
@ -73,7 +85,9 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber<Det
|
|||
tv_erase_wallet.show()
|
||||
tv_erase_wallet.text = getText(R.string.details_row_title_twins_recreate)
|
||||
tv_erase_wallet.setOnClickListener {
|
||||
store.dispatch(DetailsAction.CreateTwinWalletAction.ShowWarning)
|
||||
store.dispatch(DetailsAction.CreateTwinWalletAction.ShowWarning(
|
||||
null, CreateTwinWallet.RecreateWallet
|
||||
))
|
||||
}
|
||||
} else {
|
||||
tv_erase_wallet.hide()
|
||||
|
|
|
|||
|
|
@ -8,12 +8,14 @@ import androidx.core.content.ContextCompat.getColor
|
|||
import androidx.fragment.app.Fragment
|
||||
import androidx.transition.TransitionInflater
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.squareup.picasso.Picasso
|
||||
import com.tangem.Message
|
||||
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.details.redux.DetailsState
|
||||
import com.tangem.tap.features.details.redux.twins.CreateTwinWallet
|
||||
import com.tangem.tap.features.details.redux.twins.CreateTwinWalletStep
|
||||
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.*
|
||||
|
|
@ -56,6 +58,18 @@ class CreateTwinWalletFragment : Fragment(R.layout.fragment_details_twin_cards),
|
|||
toolbar.setNavigationOnClickListener {
|
||||
store.dispatch(DetailsAction.CreateTwinWalletAction.Cancel)
|
||||
}
|
||||
|
||||
Picasso.get()
|
||||
.load(Artwork.TWIN_CARD_1)
|
||||
.placeholder(R.drawable.card_placeholder)
|
||||
?.error(R.drawable.card_placeholder)
|
||||
?.into(iv_card_1)
|
||||
|
||||
Picasso.get()
|
||||
.load(Artwork.TWIN_CARD_2)
|
||||
.placeholder(R.drawable.card_placeholder)
|
||||
?.error(R.drawable.card_placeholder)
|
||||
?.into(iv_card_2)
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -96,14 +110,14 @@ class CreateTwinWalletFragment : Fragment(R.layout.fragment_details_twin_cards),
|
|||
|
||||
tv_step_number.text =
|
||||
getString(R.string.details_twins_recreate_step_format, "2")
|
||||
v_step_1.setBackgroundColor(defaultColor)
|
||||
v_step_1.setBackgroundColor(selectedColor)
|
||||
v_step_2.setBackgroundColor(selectedColor)
|
||||
v_step_3.setBackgroundColor(defaultColor)
|
||||
btn_tap.setOnClickListener {
|
||||
store.dispatch(DetailsAction.CreateTwinWalletAction.LaunchSecondStep(
|
||||
Message(getString(
|
||||
R.string.details_twins_recreate_title_format, twinCardNumberString)
|
||||
)
|
||||
Message(getString(R.string.details_twins_recreate_title_format, twinCardNumberString)),
|
||||
Message(getString(R.string.details_twins_recreate_title_preparing)),
|
||||
Message(getString(R.string.details_twins_recreate_title_creating_wallet)),
|
||||
))
|
||||
}
|
||||
btn_tap.text = getString(R.string.details_twins_recreate_button_format, "2")
|
||||
|
|
@ -114,8 +128,8 @@ class CreateTwinWalletFragment : Fragment(R.layout.fragment_details_twin_cards),
|
|||
|
||||
tv_step_number.text =
|
||||
getString(R.string.details_twins_recreate_step_format, "3")
|
||||
v_step_1.setBackgroundColor(defaultColor)
|
||||
v_step_2.setBackgroundColor(defaultColor)
|
||||
v_step_1.setBackgroundColor(selectedColor)
|
||||
v_step_2.setBackgroundColor(selectedColor)
|
||||
v_step_3.setBackgroundColor(selectedColor)
|
||||
btn_tap.setOnClickListener {
|
||||
store.dispatch(DetailsAction.CreateTwinWalletAction.LaunchThirdStep(
|
||||
|
|
@ -133,16 +147,21 @@ class CreateTwinWalletFragment : Fragment(R.layout.fragment_details_twin_cards),
|
|||
tv_twin_title.text = getString(R.string.details_twins_recreate_title_format, cardNumber)
|
||||
|
||||
if (state.createTwinWalletState?.showAlert == true) {
|
||||
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) { _, _ ->
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -42,6 +42,7 @@ val sendMiddleware: Middleware<AppState> = { 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<TransactionError>, 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))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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<Walle
|
|||
handleDialogs(state.walletDialog)
|
||||
|
||||
l_balance.show()
|
||||
BalanceWidget(this, state.currencyData).setup()
|
||||
BalanceWidget(this, state.currencyData, state.twinCardsState != null).setup()
|
||||
|
||||
if (state.state == ProgressState.Done) {
|
||||
srl_wallet.isRefreshing = false
|
||||
|
|
@ -201,7 +201,13 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
|
|||
|
||||
val buttonTitle = when (state.mainButton) {
|
||||
is WalletMainButton.SendButton -> 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
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
||||
|
|
|
|||
|
|
@ -13,14 +13,22 @@
|
|||
android:layout_height="match_parent"
|
||||
android:layout_marginBottom="33dp">
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/l_twin_cards"
|
||||
android:alpha="0.55"
|
||||
android:background="@color/warning"/>
|
||||
|
||||
<include
|
||||
android:id="@+id/l_twin_cards"
|
||||
layout="@layout/layout_twin_cards_orange"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="-40dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/tv_twin_title"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0" />
|
||||
|
||||
<TextView
|
||||
|
|
|
|||
|
|
@ -12,6 +12,14 @@
|
|||
android:layout_height="match_parent"
|
||||
android:layout_marginBottom="33dp">
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/l_twin_cards"
|
||||
android:alpha="0.22"
|
||||
android:background="@color/darkGray6"/>
|
||||
|
||||
<include
|
||||
android:id="@+id/l_twin_cards"
|
||||
layout="@layout/layout_twin_cards"
|
||||
|
|
@ -21,7 +29,6 @@
|
|||
android:layout_marginBottom="24dp"
|
||||
android:gravity="top"
|
||||
app:layout_constraintBottom_toTopOf="@id/tv_twin_title"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0" />
|
||||
|
||||
<TextView
|
||||
|
|
@ -61,7 +68,7 @@
|
|||
android:textSize="16sp"
|
||||
app:layout_constraintBottom_toTopOf="@id/btn_continue"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline"
|
||||
|
|
|
|||
|
|
@ -152,7 +152,8 @@
|
|||
android:layout_margin="16dp"
|
||||
android:src="@drawable/ic_payid"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/v_payid_divider" />
|
||||
app:layout_constraintTop_toBottomOf="@id/v_payid_divider"
|
||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_create_payid"
|
||||
|
|
@ -172,9 +173,7 @@
|
|||
android:id="@+id/tv_payid_address"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="middle"
|
||||
android:padding="16dp"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="textEnd"
|
||||
android:textColor="@color/darkGray1"
|
||||
android:textSize="13sp"
|
||||
|
|
@ -183,7 +182,7 @@
|
|||
app:layout_constraintHorizontal_bias="1"
|
||||
app:layout_constraintStart_toEndOf="@id/iv_payid_icon"
|
||||
app:layout_constraintTop_toBottomOf="@id/v_payid_divider"
|
||||
tools:text="roman$payid.tangem.com" />
|
||||
tools:text="romafdffdfdfn$payid.tangem.com" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/group_payid"
|
||||
|
|
|
|||
|
|
@ -197,7 +197,7 @@
|
|||
<string name="initial_message_scan_header">Tap to scan</string>
|
||||
<string name="initial_message_tap_header">Tap the card</string>
|
||||
|
||||
<string name="details_notification_erase_wallet_not_possible">You balance on this wallet is not zero, or you have unconfirmed transactions</string>
|
||||
<string name="details_notification_erase_wallet_not_possible">Your balance on this wallet is not zero, or you have unconfirmed transactions</string>
|
||||
<string name="details_notification_security_option_already_active">This is the currently active option</string>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@
|
|||
<string name="wallet_address_chip_compatibility" translatable="false">Compatibility</string>
|
||||
<string name="wallet_address_chip_legacy" translatable="false">Legacy</string>
|
||||
|
||||
<string name="wallet_button_create_twin_wallet" translatable="false">Create twin wallet</string>
|
||||
<string name="wallet_error_empty_twin_card" translatable="false">Create twin wallet</string>
|
||||
<string name="wallet_error_empty_twin_card_subtitle" translatable="false"> Generate wallet keys on both cards to start using your Twins</string>
|
||||
|
||||
<string name="twins_onboarding_title" translatable="false">Tangem Twin</string>
|
||||
<string name="twins_onboarding_subtitle" translatable="false">One wallet. Two cards.</string>
|
||||
<string name="twins_onboarding_description_format" translatable="false">This one that you are holding in your hands
|
||||
|
|
@ -14,11 +18,13 @@ this wallet.
|
|||
<string name="wallet_twins_chip_format" translatable="false">Card %s of 2</string>
|
||||
|
||||
<string name="details_row_title_twins_recreate" translatable="false">Re-create twin wallet</string>
|
||||
<string name="details_twins_recreate_warning" translatable="false">This action is irreversible. If, after deleting the old wallet, someone sends funds to it, then you will not be able to withdraw them.</string>
|
||||
<string name="details_twins_recreate_warning" translatable="false">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.</string>
|
||||
<string name="details_twins_recreate_toolbar" translatable="false">Re-create wallet</string>
|
||||
<string name="details_twins_recreate_step_format" translatable="false">Step %s</string>
|
||||
<string name="details_twins_recreate_title_format" translatable="false">Tap the #%s twin card</string>
|
||||
<string name="details_twins_recreate_subtitle" translatable="false">If the process of re-creating the wallet gets interrupted in any way, you’ll have to start over.</string>
|
||||
<string name="details_twins_recreate_title_preparing" translatable="false">Preparing card</string>
|
||||
<string name="details_twins_recreate_title_creating_wallet" translatable="false">Creating wallet</string>
|
||||
<string name="details_twins_recreate_subtitle" translatable="false">The wallet creation procedure consists of three steps. You must complete it to the end, otherwise you will have to start from the beginning.</string>
|
||||
<string name="details_twins_recreate_button_format" translatable="false">Tap the card #%s</string>
|
||||
<string name="details_twins_recreate_alert" translatable="false">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.</string>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue