Updated on 2026-08-14
This commit is contained in:
commit
7bb49ef13a
45 changed files with 484 additions and 196 deletions
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue