Updated on 2026-08-14
This commit is contained in:
commit
b5adbe0325
80 changed files with 1679 additions and 945 deletions
|
|
@ -7,7 +7,6 @@
|
|||
android:name="android.hardware.nfc"
|
||||
android:required="true" />
|
||||
|
||||
<uses-permission android:name="android.permission.USE_BIOMETRICT" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
|
@ -131,18 +130,6 @@
|
|||
<activity android:name="zendesk.messaging.MessagingActivity"
|
||||
android:theme="@style/ZendeskTheme" />
|
||||
|
||||
<activity android:name="zendesk.support.guide.HelpCenterActivity"
|
||||
android:theme="@style/ZendeskTheme" />
|
||||
|
||||
<activity android:name="zendesk.support.guide.ViewArticleActivity"
|
||||
android:theme="@style/ZendeskTheme" />
|
||||
|
||||
<activity android:name="zendesk.support.request.RequestActivity"
|
||||
android:theme="@style/ZendeskTheme" />
|
||||
|
||||
<activity android:name="zendesk.support.requestlist.RequestListActivity"
|
||||
android:theme="@style/ZendeskTheme" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
|
|
|
|||
|
|
@ -412,12 +412,22 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"id" : "stellar",
|
||||
"symbol" : "XLM",
|
||||
"name" : "Stellar",
|
||||
"networks" : [
|
||||
"id": "stellar",
|
||||
"symbol": "XLM",
|
||||
"name": "Stellar",
|
||||
"networks": [
|
||||
{
|
||||
"networkId" : "stellar/test"
|
||||
"networkId": "stellar/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ethereum-pow-iou",
|
||||
"symbol": "ETHW",
|
||||
"name": "Ethereum PoW",
|
||||
"networks": [
|
||||
{
|
||||
"networkId": "ethereum-pow-iou/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import com.tangem.domain.DomainLayer
|
|||
import com.tangem.domain.common.LogConfig
|
||||
import com.tangem.network.common.MoshiConverter
|
||||
import com.tangem.tap.common.AndroidAssetReader
|
||||
import com.tangem.tap.common.AssetReader
|
||||
import com.tangem.tap.common.analytics.GlobalAnalyticsHandler
|
||||
import com.tangem.tap.common.feedback.AdditionalFeedbackInfo
|
||||
import com.tangem.tap.common.feedback.FeedbackManager
|
||||
|
|
@ -26,7 +27,7 @@ import com.tangem.tap.domain.configurable.config.Config
|
|||
import com.tangem.tap.domain.configurable.config.ConfigManager
|
||||
import com.tangem.tap.domain.configurable.config.FeaturesLocalLoader
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
|
||||
import com.tangem.tap.domain.tokens.CurrenciesRepository
|
||||
import com.tangem.tap.domain.tokens.UserTokensRepository
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectRepository
|
||||
import com.tangem.tap.network.NetworkConnectivity
|
||||
import com.tangem.tap.persistence.PreferencesStorage
|
||||
|
|
@ -41,11 +42,11 @@ val store = Store(
|
|||
)
|
||||
|
||||
lateinit var foregroundActivityObserver: ForegroundActivityObserver
|
||||
|
||||
lateinit var preferencesStorage: PreferencesStorage
|
||||
lateinit var currenciesRepository: CurrenciesRepository
|
||||
lateinit var walletConnectRepository: WalletConnectRepository
|
||||
lateinit var shopService: TangemShopService
|
||||
lateinit var assetReader: AssetReader
|
||||
lateinit var userTokensRepository: UserTokensRepository
|
||||
|
||||
class TapApplication : Application(), ImageLoaderFactory {
|
||||
|
||||
|
|
@ -62,14 +63,19 @@ class TapApplication : Application(), ImageLoaderFactory {
|
|||
DomainLayer.init()
|
||||
NetworkConnectivity.createInstance(store, this)
|
||||
preferencesStorage = PreferencesStorage(this)
|
||||
currenciesRepository = CurrenciesRepository(this, store.state.domainNetworks.tangemTechService)
|
||||
walletConnectRepository = WalletConnectRepository(this)
|
||||
|
||||
val configLoader = FeaturesLocalLoader(AndroidAssetReader(this), MoshiConverter.defaultMoshi())
|
||||
assetReader = AndroidAssetReader(this)
|
||||
val configLoader = FeaturesLocalLoader(assetReader, MoshiConverter.defaultMoshi())
|
||||
initConfigManager(configLoader, ::initWithConfigDependency)
|
||||
initWarningMessagesManager()
|
||||
|
||||
BlockchainSdkRetrofitBuilder.enableNetworkLogging = LogConfig.network.blockchainSdkNetwork
|
||||
|
||||
userTokensRepository = UserTokensRepository.init(
|
||||
context = this,
|
||||
tangemTechService = store.state.domainNetworks.tangemTechService,
|
||||
)
|
||||
}
|
||||
|
||||
override fun newImageLoader(): ImageLoader {
|
||||
|
|
|
|||
20
app/src/main/java/com/tangem/tap/common/FileReader.kt
Normal file
20
app/src/main/java/com/tangem/tap/common/FileReader.kt
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.tap.common
|
||||
|
||||
import android.content.Context
|
||||
import com.tangem.tap.common.extensions.readFile
|
||||
import com.tangem.tap.common.extensions.rewriteFile
|
||||
|
||||
interface FileReader {
|
||||
fun readFile(fileName: String): String
|
||||
fun rewriteFile(content: String, fileName: String)
|
||||
}
|
||||
|
||||
class AndroidFileReader(private val context: Context) : FileReader {
|
||||
override fun readFile(fileName: String): String {
|
||||
return context.readFile(fileName)
|
||||
}
|
||||
|
||||
override fun rewriteFile(content: String, fileName: String) {
|
||||
context.rewriteFile(content, fileName)
|
||||
}
|
||||
}
|
||||
|
|
@ -28,7 +28,10 @@ fun Blockchain.getRoundIconRes(): Int {
|
|||
Blockchain.Dogecoin -> R.drawable.ic_dogecoin_round
|
||||
Blockchain.Tron, Blockchain.TronTestnet -> R.drawable.ic_tron_round
|
||||
Blockchain.Gnosis -> R.drawable.ic_gnosis_round
|
||||
Blockchain.EthereumPow, Blockchain.EthereumPowTestnet -> R.drawable.ic_ethereumpow_round
|
||||
Blockchain.EthereumFair -> R.drawable.ic_ethereumfair_round
|
||||
Blockchain.Polkadot, Blockchain.PolkadotTestnet -> R.drawable.ic_polkadot_round
|
||||
Blockchain.Kusama -> R.drawable.ic_kusama_round
|
||||
Blockchain.Optimism, Blockchain.OptimismTestnet -> R.drawable.ic_optimism
|
||||
Blockchain.Dash -> R.drawable.ic_dash
|
||||
else -> R.drawable.ic_tangem_logo
|
||||
|
|
@ -58,7 +61,10 @@ fun Blockchain.getGreyedOutIconRes(): Int {
|
|||
Blockchain.Dogecoin -> R.drawable.ic_dogecoin_no_color
|
||||
Blockchain.Tron, Blockchain.TronTestnet -> R.drawable.ic_tron_no_color
|
||||
Blockchain.Gnosis -> R.drawable.ic_gnosis_no_color
|
||||
Blockchain.EthereumPow, Blockchain.EthereumPowTestnet -> R.drawable.ic_ethereumpow_no_color
|
||||
Blockchain.EthereumFair -> R.drawable.ic_ethereumfair_no_color
|
||||
Blockchain.Polkadot, Blockchain.PolkadotTestnet -> R.drawable.ic_polkadot_no_color
|
||||
Blockchain.Kusama -> R.drawable.ic_kusama_no_color
|
||||
Blockchain.Optimism, Blockchain.OptimismTestnet -> R.drawable.ic_optimism_no_color
|
||||
Blockchain.Dash -> R.drawable.ic_dash_no_color
|
||||
else -> R.drawable.ic_tangem_logo
|
||||
|
|
|
|||
|
|
@ -7,16 +7,19 @@ 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.blockchain.common.address.Address
|
||||
import com.tangem.common.card.CardWallet
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.tap.common.extensions.stripZeroPlainString
|
||||
|
||||
class AdditionalFeedbackInfo {
|
||||
class EmailWalletInfo(
|
||||
var blockchain: Blockchain = Blockchain.Unknown,
|
||||
var address: String = "",
|
||||
var explorerLink: String = "",
|
||||
var host: String = "",
|
||||
var derivationPath: String = "",
|
||||
var outputsCount: String? = null,
|
||||
var host: String = "",
|
||||
var addresses: String = "",
|
||||
var explorerLink: String = "",
|
||||
)
|
||||
|
||||
var appVersion: String = ""
|
||||
|
|
@ -48,23 +51,14 @@ class AdditionalFeedbackInfo {
|
|||
cardBlockchain = data.walletData?.blockchain ?: ""
|
||||
cardFirmwareVersion = data.card.firmwareVersion.stringValue
|
||||
cardIssuer = data.card.issuer.name
|
||||
signedHashesCount = data.card.wallets
|
||||
.joinToString("; ") { "${it.curve.curve} - ${it.totalSignedHashes}" }
|
||||
signedHashesCount = formatSignedHashes(data.card.wallets)
|
||||
}
|
||||
|
||||
fun setWalletsInfo(walletManagers: List<WalletManager>) {
|
||||
walletsInfo.clear()
|
||||
tokens.clear()
|
||||
walletManagers.forEach { manager ->
|
||||
walletsInfo.add(
|
||||
EmailWalletInfo(
|
||||
blockchain = manager.wallet.blockchain,
|
||||
address = getAddress(manager.wallet),
|
||||
explorerLink = getExploreUri(manager.wallet),
|
||||
host = manager.currentHost,
|
||||
derivationPath = manager.wallet.publicKey.derivationPath?.rawPath ?: ""
|
||||
)
|
||||
)
|
||||
walletsInfo.add(createEmailWalletInfo(manager))
|
||||
if (manager.cardTokens.isNotEmpty()) {
|
||||
tokens[manager.wallet.blockchain] = manager.cardTokens
|
||||
}
|
||||
|
|
@ -72,45 +66,55 @@ class AdditionalFeedbackInfo {
|
|||
}
|
||||
|
||||
fun updateOnSendError(
|
||||
wallet: Wallet,
|
||||
host: String,
|
||||
walletManager: WalletManager,
|
||||
amountToSend: Amount,
|
||||
feeAmount: Amount,
|
||||
destinationAddress: String,
|
||||
) {
|
||||
onSendErrorWalletInfo = EmailWalletInfo(
|
||||
blockchain = wallet.blockchain,
|
||||
address = getAddress(wallet),
|
||||
explorerLink = getExploreUri(wallet),
|
||||
host = host,
|
||||
derivationPath = wallet.publicKey.derivationPath?.rawPath ?: ""
|
||||
)
|
||||
|
||||
onSendErrorWalletInfo = createEmailWalletInfo(walletManager)
|
||||
this.destinationAddress = destinationAddress
|
||||
amount = amountToSend.value?.stripZeroPlainString() ?: "0"
|
||||
fee = feeAmount.value?.stripZeroPlainString() ?: "0"
|
||||
token = if (amountToSend.type is AmountType.Token) amountToSend.currencySymbol else ""
|
||||
}
|
||||
|
||||
private fun getAddress(wallet: Wallet): String {
|
||||
return if (wallet.addresses.size == 1) {
|
||||
wallet.address
|
||||
} else {
|
||||
val addresses = wallet.addresses.joinToString(", ") {
|
||||
"${it.type.javaClass.simpleName} - ${it.value}"
|
||||
}
|
||||
"Multiple address: $addresses"
|
||||
private fun createEmailWalletInfo(walletManager: WalletManager): EmailWalletInfo {
|
||||
return EmailWalletInfo(
|
||||
blockchain = walletManager.wallet.blockchain,
|
||||
derivationPath = walletManager.wallet.publicKey.derivationPath?.rawPath ?: "",
|
||||
outputsCount = walletManager.outputsCount?.toString(),
|
||||
host = walletManager.currentHost,
|
||||
addresses = formatAddresses(walletManager.wallet),
|
||||
explorerLink = formatExploreUrls(walletManager.wallet),
|
||||
)
|
||||
}
|
||||
|
||||
private fun formatSignedHashes(wallets: List<CardWallet>): String {
|
||||
return wallets.joinToString("\n") { "Signed hashes: ${it.curve.curve} - ${it.totalSignedHashes}" }
|
||||
}
|
||||
|
||||
private fun formatAddresses(wallet: Wallet): String {
|
||||
return wallet.formatAddressWith("Multiple address:") {
|
||||
"${it.name} - ${it.value}"
|
||||
}
|
||||
}
|
||||
|
||||
private fun getExploreUri(wallet: Wallet): String {
|
||||
return if (wallet.addresses.size == 1) {
|
||||
wallet.getExploreUrl(wallet.address)
|
||||
} else {
|
||||
val links = wallet.addresses.joinToString(", ") {
|
||||
"${it.type.javaClass.simpleName} - ${wallet.getExploreUrl(it.value)}"
|
||||
}
|
||||
"Multiple explorers links: $links"
|
||||
private fun formatExploreUrls(wallet: Wallet): String {
|
||||
return wallet.formatAddressWith("Multiple explorers links:") {
|
||||
"${it.name} - ${wallet.getExploreUrl(it.value)}"
|
||||
}
|
||||
}
|
||||
|
||||
private fun Wallet.formatAddressWith(with: String, mapAddress: (Address) -> String): String {
|
||||
return if (addresses.size == 1) {
|
||||
getExploreUrl(address)
|
||||
} else {
|
||||
addresses.map { mapAddress(it) }.toMutableList()
|
||||
.apply { add(0, with) }
|
||||
.joinToString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
private val Address.name: String
|
||||
get() = type.javaClass.simpleName
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ package com.tangem.tap.common.feedback
|
|||
import com.tangem.tap.common.extensions.breakLine
|
||||
|
||||
class FeedbackDataBuilder(
|
||||
private val infoHolder: AdditionalFeedbackInfo
|
||||
private val infoHolder: AdditionalFeedbackInfo,
|
||||
) {
|
||||
val builder = StringBuilder()
|
||||
|
||||
|
|
@ -21,7 +21,7 @@ class FeedbackDataBuilder(
|
|||
builder.appendKeyValue("Card ID", infoHolder.cardId)
|
||||
builder.appendKeyValue("Firmware version", infoHolder.cardFirmwareVersion)
|
||||
builder.appendKeyValue("Card Blockchain", infoHolder.cardBlockchain)
|
||||
builder.appendKeyValue("Signed hashes", infoHolder.signedHashesCount)
|
||||
builder.appendKeyValue("", infoHolder.signedHashesCount)
|
||||
return this
|
||||
}
|
||||
|
||||
|
|
@ -29,20 +29,22 @@ class FeedbackDataBuilder(
|
|||
infoHolder.walletsInfo.forEach {
|
||||
builder.appendDelimiter()
|
||||
builder.appendKeyValue("Blockchain", it.blockchain.fullName)
|
||||
builder.appendKeyValue("Host", it.host)
|
||||
builder.appendKeyValue("Wallet address", it.address)
|
||||
builder.appendKeyValue("Derivation path", it.derivationPath)
|
||||
builder.appendKeyValue("Explorer link", it.explorerLink)
|
||||
builder.appendKeyValue("Outputs count", it.outputsCount)
|
||||
|
||||
infoHolder.tokens[it.blockchain]?.let { tokens ->
|
||||
builder.append("Tokens:")
|
||||
breakLine()
|
||||
tokens.forEach { token ->
|
||||
builder.appendKeyValue("Name", token.name)
|
||||
builder.appendKeyValue("ID", token.id ?: "[custom token]")
|
||||
builder.appendKeyValue("Name", token.name)
|
||||
builder.appendKeyValue("Contract address", token.contractAddress)
|
||||
}
|
||||
}
|
||||
|
||||
builder.appendKeyValue("Host", it.host)
|
||||
builder.appendKeyValue("Wallet address", it.addresses)
|
||||
builder.appendKeyValue("Explorer link", it.explorerLink)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
|
@ -55,7 +57,7 @@ class FeedbackDataBuilder(
|
|||
builder.appendKeyValue("Token", infoHolder.token)
|
||||
builder.appendKeyValue("Error", error)
|
||||
builder.appendDelimiter()
|
||||
builder.appendKeyValue("Source address", walletInfo.address)
|
||||
builder.appendKeyValue("Source address", walletInfo.addresses)
|
||||
builder.appendKeyValue("Destination address", infoHolder.destinationAddress)
|
||||
builder.appendKeyValue("Amount", infoHolder.amount)
|
||||
builder.appendKeyValue("Fee", infoHolder.fee)
|
||||
|
|
@ -72,8 +74,10 @@ class FeedbackDataBuilder(
|
|||
fun build(): String = builder.toString()
|
||||
}
|
||||
|
||||
private fun StringBuilder.appendKeyValue(key: String, value: String): StringBuilder {
|
||||
return if (value.isNotBlank()) this.append("$key: $value\n") else this
|
||||
private fun StringBuilder.appendKeyValue(key: String, value: String?): StringBuilder = when {
|
||||
value.isNullOrBlank() -> this
|
||||
key.isBlank() -> this.append("$value\n")
|
||||
else -> this.append("$key: $value\n")
|
||||
}
|
||||
|
||||
private fun StringBuilder.appendDelimiter(): StringBuilder = append("----------\n")
|
||||
|
|
@ -12,7 +12,6 @@ import com.tangem.tap.common.extensions.dispatchDialogShow
|
|||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.currenciesRepository
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
|
||||
import com.tangem.tap.features.send.redux.SendAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
|
|
@ -25,6 +24,7 @@ import com.tangem.tap.preferencesStorage
|
|||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import com.tangem.tap.userTokensRepository
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.DispatchFunction
|
||||
|
|
@ -141,7 +141,7 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
|
|||
scope.launch {
|
||||
val result = tangemSdkManager.scanProduct(
|
||||
store.state.globalState.analyticsHandlers,
|
||||
currenciesRepository,
|
||||
userTokensRepository,
|
||||
action.additionalBlockchainsToDerive,
|
||||
action.messageResId,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ import com.tangem.tap.common.analytics.AnalyticsParam
|
|||
import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask
|
||||
import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask
|
||||
import com.tangem.tap.domain.tasks.product.ScanProductTask
|
||||
import com.tangem.tap.domain.tokens.CurrenciesRepository
|
||||
import com.tangem.tap.domain.tokens.UserTokensRepository
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
|
@ -45,7 +45,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
|
|||
|
||||
suspend fun scanProduct(
|
||||
analyticsHandler: AnalyticsHandler?,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
userTokensRepository: UserTokensRepository,
|
||||
additionalBlockchainsToDerive: Collection<Blockchain>? = null,
|
||||
messageRes: Int? = null,
|
||||
): CompletionResult<ScanResponse> {
|
||||
|
|
@ -53,8 +53,8 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
|
|||
|
||||
val message = Message(context.getString(messageRes ?: R.string.initial_message_scan_header))
|
||||
return runTaskAsyncReturnOnMain(
|
||||
runnable = ScanProductTask(null, currenciesRepository, additionalBlockchainsToDerive),
|
||||
cardId = null, initialMessage = message
|
||||
runnable = ScanProductTask(null, userTokensRepository, additionalBlockchainsToDerive),
|
||||
cardId = null, initialMessage = message,
|
||||
).also { sendScanResultsToAnalytics(analyticsHandler, it) }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.tap.domain
|
|||
|
||||
import androidx.annotation.StringRes
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.network.api.tangemTech.TangemTechError
|
||||
import com.tangem.wallet.R
|
||||
|
||||
interface TapErrors
|
||||
|
|
@ -41,7 +42,7 @@ sealed class TapError(
|
|||
|
||||
data class UnsupportedState(
|
||||
val stateError: String,
|
||||
val customMessage: String = "Unsupported state:"
|
||||
val customMessage: String = "Unsupported state:",
|
||||
) : TapError(R.string.common_custom_string, listOf("$customMessage $stateError"))
|
||||
|
||||
sealed class WalletManager {
|
||||
|
|
@ -52,9 +53,14 @@ sealed class TapError(
|
|||
object BlockchainIsUnreachableTryLater : TapError(R.string.wallet_balance_blockchain_unreachable_try_later)
|
||||
}
|
||||
|
||||
sealed class WalletConnect {
|
||||
object UnsupportedDapp : TapError(R.string.wallet_connect_error_unsupported_dapp)
|
||||
object UnsupportedLink : TapError(R.string.wallet_connect_error_failed_to_connect)
|
||||
}
|
||||
|
||||
data class ValidateTransactionErrors(
|
||||
override val errorList: List<TapError>,
|
||||
override val builder: (List<String>) -> String
|
||||
override val builder: (List<String>) -> String,
|
||||
) : TapError(-1), MultiMessageError
|
||||
}
|
||||
|
||||
|
|
@ -67,7 +73,6 @@ sealed class TapSdkError(override val messageResId: Int?) : Throwable(), TangemE
|
|||
object ScanPrimaryCard : TapSdkError(R.string.saltpay_backup_warning)
|
||||
}
|
||||
|
||||
|
||||
fun TapErrors.assembleErrors(): MutableList<Pair<Int, List<Any>?>> {
|
||||
val idList = mutableListOf<Pair<Int, List<Any>?>>()
|
||||
when (this) {
|
||||
|
|
@ -75,4 +80,13 @@ fun TapErrors.assembleErrors(): MutableList<Pair<Int, List<Any>?>> {
|
|||
is TapError -> idList.add(Pair(this.messageResource, this.args))
|
||||
}
|
||||
return idList
|
||||
}
|
||||
}
|
||||
|
||||
fun TangemTechError.toTapError(): TapError {
|
||||
return when (this.code) {
|
||||
404 -> NoDataError(this.description)
|
||||
else -> TapError.CustomError(customMessage = this.description)
|
||||
}
|
||||
}
|
||||
|
||||
class NoDataError(message: String) : TapError.CustomError(customMessage = message)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import com.tangem.domain.common.extensions.withMainContext
|
|||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.safeUpdate
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.currenciesRepository
|
||||
import com.tangem.tap.domain.configurable.config.ConfigManager
|
||||
import com.tangem.tap.domain.extensions.isMultiwalletAllowed
|
||||
import com.tangem.tap.domain.extensions.makePrimaryWalletManager
|
||||
|
|
@ -24,17 +23,17 @@ import com.tangem.tap.domain.extensions.makeWalletManagersForApp
|
|||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.demo.isDemoCard
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.features.wallet.models.toBlockchainNetworks
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.network.NetworkConnectivity
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.userTokensRepository
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
||||
class TapWalletManager {
|
||||
val walletManagerFactory: WalletManagerFactory
|
||||
by lazy { WalletManagerFactory(blockchainSdkConfig) }
|
||||
|
||||
by lazy { WalletManagerFactory(blockchainSdkConfig) }
|
||||
val rates: RatesRepository = RatesRepository()
|
||||
|
||||
private val blockchainSdkConfig by lazy {
|
||||
|
|
@ -65,16 +64,16 @@ class TapWalletManager {
|
|||
WalletAction.LoadWallet.NoAccount(
|
||||
walletManager.wallet,
|
||||
blockchainNetwork,
|
||||
(result.error as TapError.WalletManager.NoAccountError).customMessage
|
||||
)
|
||||
(result.error as TapError.WalletManager.NoAccountError).customMessage,
|
||||
),
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
dispatchOnMain(
|
||||
WalletAction.LoadWallet.Failure(
|
||||
walletManager.wallet,
|
||||
result.error.localizedMessage
|
||||
)
|
||||
result.error.localizedMessage,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -138,24 +137,23 @@ class TapWalletManager {
|
|||
dispatchOnMain(WalletAction.LoadFiatRate())
|
||||
}
|
||||
|
||||
private suspend fun loadMultiWalletData(
|
||||
scanResponse: ScanResponse
|
||||
) {
|
||||
val savedCurrencies = currenciesRepository.loadSavedCurrencies(
|
||||
scanResponse.card.cardId, scanResponse.card.settings.isHDWalletAllowed
|
||||
)
|
||||
if (savedCurrencies.isEmpty()) return
|
||||
private suspend fun loadMultiWalletData(scanResponse: ScanResponse) {
|
||||
loadUserCurrencies(scanResponse, walletManagerFactory)
|
||||
}
|
||||
|
||||
val walletManagers =
|
||||
walletManagerFactory.makeWalletManagersForApp(scanResponse, savedCurrencies)
|
||||
dispatchOnMain(
|
||||
WalletAction.MultiWallet.AddBlockchains(savedCurrencies, walletManagers),
|
||||
)
|
||||
savedCurrencies.map {
|
||||
private fun checkIfDerivationsAreMissing(blockchainNetworks: List<BlockchainNetwork>, scanResponse: ScanResponse) {
|
||||
blockchainNetworks.map {
|
||||
if (it.tokens.isNotEmpty()) {
|
||||
dispatchOnMain(WalletAction.MultiWallet.AddTokens(it.tokens, it))
|
||||
WalletAction.MultiWallet.AddTokens(it.tokens, it, false)
|
||||
}
|
||||
}
|
||||
val missingDerivations = blockchainNetworks
|
||||
.filter {
|
||||
it.derivationPath != null && !scanResponse.hasDerivation(it.blockchain, it.derivationPath)
|
||||
}
|
||||
if (missingDerivations.isNotEmpty()) {
|
||||
store.dispatch(WalletAction.MultiWallet.AddMissingDerivations(missingDerivations))
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadSingleWalletData(data: ScanResponse) {
|
||||
|
|
@ -163,7 +161,6 @@ class TapWalletManager {
|
|||
val primaryWalletManager = walletManagerFactory.makePrimaryWalletManager(data)
|
||||
|
||||
if (blockchain != Blockchain.Unknown && primaryWalletManager != null) {
|
||||
val blockchainNetwork = BlockchainNetwork.fromWalletManager(primaryWalletManager)
|
||||
val primaryToken = data.getPrimaryToken()
|
||||
|
||||
dispatchOnMain(WalletAction.MultiWallet.SetPrimaryBlockchain(blockchain))
|
||||
|
|
@ -173,14 +170,43 @@ class TapWalletManager {
|
|||
}
|
||||
dispatchOnMain(
|
||||
WalletAction.MultiWallet.AddBlockchains(
|
||||
listOf(blockchainNetwork),
|
||||
listOf(primaryWalletManager),
|
||||
blockchains = listOf(BlockchainNetwork.fromWalletManager(primaryWalletManager)),
|
||||
walletManagers = listOf(primaryWalletManager),
|
||||
save = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadUserCurrencies(scanResponse: ScanResponse, walletManagerFactory: WalletManagerFactory) {
|
||||
val userTokens = userTokensRepository.getUserTokens(scanResponse.card)
|
||||
withMainContext {
|
||||
val blockchainNetworks = userTokens.toBlockchainNetworks()
|
||||
val walletManagers = walletManagerFactory.makeWalletManagersForApp(scanResponse, userTokens)
|
||||
store.dispatch(
|
||||
WalletAction.MultiWallet.AddBlockchains(
|
||||
blockchains = blockchainNetworks,
|
||||
walletManagers = walletManagers,
|
||||
save = false,
|
||||
),
|
||||
)
|
||||
|
||||
blockchainNetworks.filter { it.tokens.isNotEmpty() }
|
||||
.map {
|
||||
store.dispatch(
|
||||
WalletAction.MultiWallet.AddTokens(
|
||||
tokens = it.tokens,
|
||||
blockchain = it,
|
||||
save = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
checkIfDerivationsAreMissing(blockchainNetworks, scanResponse)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun reloadData(data: ScanResponse) {
|
||||
loadUserCurrencies(data, walletManagerFactory)
|
||||
withContext(Dispatchers.Main) {
|
||||
getActionIfUnknownBlockchainOrEmptyWallet(data)?.let {
|
||||
store.dispatch(it)
|
||||
|
|
|
|||
|
|
@ -103,9 +103,11 @@ fun WalletManagerFactory.makeWalletManagerForApp(
|
|||
}
|
||||
|
||||
fun WalletManagerFactory.makeWalletManagersForApp(
|
||||
scanResponse: ScanResponse, blockchains: List<BlockchainNetwork>,
|
||||
scanResponse: ScanResponse, blockchains: List<Currency>,
|
||||
): List<WalletManager> {
|
||||
return blockchains.mapNotNull { this.makeWalletManagerForApp(scanResponse, it) }
|
||||
return blockchains
|
||||
.filter { it.isBlockchain() }
|
||||
.mapNotNull { this.makeWalletManagerForApp(scanResponse, it) }
|
||||
}
|
||||
|
||||
fun WalletManagerFactory.makePrimaryWalletManager(
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import com.tangem.domain.common.TapWorkarounds.isSaltPay
|
|||
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
|
||||
import com.tangem.domain.common.TwinsHelper
|
||||
import com.tangem.domain.common.isTangemTwins
|
||||
import com.tangem.domain.common.productType
|
||||
import com.tangem.operations.PreflightReadMode
|
||||
import com.tangem.operations.PreflightReadTask
|
||||
import com.tangem.operations.ScanTask
|
||||
|
|
@ -32,19 +31,17 @@ import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand
|
|||
import com.tangem.tap.domain.TapSdkError
|
||||
import com.tangem.tap.domain.extensions.getPrimaryCurve
|
||||
import com.tangem.tap.domain.extensions.getSingleWallet
|
||||
import com.tangem.tap.domain.tokens.UserTokensRepository
|
||||
import com.tangem.tap.domain.extensions.hasNoWallets
|
||||
import com.tangem.tap.domain.extensions.isHdWalletAllowedByApp
|
||||
import com.tangem.tap.domain.extensions.isMultiwalletAllowed
|
||||
import com.tangem.tap.domain.tokens.CurrenciesRepository
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.preferencesStorage
|
||||
import com.tangem.tap.scope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ScanProductTask(
|
||||
val card: Card? = null,
|
||||
private val currenciesRepository: CurrenciesRepository?,
|
||||
private val additionalBlockchainsToDerive: Collection<Blockchain>? = null
|
||||
private val userTokensRepository: UserTokensRepository?,
|
||||
private val additionalBlockchainsToDerive: Collection<Blockchain>? = null,
|
||||
) : CardSessionRunnable<ScanResponse> {
|
||||
|
||||
override fun run(
|
||||
|
|
@ -64,7 +61,7 @@ class ScanProductTask(
|
|||
|
||||
val commandProcessor = when {
|
||||
card.isTangemTwins() -> ScanTwinProcessor()
|
||||
else -> ScanWalletProcessor(currenciesRepository, additionalBlockchainsToDerive)
|
||||
else -> ScanWalletProcessor(userTokensRepository, additionalBlockchainsToDerive)
|
||||
}
|
||||
commandProcessor.proceed(card, session) { processorResult ->
|
||||
when (processorResult) {
|
||||
|
|
@ -95,8 +92,8 @@ class ScanProductTask(
|
|||
}
|
||||
|
||||
private class ScanWalletProcessor(
|
||||
private val currenciesRepository: CurrenciesRepository?,
|
||||
private val additionalBlockchainsToDerive: Collection<Blockchain>? = null
|
||||
private val userTokensRepository: UserTokensRepository?,
|
||||
private val additionalBlockchainsToDerive: Collection<Blockchain>? = null,
|
||||
) : ProductCommandProcessor<ScanResponse> {
|
||||
|
||||
var primaryCard: PrimaryCard? = null
|
||||
|
|
@ -169,47 +166,41 @@ private class ScanWalletProcessor(
|
|||
session: CardSession,
|
||||
callback: (result: CompletionResult<ScanResponse>) -> Unit,
|
||||
) {
|
||||
scope.launch {
|
||||
val derivations = collectDerivations(card)
|
||||
if (derivations.isEmpty() || !card.isHdWalletAllowedByApp) {
|
||||
callback(
|
||||
CompletionResult.Success(
|
||||
ScanResponse(
|
||||
card = card,
|
||||
productType = card.productType,
|
||||
walletData = session.environment.walletData,
|
||||
primaryCard = primaryCard,
|
||||
),
|
||||
val derivations = collectDerivations(card)
|
||||
if (derivations.isEmpty() || !card.settings.isHDWalletAllowed) {
|
||||
callback(
|
||||
CompletionResult.Success(
|
||||
ScanResponse(
|
||||
card = card,
|
||||
productType = ProductType.Wallet,
|
||||
walletData = session.environment.walletData,
|
||||
primaryCard = primaryCard,
|
||||
),
|
||||
)
|
||||
return@launch
|
||||
}
|
||||
),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
DeriveMultipleWalletPublicKeysTask(derivations).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val response = ScanResponse(
|
||||
card = card,
|
||||
productType = card.productType,
|
||||
walletData = session.environment.walletData,
|
||||
derivedKeys = result.data.entries,
|
||||
primaryCard = primaryCard,
|
||||
)
|
||||
callback(CompletionResult.Success(response))
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
DeriveMultipleWalletPublicKeysTask(derivations).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val response = ScanResponse(
|
||||
card = card,
|
||||
productType = ProductType.Wallet,
|
||||
walletData = session.environment.walletData,
|
||||
derivedKeys = result.data.entries,
|
||||
primaryCard = primaryCard,
|
||||
)
|
||||
callback(CompletionResult.Success(response))
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getBlockchainsToDerive(card: Card): List<BlockchainNetwork> {
|
||||
val currenciesRepository = currenciesRepository ?: return emptyList()
|
||||
|
||||
val cardCurrencies = currenciesRepository
|
||||
.loadSavedCurrencies(card.cardId, card.isHdWalletAllowedByApp).toMutableList()
|
||||
|
||||
val blockchainsToDerive = cardCurrencies.ifEmpty {
|
||||
private fun getBlockchainsToDerive(card: Card): List<BlockchainNetwork> {
|
||||
val userTokensRepository = userTokensRepository ?: return emptyList()
|
||||
val blockchainsToDerive = userTokensRepository.loadBlockchainsToDerive(card).toMutableList().ifEmpty {
|
||||
mutableListOf(
|
||||
BlockchainNetwork(Blockchain.Bitcoin, card),
|
||||
BlockchainNetwork(Blockchain.Ethereum, card),
|
||||
|
|
@ -221,7 +212,7 @@ private class ScanWalletProcessor(
|
|||
listOf(
|
||||
BlockchainNetwork(Blockchain.Ethereum, card),
|
||||
BlockchainNetwork(Blockchain.EthereumTestnet, card),
|
||||
)
|
||||
),
|
||||
)
|
||||
}
|
||||
if (additionalBlockchainsToDerive != null) {
|
||||
|
|
@ -241,13 +232,12 @@ private class ScanWalletProcessor(
|
|||
return blockchainsToDerive.distinct()
|
||||
}
|
||||
|
||||
private suspend fun collectDerivations(card: Card): Map<ByteArrayKey, List<DerivationPath>> {
|
||||
private fun collectDerivations(card: Card): Map<ByteArrayKey, List<DerivationPath>> {
|
||||
val blockchains = getBlockchainsToDerive(card)
|
||||
val derivations = mutableMapOf<ByteArrayKey, List<DerivationPath>>()
|
||||
|
||||
blockchains.forEach { blockchain ->
|
||||
val curve = blockchain.blockchain.getPrimaryCurve()
|
||||
|
||||
val wallet = card.wallets.firstOrNull { it.curve == curve } ?: return@forEach
|
||||
if (wallet.chainCode == null) return@forEach
|
||||
|
||||
|
|
|
|||
|
|
@ -1,247 +1,12 @@
|
|||
package com.tangem.tap.domain.tokens
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.Types
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.DerivationStyle
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.common.card.FirmwareVersion
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.domain.common.extensions.getTokens
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
import com.tangem.network.api.tangemTech.TangemTechService
|
||||
import com.tangem.network.common.MoshiConverter
|
||||
import com.tangem.tap.common.extensions.appendIf
|
||||
import com.tangem.tap.common.extensions.readJsonFileToString
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.domain.tokens.models.ObsoleteTokenDao
|
||||
import com.tangem.tap.domain.tokens.models.TokenDao
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import timber.log.Timber
|
||||
import java.util.*
|
||||
|
||||
class CurrenciesRepository(
|
||||
private val context: Application,
|
||||
private val tangemNetworkService: TangemTechService
|
||||
) {
|
||||
|
||||
private val moshi = MoshiConverter.defaultMoshi()
|
||||
private val blockchainsAdapter: JsonAdapter<List<Blockchain>> = moshi.adapter(
|
||||
Types.newParameterizedType(List::class.java, Blockchain::class.java)
|
||||
)
|
||||
private val tokensAdapter: JsonAdapter<List<TokenDao>> = moshi.adapter(
|
||||
Types.newParameterizedType(List::class.java, TokenDao::class.java)
|
||||
)
|
||||
private val obsoleteTokensAdapter: JsonAdapter<List<ObsoleteTokenDao>> = moshi.adapter(
|
||||
Types.newParameterizedType(List::class.java, ObsoleteTokenDao::class.java)
|
||||
)
|
||||
private val currenciesAdapter: JsonAdapter<CurrenciesFromJson> =
|
||||
moshi.adapter(CurrenciesFromJson::class.java)
|
||||
|
||||
private val blockchainNetworkAdapter: JsonAdapter<List<BlockchainNetwork>> =
|
||||
moshi.adapter(Types.newParameterizedType(List::class.java, BlockchainNetwork::class.java))
|
||||
|
||||
fun saveUpdatedCurrency(cardId: String, blockchainNetwork: BlockchainNetwork) {
|
||||
var changed = false
|
||||
val currencies = loadSavedCurrenciesWithoutMigration(cardId).map {
|
||||
if (it == blockchainNetwork) {
|
||||
changed = true
|
||||
blockchainNetwork
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
val updatedCurrencies = if (changed) currencies else currencies + blockchainNetwork
|
||||
saveCurrencies(cardId, updatedCurrencies.distinct())
|
||||
}
|
||||
|
||||
fun removeToken(cardId: String, token: Token, blockchainNetwork: BlockchainNetwork) {
|
||||
val currencies = loadSavedCurrenciesWithoutMigration(cardId).map {
|
||||
if (it == blockchainNetwork) {
|
||||
it.copy(tokens = it.tokens.filterNot { it == token })
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
saveCurrencies(cardId, currencies)
|
||||
}
|
||||
|
||||
fun removeBlockchain(cardId: String, blockchainNetwork: BlockchainNetwork) {
|
||||
val currencies = loadSavedCurrenciesWithoutMigration(cardId)
|
||||
.filterNot { it == blockchainNetwork }
|
||||
saveCurrencies(cardId, currencies)
|
||||
}
|
||||
|
||||
fun removeCurrencies(cardId: String) {
|
||||
saveCurrencies(cardId, emptyList())
|
||||
}
|
||||
|
||||
@Deprecated("Use BlockchainNetwork instead")
|
||||
private fun loadSavedTokens(cardId: String): List<TokenDao> {
|
||||
val json = try {
|
||||
context.readFileText(getFileNameForTokens(cardId))
|
||||
} catch (exception: Exception) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
return try {
|
||||
tokensAdapter.fromJson(json) ?: emptyList()
|
||||
} catch (exception: Exception) {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Use BlockchainNetwork instead")
|
||||
private fun loadSavedBlockchains(cardId: String): List<Blockchain> {
|
||||
return try {
|
||||
val json = context.readFileText(getFileNameForBlockchains(cardId))
|
||||
blockchainsAdapter.fromJson(json)?.distinct() ?: emptyList()
|
||||
} catch (exception: Exception) {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun loadSavedCurrencies(
|
||||
cardId: String,
|
||||
isHdWalletSupported: Boolean = false
|
||||
): List<BlockchainNetwork> {
|
||||
if (DemoHelper.isDemoCardId(cardId)) {
|
||||
return loadDemoCurrencies()
|
||||
}
|
||||
return try {
|
||||
val json = context.readFileText(getFileNameForBlockchains(cardId))
|
||||
blockchainNetworkAdapter.fromJson(json)?.distinct() ?: emptyList()
|
||||
} catch (exception: Exception) {
|
||||
tryToLoadPreviousFormatAndMigrate(cardId, isHdWalletSupported)
|
||||
}
|
||||
}
|
||||
|
||||
fun loadSavedCurrenciesWithoutMigration(
|
||||
cardId: String,
|
||||
): List<BlockchainNetwork> {
|
||||
if (DemoHelper.isDemoCardId(cardId)) {
|
||||
return loadDemoCurrencies()
|
||||
}
|
||||
return try {
|
||||
val json = context.readFileText(getFileNameForBlockchains(cardId))
|
||||
blockchainNetworkAdapter.fromJson(json)?.distinct() ?: emptyList()
|
||||
} catch (exception: Exception) {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadDemoCurrencies(): List<BlockchainNetwork> {
|
||||
return DemoHelper.config.demoBlockchains.map {
|
||||
BlockchainNetwork(
|
||||
blockchain = it,
|
||||
derivationPath = it.derivationPath(DerivationStyle.LEGACY)?.rawPath,
|
||||
tokens = emptyList()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun tryToLoadPreviousFormatAndMigrate(
|
||||
cardId: String,
|
||||
isHdWalletSupported: Boolean = false
|
||||
): List<BlockchainNetwork> {
|
||||
return try {
|
||||
loadSavedCurrenciesOldWay(
|
||||
cardId,
|
||||
isHdWalletSupported
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadSavedCurrenciesOldWay(
|
||||
cardId: String, isHdWalletSupported: Boolean = false
|
||||
): List<BlockchainNetwork> {
|
||||
val blockchains = loadSavedBlockchains(cardId)
|
||||
val tokens = loadSavedTokens(cardId)
|
||||
val ids = getTokensIds(tokens)
|
||||
val derivationStyle = if (isHdWalletSupported) DerivationStyle.LEGACY else null
|
||||
val blockchainNetworks = blockchains.map { blockchain ->
|
||||
BlockchainNetwork(
|
||||
blockchain = blockchain,
|
||||
derivationPath = blockchain.derivationPath(derivationStyle)?.rawPath,
|
||||
tokens = tokens
|
||||
.filter { it.blockchainDao.toBlockchain() == blockchain }
|
||||
.map {
|
||||
val token = it.toToken()
|
||||
token.copy(id = ids[token.contractAddress])
|
||||
}
|
||||
)
|
||||
}
|
||||
saveCurrencies(cardId, blockchainNetworks) // migrate saved currencies
|
||||
return blockchainNetworks
|
||||
}
|
||||
|
||||
private suspend fun getTokensIds(tokens: List<TokenDao>): Map<String, String> = coroutineScope {
|
||||
tokens.map {
|
||||
async {
|
||||
tangemNetworkService.getTokens(
|
||||
contractAddress = it.contractAddress,
|
||||
networkId = it.blockchainDao.toBlockchain().toNetworkId(),
|
||||
active = true,
|
||||
)
|
||||
}
|
||||
}.map { it.await() }
|
||||
.map { (it as? Result.Success)?.data?.coins?.firstOrNull()?.id }
|
||||
.mapIndexedNotNull { index, id ->
|
||||
if (id == null) null else tokens[index].contractAddress to id
|
||||
}.toMap()
|
||||
}
|
||||
|
||||
fun saveCurrencies(cardId: String, currencies: List<BlockchainNetwork>) {
|
||||
val json = blockchainNetworkAdapter.toJson(currencies)
|
||||
context.rewriteFile(json, getFileNameForBlockchains(cardId))
|
||||
}
|
||||
|
||||
private fun Context.readFileText(fileName: String): String =
|
||||
this.openFileInput(fileName).bufferedReader().readText()
|
||||
|
||||
private fun Context.rewriteFile(content: String, fileName: String) {
|
||||
this.openFileOutput(fileName, Context.MODE_PRIVATE).use {
|
||||
it.write(content.toByteArray(), 0, content.length)
|
||||
}
|
||||
}
|
||||
|
||||
fun getTestnetCoins(): List<Currency> {
|
||||
val json = context.assets.readJsonFileToString(FILE_NAME_TESTNET_COINS)
|
||||
return currenciesAdapter.fromJson(json)!!.coins
|
||||
.map { Currency.fromJsonObject(it) }
|
||||
}
|
||||
|
||||
private fun loadTokensJson(blockchain: Blockchain): String? {
|
||||
val fileName = getFileName(blockchain)
|
||||
return try {
|
||||
context.assets.readJsonFileToString(fileName)
|
||||
} catch (ex: Exception) {
|
||||
Timber.e(ex, "Tokens with the file name %s not found", fileName)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFileName(blockchain: Blockchain): String {
|
||||
return StringBuilder().apply {
|
||||
append(blockchain.id.lowercase(Locale.getDefault()).replace("/test", ""))
|
||||
append("_tokens")
|
||||
appendIf("_testnet") { blockchain.isTestnet() }
|
||||
}.toString()
|
||||
}
|
||||
|
||||
private fun fromJsonToTokensDao(tokenJson: String, blockchain: Blockchain): List<TokenDao> {
|
||||
return obsoleteTokensAdapter.fromJson(tokenJson)!!.map { it.toTokenDao(blockchain) }
|
||||
}
|
||||
|
||||
object CurrenciesRepository {
|
||||
fun getBlockchains(
|
||||
cardFirmware: FirmwareVersion,
|
||||
isTestNet: Boolean = false
|
||||
isTestNet: Boolean = false,
|
||||
): List<Blockchain> {
|
||||
val blockchains = if (cardFirmware < FirmwareVersion.MultiWalletAvailable) {
|
||||
Blockchain.secp256k1Blockchains(isTestNet)
|
||||
|
|
@ -261,21 +26,6 @@ class CurrenciesRepository(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val FILE_NAME_PREFIX_TOKENS = "tokens"
|
||||
private const val FILE_NAME_PREFIX_BLOCKCHAINS = "blockchains"
|
||||
private const val FILE_NAME_TESTNET_COINS = "testnet_tokens"
|
||||
|
||||
fun getFileNameForTokens(cardId: String): String = "${FILE_NAME_PREFIX_TOKENS}_$cardId"
|
||||
fun getFileNameForBlockchains(cardId: String): String =
|
||||
"${FILE_NAME_PREFIX_BLOCKCHAINS}_$cardId"
|
||||
}
|
||||
}
|
||||
|
||||
fun Blockchain.getTokensName(): String {
|
||||
return when (this) {
|
||||
Blockchain.Fantom -> "Fantom Opera"
|
||||
else -> this.fullName
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,29 +1,36 @@
|
|||
package com.tangem.tap.domain.tokens
|
||||
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.domain.common.extensions.getListOfCoins
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
import com.tangem.network.api.tangemTech.CoinsResponse
|
||||
import com.tangem.network.api.tangemTech.TangemTechService
|
||||
import com.tangem.network.common.MoshiConverter
|
||||
import com.tangem.tap.common.AssetReader
|
||||
|
||||
class LoadAvailableCoinsService(
|
||||
private val networkService: TangemTechService,
|
||||
private val currenciesRepository: CurrenciesRepository
|
||||
private val assetReader: AssetReader,
|
||||
) {
|
||||
private val moshi: Moshi by lazy { MoshiConverter.defaultMoshi() }
|
||||
private val currenciesAdapter: JsonAdapter<CurrenciesFromJson> =
|
||||
moshi.adapter(CurrenciesFromJson::class.java)
|
||||
|
||||
suspend fun getSupportedTokens(
|
||||
isTestNet: Boolean = false,
|
||||
supportedBlockchains: List<Blockchain>,
|
||||
page: Int,
|
||||
searchInput: String? = null
|
||||
searchInput: String? = null,
|
||||
): Result<LoadedCoins> {
|
||||
if (isTestNet) {
|
||||
return Result.Success(
|
||||
LoadedCoins(
|
||||
currencies = currenciesRepository.getTestnetCoins().filter(searchInput),
|
||||
currencies = getTestnetCoins().filter(searchInput),
|
||||
moreAvailable = false,
|
||||
)
|
||||
),
|
||||
)
|
||||
}
|
||||
val offset = page * LOAD_PER_PAGE
|
||||
|
|
@ -56,21 +63,28 @@ class LoadAvailableCoinsService(
|
|||
active = true,
|
||||
offset = offset,
|
||||
limit = LOAD_PER_PAGE,
|
||||
searchText = searchInput
|
||||
searchText = searchInput,
|
||||
)
|
||||
}
|
||||
|
||||
fun getTestnetCoins(): List<Currency> {
|
||||
val json = assetReader.readAssetAsString(FILE_NAME_TESTNET_COINS)
|
||||
return currenciesAdapter.fromJson(json)!!.coins
|
||||
.map { Currency.fromJsonObject(it) }
|
||||
}
|
||||
|
||||
private fun List<Currency>.filter(searchInput: String?): List<Currency> {
|
||||
if (searchInput.isNullOrBlank()) return this
|
||||
|
||||
return filter{
|
||||
return filter {
|
||||
it.symbol.contains(searchInput, ignoreCase = true) ||
|
||||
it.name.contains(searchInput, ignoreCase = true)
|
||||
it.name.contains(searchInput, ignoreCase = true)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val LOAD_PER_PAGE = 100
|
||||
private const val FILE_NAME_TESTNET_COINS = "testnet_tokens"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,130 @@
|
|||
package com.tangem.tap.domain.tokens
|
||||
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.Types
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.DerivationStyle
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.domain.common.extensions.getTokens
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
import com.tangem.network.api.tangemTech.TangemTechService
|
||||
import com.tangem.network.common.MoshiConverter
|
||||
import com.tangem.tap.common.FileReader
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.domain.tokens.models.TokenDao
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
|
||||
@Deprecated("Use this only for migration")
|
||||
class OldUserTokensRepository(
|
||||
private val fileReader: FileReader,
|
||||
private val tangemNetworkService: TangemTechService,
|
||||
) {
|
||||
private val moshi = MoshiConverter.defaultMoshi()
|
||||
private val blockchainsAdapter: JsonAdapter<List<Blockchain>> = moshi.adapter(
|
||||
Types.newParameterizedType(List::class.java, Blockchain::class.java),
|
||||
)
|
||||
private val tokensAdapter: JsonAdapter<List<TokenDao>> = moshi.adapter(
|
||||
Types.newParameterizedType(List::class.java, TokenDao::class.java),
|
||||
)
|
||||
private val blockchainNetworkAdapter: JsonAdapter<List<BlockchainNetwork>> =
|
||||
moshi.adapter(Types.newParameterizedType(List::class.java, BlockchainNetwork::class.java))
|
||||
|
||||
@Deprecated("Use BlockchainNetwork instead")
|
||||
private fun loadSavedTokens(cardId: String): List<TokenDao> {
|
||||
val json = try {
|
||||
fileReader.readFile(getFileNameForTokens(cardId))
|
||||
} catch (exception: Exception) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
return try {
|
||||
tokensAdapter.fromJson(json) ?: emptyList()
|
||||
} catch (exception: Exception) {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Use BlockchainNetwork instead")
|
||||
private fun loadSavedBlockchains(cardId: String): List<Blockchain> {
|
||||
return try {
|
||||
val json = fileReader.readFile(getFileNameForBlockchains(cardId))
|
||||
blockchainsAdapter.fromJson(json)?.distinct() ?: emptyList()
|
||||
} catch (exception: Exception) {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Use TokensRepository instead")
|
||||
suspend fun loadSavedCurrencies(
|
||||
cardId: String,
|
||||
isHdWalletSupported: Boolean = false,
|
||||
): List<BlockchainNetwork> {
|
||||
return try {
|
||||
val json = fileReader.readFile(getFileNameForBlockchains(cardId))
|
||||
blockchainNetworkAdapter.fromJson(json)?.distinct() ?: emptyList()
|
||||
} catch (exception: Exception) {
|
||||
tryToLoadPreviousFormatAndMigrate(cardId, isHdWalletSupported)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun tryToLoadPreviousFormatAndMigrate(
|
||||
cardId: String,
|
||||
isHdWalletSupported: Boolean = false,
|
||||
): List<BlockchainNetwork> {
|
||||
return try {
|
||||
loadSavedCurrenciesOldWay(
|
||||
cardId,
|
||||
isHdWalletSupported,
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadSavedCurrenciesOldWay(
|
||||
cardId: String, isHdWalletSupported: Boolean = false,
|
||||
): List<BlockchainNetwork> {
|
||||
val blockchains = loadSavedBlockchains(cardId)
|
||||
val tokens = loadSavedTokens(cardId)
|
||||
val ids = getTokensIds(tokens)
|
||||
val derivationStyle = if (isHdWalletSupported) DerivationStyle.LEGACY else null
|
||||
val blockchainNetworks = blockchains.map { blockchain ->
|
||||
BlockchainNetwork(
|
||||
blockchain = blockchain,
|
||||
derivationPath = blockchain.derivationPath(derivationStyle)?.rawPath,
|
||||
tokens = tokens
|
||||
.filter { it.blockchainDao.toBlockchain() == blockchain }
|
||||
.map {
|
||||
val token = it.toToken()
|
||||
token.copy(id = ids[token.contractAddress])
|
||||
},
|
||||
)
|
||||
}
|
||||
return blockchainNetworks
|
||||
}
|
||||
|
||||
private suspend fun getTokensIds(tokens: List<TokenDao>): Map<String, String> = coroutineScope {
|
||||
tokens.map {
|
||||
async {
|
||||
tangemNetworkService.getTokens(
|
||||
contractAddress = it.contractAddress,
|
||||
networkId = it.blockchainDao.toBlockchain().toNetworkId(),
|
||||
active = true,
|
||||
)
|
||||
}
|
||||
}.map { it.await() }
|
||||
.map { (it as? Result.Success)?.data?.coins?.firstOrNull()?.id }
|
||||
.mapIndexedNotNull { index, id ->
|
||||
if (id == null) null else tokens[index].contractAddress to id
|
||||
}.toMap()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val FILE_NAME_PREFIX_TOKENS = "tokens"
|
||||
private const val FILE_NAME_PREFIX_BLOCKCHAINS = "blockchains"
|
||||
private fun getFileNameForTokens(cardId: String): String = "${FILE_NAME_PREFIX_TOKENS}_$cardId"
|
||||
private fun getFileNameForBlockchains(cardId: String): String =
|
||||
"${FILE_NAME_PREFIX_BLOCKCHAINS}_$cardId"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.tap.domain.tokens
|
||||
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.network.api.tangemTech.TangemTechService
|
||||
import com.tangem.network.api.tangemTech.UserTokensResponse
|
||||
import com.tangem.tap.domain.NoDataError
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
|
||||
class UserTokensNetworkService(private val tangemTechService: TangemTechService) {
|
||||
suspend fun getUserTokens(userId: String): Result<UserTokensResponse> {
|
||||
return when (val result = tangemTechService.getUserTokens(userId)) {
|
||||
is Result.Success -> result
|
||||
is Result.Failure -> {
|
||||
val error = result.error
|
||||
if (error is TangemSdkError.NetworkError && error.customMessage.contains("404")) {
|
||||
return Result.Failure(NoDataError(error.customMessage))
|
||||
} else {
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun saveUserTokens(userId: String, tokens: List<Currency>): Result<Unit> {
|
||||
val tokensResponse = tokens.map { it.toTokenResponse() }
|
||||
val data = UserTokensResponse(tokens = tokensResponse)
|
||||
return tangemTechService.putUserTokens(userId, data)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
package com.tangem.tap.domain.tokens
|
||||
|
||||
import android.content.Context
|
||||
import com.tangem.blockchain.common.DerivationStyle
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.extensions.calculateSha256
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.domain.common.extensions.calculateHmacSha256
|
||||
import com.tangem.network.api.tangemTech.TangemTechService
|
||||
import com.tangem.tap.common.AndroidFileReader
|
||||
import com.tangem.tap.domain.NoDataError
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.models.toBlockchainNetworks
|
||||
import com.tangem.tap.features.wallet.models.toCurrencies
|
||||
import com.tangem.tap.network.NetworkConnectivity
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class UserTokensRepository(
|
||||
private val storageService: UserTokensStorageService,
|
||||
private val networkService: UserTokensNetworkService,
|
||||
) {
|
||||
suspend fun getUserTokens(card: Card): List<Currency> {
|
||||
if (DemoHelper.isDemoCardId(card.cardId)) {
|
||||
return loadDemoCurrencies()
|
||||
}
|
||||
val userId = card.getUserId()
|
||||
if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) {
|
||||
return loadTokensOffline(card, userId)
|
||||
}
|
||||
|
||||
return when (val networkResult = networkService.getUserTokens(userId)) {
|
||||
is Result.Success -> {
|
||||
val tokens = networkResult.data.tokens.map { Currency.fromTokenResponse(it) }
|
||||
storageService.saveUserTokens(card.getUserId(), tokens)
|
||||
tokens
|
||||
}
|
||||
is Result.Failure -> {
|
||||
handleGetUserTokensFailure(card = card, userId = userId, error = networkResult.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun saveUserTokens(card: Card, tokens: List<Currency>) {
|
||||
networkService.saveUserTokens(card.getUserId(), tokens)
|
||||
storageService.saveUserTokens(card.getUserId(), tokens)
|
||||
}
|
||||
|
||||
suspend fun removeUserTokens(card: Card) {
|
||||
networkService.saveUserTokens(card.getUserId(), emptyList())
|
||||
storageService.saveUserTokens(card.getUserId(), emptyList())
|
||||
}
|
||||
|
||||
fun loadBlockchainsToDerive(card: Card): List<BlockchainNetwork> {
|
||||
return storageService.getUserTokens(card.getUserId())?.toBlockchainNetworks() ?: emptyList()
|
||||
}
|
||||
|
||||
private fun loadDemoCurrencies(): List<Currency> {
|
||||
return DemoHelper.config.demoBlockchains.map {
|
||||
BlockchainNetwork(
|
||||
blockchain = it,
|
||||
derivationPath = it.derivationPath(DerivationStyle.LEGACY)?.rawPath,
|
||||
tokens = emptyList(),
|
||||
)
|
||||
}.flatMap { it.toCurrencies() }
|
||||
}
|
||||
|
||||
private suspend fun handleGetUserTokensFailure(
|
||||
card: Card,
|
||||
userId: String,
|
||||
error: Throwable,
|
||||
): List<Currency> {
|
||||
return when (error) {
|
||||
is NoDataError -> {
|
||||
val tokens = storageService.getUserTokens(card)
|
||||
coroutineScope { launch { networkService.saveUserTokens(userId = userId, tokens = tokens) } }
|
||||
tokens
|
||||
}
|
||||
else -> {
|
||||
val tokens = storageService.getUserTokens(userId) ?: storageService.getUserTokens(card)
|
||||
tokens
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadTokensOffline(card: Card, userId: String): List<Currency> {
|
||||
return storageService.getUserTokens(userId) ?: storageService.getUserTokens(card)
|
||||
}
|
||||
|
||||
private fun Card.getUserId(): String {
|
||||
val walletPublicKey = this.wallets.firstOrNull()?.publicKey ?: return ""
|
||||
return calculateUserId(walletPublicKey)
|
||||
}
|
||||
|
||||
private fun calculateUserId(walletPublicKey: ByteArray): String {
|
||||
val message = MESSAGE.toByteArray()
|
||||
val keyHash = walletPublicKey.calculateSha256()
|
||||
return message.calculateHmacSha256(keyHash).toHexString()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val MESSAGE = "UserWalletID"
|
||||
fun init(context: Context, tangemTechService: TangemTechService): UserTokensRepository {
|
||||
val fileReader = AndroidFileReader(context)
|
||||
val oldUserTokensRepository = OldUserTokensRepository(
|
||||
fileReader, store.state.domainNetworks.tangemTechService,
|
||||
)
|
||||
val storageService = UserTokensStorageService(oldUserTokensRepository, fileReader)
|
||||
val networkService = UserTokensNetworkService(tangemTechService)
|
||||
return UserTokensRepository(storageService, networkService)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
package com.tangem.tap.domain.tokens
|
||||
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.tangem.Log
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.network.api.tangemTech.UserTokensResponse
|
||||
import com.tangem.network.common.MoshiConverter
|
||||
import com.tangem.tap.common.FileReader
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.models.toCurrencies
|
||||
|
||||
class UserTokensStorageService(
|
||||
private val oldUserTokensRepository: OldUserTokensRepository,
|
||||
private val fileReader: FileReader,
|
||||
) {
|
||||
private val moshi = MoshiConverter.defaultMoshi()
|
||||
private val userTokensAdapter: JsonAdapter<UserTokensResponse> =
|
||||
moshi.adapter(UserTokensResponse::class.java)
|
||||
|
||||
fun getUserTokens(userId: String): List<Currency>? {
|
||||
return try {
|
||||
val json = fileReader.readFile(getFileNameForUserTokens(userId))
|
||||
userTokensAdapter.fromJson(json)?.tokens?.map { Currency.fromTokenResponse(it) }
|
||||
} catch (exception: Exception) {
|
||||
Log.error { exception.stackTraceToString() }
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("")
|
||||
suspend fun getUserTokens(card: Card): List<Currency> {
|
||||
val blockchainNetworks =
|
||||
oldUserTokensRepository.loadSavedCurrencies(card.cardId, card.settings.isHDWalletAllowed)
|
||||
return blockchainNetworks.flatMap { it.toCurrencies() }
|
||||
}
|
||||
|
||||
fun saveUserTokens(userId: String, tokens: List<Currency>) {
|
||||
val tokensResponse = tokens.map { it.toTokenResponse() }
|
||||
val data = UserTokensResponse(tokens = tokensResponse)
|
||||
val json = userTokensAdapter.toJson(data)
|
||||
fileReader.rewriteFile(json, getFileNameForUserTokens(userId))
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val FILE_NAME_PREFIX_USER_TOKENS = "user_tokens"
|
||||
private fun getFileNameForUserTokens(userId: String): String = "${FILE_NAME_PREFIX_USER_TOKENS}_$userId"
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,8 @@ import com.tangem.domain.common.ScanResponse
|
|||
import com.tangem.tap.common.analytics.Analytics
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.walletconnect.extensions.isDappSupported
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectDialog
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession
|
||||
|
|
@ -57,7 +59,15 @@ class WalletConnectManager {
|
|||
private var sessions: MutableMap<WCSession, WalletConnectActiveData> = mutableMapOf()
|
||||
|
||||
fun connect(wcUri: String) {
|
||||
val session = WCSession.from(wcUri) ?: return
|
||||
val session = WCSession.from(wcUri).guard {
|
||||
store.dispatchOnMain(
|
||||
WalletConnectAction.FailureEstablishingSession(
|
||||
session = null,
|
||||
error = TapError.WalletConnect.UnsupportedLink,
|
||||
),
|
||||
)
|
||||
return
|
||||
}
|
||||
if (sessions[session] != null) {
|
||||
store.dispatchOnMain(WalletConnectAction.RefuseOpeningSession)
|
||||
return
|
||||
|
|
@ -65,7 +75,19 @@ class WalletConnectManager {
|
|||
val client = WCClient(httpClient = okHttpClient)
|
||||
setListeners(client)
|
||||
val peerId = UUID.randomUUID().toString()
|
||||
client.connect(session, tangemPeerMeta, peerId)
|
||||
|
||||
try {
|
||||
client.connect(session, tangemPeerMeta, peerId)
|
||||
} catch (exception: IllegalArgumentException) {
|
||||
store.dispatchOnMain(
|
||||
WalletConnectAction.FailureEstablishingSession(
|
||||
session = null,
|
||||
error = TapError.WalletConnect.UnsupportedLink,
|
||||
),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
sessions[session] = WalletConnectActiveData(
|
||||
peerId = peerId,
|
||||
remotePeerId = null,
|
||||
|
|
@ -331,21 +353,30 @@ class WalletConnectManager {
|
|||
val session = client.session
|
||||
val data = sessions[session]?.copy(peerMeta = peer, remotePeerId = client.remotePeerId)
|
||||
if (data != null && session != null) {
|
||||
sessions[session] = data
|
||||
val sessionData = data.toWalletConnectSession()
|
||||
sessionData?.let {
|
||||
if (!peer.isDappSupported()) {
|
||||
store.dispatchOnMain(
|
||||
WalletConnectAction.ScanCard(
|
||||
session = sessionData,
|
||||
chainId = client.chainId?.toIntOrNull()
|
||||
WalletConnectAction.FailureEstablishingSession(
|
||||
session = session,
|
||||
error = TapError.WalletConnect.UnsupportedDapp,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
sessions[session] = data
|
||||
val sessionData = data.toWalletConnectSession()
|
||||
sessionData?.let {
|
||||
store.dispatchOnMain(
|
||||
WalletConnectAction.ScanCard(
|
||||
session = sessionData,
|
||||
chainId = client.chainId?.toIntOrNull(),
|
||||
),
|
||||
)
|
||||
}
|
||||
store.state.globalState.analyticsHandlers?.logWcEvent(
|
||||
Analytics.WcAnalyticsEvent.Session(
|
||||
Analytics.WcSessionEvent.Connect, peer.url,
|
||||
),
|
||||
)
|
||||
}
|
||||
store.state.globalState.analyticsHandlers?.logWcEvent(
|
||||
Analytics.WcAnalyticsEvent.Session(
|
||||
Analytics.WcSessionEvent.Connect, peer.url
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
client.onSessionUpdate = { id: Long, update: WCSessionUpdate ->
|
||||
|
|
|
|||
|
|
@ -1,64 +0,0 @@
|
|||
package com.tangem.tap.domain.walletconnect
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchain.common.WalletManagerFactory
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.tap.domain.extensions.makeWalletManagerForApp
|
||||
import com.tangem.tap.domain.tokens.CurrenciesRepository
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletForSession
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
|
||||
class WcWalletManagerFactory(
|
||||
private val factory: WalletManagerFactory,
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
) {
|
||||
fun getWalletManager(
|
||||
wallet: WalletForSession, blockchain: Blockchain, walletState: WalletState,
|
||||
): WalletManager? {
|
||||
val blockchainToMake = if (blockchain == Blockchain.Ethereum && wallet.isTestNet) {
|
||||
Blockchain.EthereumTestnet
|
||||
} else {
|
||||
blockchain
|
||||
}
|
||||
val blockchainNetwork = BlockchainNetwork(
|
||||
blockchain = blockchainToMake,
|
||||
derivationPath = wallet.derivationPath?.rawPath,
|
||||
tokens = emptyList(),
|
||||
)
|
||||
return walletState.getWalletManager(blockchainNetwork)
|
||||
}
|
||||
|
||||
suspend fun getWalletManager(
|
||||
scanResponse: ScanResponse, blockchain: Blockchain, walletState: WalletState,
|
||||
): WalletManager? {
|
||||
val card = scanResponse.card
|
||||
val blockchainToMake = if (blockchain == Blockchain.Ethereum && card.isTestCard) {
|
||||
Blockchain.EthereumTestnet
|
||||
} else {
|
||||
blockchain
|
||||
}
|
||||
val blockchainNetwork = BlockchainNetwork(
|
||||
blockchain = blockchainToMake,
|
||||
card = card,
|
||||
)
|
||||
|
||||
return if (walletState.cardId == card.cardId) {
|
||||
walletState.getWalletManager(blockchainNetwork)
|
||||
} else {
|
||||
if (currenciesRepository
|
||||
.loadSavedCurrencies(card.cardId, card.settings.isHDWalletAllowed)
|
||||
.contains(blockchainNetwork)
|
||||
) {
|
||||
factory.makeWalletManagerForApp(
|
||||
scanResponse = scanResponse,
|
||||
blockchainNetwork = blockchainNetwork,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.tap.domain.walletconnect.extensions
|
||||
|
||||
import com.trustwallet.walletconnect.models.WCPeerMeta
|
||||
|
||||
fun WCPeerMeta.isDappSupported(): Boolean {
|
||||
return !unsupportedDappsList.any { this.url.contains(it) }
|
||||
}
|
||||
|
||||
private val unsupportedDappsList: List<String> = listOf("dydx.exchange")
|
||||
|
|
@ -448,6 +448,33 @@ class DemoConfig {
|
|||
"AB02000000049533",
|
||||
"AB02000000049541",
|
||||
"AB02000000049830",
|
||||
// === more cids ===
|
||||
"AC03000000091418",
|
||||
"AC03000000091400",
|
||||
"AC03000000099007",
|
||||
"AC03000000098991",
|
||||
"AC03000000098942",
|
||||
"AC03000000091715",
|
||||
"AC03000000091301",
|
||||
"AC03000000091343",
|
||||
"AB01000000055705",
|
||||
"AB01000000052918",
|
||||
"AB01000000047710",
|
||||
"AB01000000052306",
|
||||
"AB01000000047645",
|
||||
"AB01000000048957",
|
||||
"AB01000000052900",
|
||||
"AB01000000050391",
|
||||
"AB01000000047363",
|
||||
"AB02000000053998",
|
||||
"AB02000000019809",
|
||||
"AB02000000020872",
|
||||
"AB02000000022027",
|
||||
"AB02000000058955",
|
||||
"AB02000000053253",
|
||||
"AB02000000048063",
|
||||
"AB02000000023736",
|
||||
"AB02000000058187",
|
||||
)
|
||||
|
||||
private val testDemoCardIds = listOf(
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import com.tangem.tap.common.redux.AppState
|
|||
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.currenciesRepository
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
|
||||
|
|
@ -110,11 +109,9 @@ class DetailsMiddleware {
|
|||
val card = store.state.detailsState.cardSettingsState?.card ?: return
|
||||
scope.launch {
|
||||
val result = tangemSdkManager.resetToFactorySettings(card)
|
||||
withContext(Dispatchers.Main) {
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
currenciesRepository.removeCurrencies(card.cardId)
|
||||
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home))
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
(result.error as? TangemSdkError)?.let { error ->
|
||||
|
|
@ -125,7 +122,6 @@ class DetailsMiddleware {
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.tap.features.details.redux.walletconnect
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.tap.common.redux.NotificationAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.wallet.R
|
||||
import com.trustwallet.walletconnect.models.binance.WCBinanceTradeOrder
|
||||
import com.trustwallet.walletconnect.models.binance.WCBinanceTransferOrder
|
||||
|
|
@ -52,7 +53,7 @@ sealed class WalletConnectAction : Action {
|
|||
val updatedSession: WalletConnectSession,
|
||||
) : WalletConnectAction()
|
||||
|
||||
data class FailureEstablishingSession(val session: WCSession?) : WalletConnectAction()
|
||||
data class FailureEstablishingSession(val session: WCSession?, val error: TapError? = null) : WalletConnectAction()
|
||||
data class SetSessionsRestored(val sessions: List<WalletConnectSession>) :
|
||||
WalletConnectAction()
|
||||
|
||||
|
|
@ -103,6 +104,6 @@ sealed class WalletConnectAction : Action {
|
|||
|
||||
data class Sign(
|
||||
val id: Long, val data: ByteArray, val sessionData: WCSession,
|
||||
) : WalletConnectAction()
|
||||
) : WalletConnectAction()
|
||||
}
|
||||
}
|
||||
|
|
@ -2,26 +2,28 @@ package com.tangem.tap.features.details.redux.walletconnect
|
|||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.DerivationStyle
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.TapWorkarounds.derivationStyle
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
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.currenciesRepository
|
||||
import com.tangem.tap.domain.extensions.isMultiwalletAllowed
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.domain.walletconnect.BnbHelper
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectManager
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectNetworkUtils
|
||||
import com.tangem.tap.domain.walletconnect.WcWalletManagerFactory
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.Middleware
|
||||
|
|
@ -96,6 +98,16 @@ class WalletConnectMiddleware {
|
|||
store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.SessionTimeout))
|
||||
}
|
||||
is WalletConnectAction.FailureEstablishingSession -> {
|
||||
if (action.error != null) {
|
||||
store.dispatch(
|
||||
GlobalAction.ShowDialog(
|
||||
AppDialog.SimpleOkDialogRes(
|
||||
headerId = R.string.common_warning,
|
||||
messageId = action.error.messageResource,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
if (action.session != null) {
|
||||
walletConnectManager.disconnect(action.session)
|
||||
}
|
||||
|
|
@ -189,15 +201,10 @@ class WalletConnectMiddleware {
|
|||
store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedNetwork))
|
||||
return
|
||||
}
|
||||
val factory = WcWalletManagerFactory(
|
||||
factory = store.state.globalState.tapWalletManager.walletManagerFactory,
|
||||
currenciesRepository = currenciesRepository,
|
||||
)
|
||||
val walletState = store.state.walletState
|
||||
val walletManager = factory.getWalletManager(
|
||||
val walletManager = getWalletManager(
|
||||
wallet = action.session.wallet,
|
||||
blockchain = blockchain,
|
||||
walletState = walletState,
|
||||
walletState = store.state.walletState,
|
||||
).guard {
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
|
|
@ -233,20 +240,10 @@ class WalletConnectMiddleware {
|
|||
handleScanResponse(scanResponse = scanResponse, session = session, blockchain = blockchain)
|
||||
}
|
||||
|
||||
private suspend fun getAvailableBlockchains(card: Card, walletState: WalletState): List<Blockchain> {
|
||||
return if (walletState.cardId == card.cardId) {
|
||||
walletState.currencies.filter {
|
||||
it.isBlockchain() && !it.isCustomCurrency(card.derivationStyle) && it.blockchain.isEvm()
|
||||
}.map { it.blockchain }
|
||||
} else {
|
||||
currenciesRepository
|
||||
.loadSavedCurrencies(card.cardId, card.settings.isHDWalletAllowed)
|
||||
.filter {
|
||||
it.blockchain.isEvm() &&
|
||||
it.derivationPath == it.blockchain.derivationPath(card.derivationStyle)?.rawPath
|
||||
}
|
||||
.map { it.blockchain }
|
||||
}
|
||||
private fun getAvailableBlockchains(card: Card, walletState: WalletState): List<Blockchain> {
|
||||
return walletState.currencies.filter {
|
||||
it.isBlockchain() && !it.isCustomCurrency(card.derivationStyle) && it.blockchain.isEvm()
|
||||
}.map { it.blockchain }
|
||||
}
|
||||
|
||||
private suspend fun prepareWalletManager(
|
||||
|
|
@ -256,11 +253,7 @@ class WalletConnectMiddleware {
|
|||
session: WalletConnectSession,
|
||||
walletConnectManager: WalletConnectManager,
|
||||
) {
|
||||
val factory = WcWalletManagerFactory(
|
||||
factory = store.state.globalState.tapWalletManager.walletManagerFactory,
|
||||
currenciesRepository = currenciesRepository,
|
||||
)
|
||||
val walletManager = factory.getWalletManager(scanResponse, blockchain, walletState).guard {
|
||||
val walletManager = getWalletManager(session.wallet, blockchain, walletState).guard {
|
||||
store.dispatchOnMain(WalletConnectAction.FailureEstablishingSession(session.session))
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
|
|
@ -309,17 +302,29 @@ class WalletConnectMiddleware {
|
|||
NewWcSessionData(session = updatedSession, scanResponse = scanResponse, blockchain = blockchain),
|
||||
),
|
||||
)
|
||||
val blockchains = if (blockchain.isEvm()) getAvailableBlockchains(card, walletState) else emptyList()
|
||||
store.dispatch(
|
||||
GlobalAction.ShowDialog(
|
||||
WalletConnectDialog.ApproveWcSession(session = updatedSession, networks = blockchains),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
val blockchains = if (blockchain.isEvm()) getAvailableBlockchains(card, walletState) else emptyList()
|
||||
|
||||
withMainContext {
|
||||
store.dispatch(
|
||||
GlobalAction.ShowDialog(
|
||||
WalletConnectDialog.ApproveWcSession(session = updatedSession, networks = blockchains),
|
||||
),
|
||||
)
|
||||
}
|
||||
private fun getWalletManager(
|
||||
wallet: WalletForSession, blockchain: Blockchain, walletState: WalletState,
|
||||
): WalletManager? {
|
||||
val blockchainToMake = if (blockchain == Blockchain.Ethereum && wallet.isTestNet) {
|
||||
Blockchain.EthereumTestnet
|
||||
} else {
|
||||
blockchain
|
||||
}
|
||||
val derivation = blockchainToMake.derivationPath(store.state.globalState.scanResponse?.card?.derivationStyle)
|
||||
?.rawPath
|
||||
val blockchainNetwork = BlockchainNetwork(
|
||||
blockchain = blockchainToMake,
|
||||
derivationPath = derivation,
|
||||
tokens = emptyList(),
|
||||
)
|
||||
return walletState.getWalletManager(blockchainNetwork)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.Arrangement
|
|||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
|
|
@ -86,7 +87,7 @@ fun WalletConnectDetailsItem(
|
|||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.height(84.dp)
|
||||
.defaultMinSize(minHeight = 84.dp)
|
||||
.fillMaxWidth()
|
||||
.clickable { onItemsClick(SettingsElement.WalletConnect) },
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
|
|
@ -99,7 +100,7 @@ fun WalletConnectDetailsItem(
|
|||
tint = colorResource(id = R.color.all_colors_azure),
|
||||
)
|
||||
Column(
|
||||
modifier = modifier.height(56.dp),
|
||||
modifier = modifier.defaultMinSize(minHeight = 56.dp),
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package com.tangem.tap.features.details.ui.resetcard
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -89,11 +91,14 @@ fun ResetCardView(
|
|||
color = colorResource(id = R.color.text_secondary),
|
||||
)
|
||||
|
||||
Spacer(modifier = modifier.size(44.dp))
|
||||
Spacer(modifier = modifier.size(28.dp))
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(end = 20.dp),
|
||||
.clickable(
|
||||
onClick = { state.onAcceptWarningToggleClick(!state.accepted) },
|
||||
)
|
||||
.padding(top = 16.dp, bottom = 16.dp),
|
||||
) {
|
||||
IconToggleButton(
|
||||
checked = state.accepted,
|
||||
|
|
@ -120,13 +125,15 @@ fun ResetCardView(
|
|||
text = stringResource(id = R.string.reset_card_to_factory_warning_message),
|
||||
style = TangemTypography.body2,
|
||||
color = colorResource(id = R.color.text_secondary),
|
||||
modifier = modifier
|
||||
.padding(end = 20.dp),
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = modifier.size(32.dp))
|
||||
Spacer(modifier = modifier.size(16.dp))
|
||||
Box(
|
||||
modifier = modifier
|
||||
.padding(start = 16.dp, end = 16.dp),
|
||||
.padding(start = 16.dp, end = 16.dp, bottom = 32.dp),
|
||||
) {
|
||||
DetailsMainButton(
|
||||
title = stringResource(id = R.string.reset_card_to_factory_button_title),
|
||||
|
|
|
|||
|
|
@ -140,13 +140,12 @@ private fun WalletConnectSessions(
|
|||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
Text(
|
||||
text = session.description,
|
||||
style = TangemTypography.subtitle1,
|
||||
color = colorResource(id = R.color.text_primary_1),
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = session.description,
|
||||
style = TangemTypography.subtitle1,
|
||||
color = colorResource(id = R.color.text_primary_1),
|
||||
modifier = modifier.weight(1f),
|
||||
)
|
||||
IconButton(
|
||||
onClick = { state.onRemoveSession(session.sessionId) },
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -79,41 +79,46 @@ private fun handleReadCard() {
|
|||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer))
|
||||
} else {
|
||||
changeButtonState(ButtonState.PROGRESS)
|
||||
store.dispatch(GlobalAction.ScanCard(onSuccess = { scanResponse ->
|
||||
store.state.globalState.tapWalletManager.updateConfigManager(scanResponse)
|
||||
store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse))
|
||||
store.dispatch(
|
||||
GlobalAction.ScanCard(
|
||||
onSuccess = { scanResponse ->
|
||||
store.state.globalState.tapWalletManager.updateConfigManager(scanResponse)
|
||||
store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse))
|
||||
|
||||
// TODO: SaltPay: temporary excluding backup process for Visa cards
|
||||
if (scanResponse.card.isSaltPayVisa) {
|
||||
scope.launch {
|
||||
store.onCardScanned(scanResponse)
|
||||
withMainContext {
|
||||
navigateTo(AppScreen.Wallet, null)
|
||||
// TODO: SaltPay: temporary excluding backup process for Visa cards
|
||||
if (scanResponse.card.isSaltPayVisa) {
|
||||
scope.launch {
|
||||
store.onCardScanned(scanResponse)
|
||||
withMainContext {
|
||||
navigateTo(AppScreen.Wallet, null)
|
||||
}
|
||||
}
|
||||
return@ScanCard
|
||||
}
|
||||
}
|
||||
return@ScanCard
|
||||
}
|
||||
|
||||
if (OnboardingHelper.isOnboardingCase(scanResponse)) {
|
||||
val navigateTo = OnboardingHelper.whereToNavigate(scanResponse)
|
||||
store.dispatch(GlobalAction.Onboarding.Start(scanResponse))
|
||||
navigateTo(navigateTo)
|
||||
} else {
|
||||
scope.launch {
|
||||
store.onCardScanned(scanResponse)
|
||||
withMainContext {
|
||||
if (scanResponse.twinsIsTwinned() && !preferencesStorage.wasTwinsOnboardingShown()) {
|
||||
store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly))
|
||||
navigateTo(AppScreen.OnboardingTwins)
|
||||
} else {
|
||||
navigateTo(AppScreen.Wallet, null)
|
||||
if (OnboardingHelper.isOnboardingCase(scanResponse)) {
|
||||
val navigateTo = OnboardingHelper.whereToNavigate(scanResponse)
|
||||
store.dispatch(GlobalAction.Onboarding.Start(scanResponse))
|
||||
navigateTo(navigateTo)
|
||||
} else {
|
||||
scope.launch {
|
||||
withMainContext {
|
||||
if (scanResponse.twinsIsTwinned() && !preferencesStorage.wasTwinsOnboardingShown()) {
|
||||
store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly))
|
||||
navigateTo(AppScreen.OnboardingTwins)
|
||||
} else {
|
||||
navigateTo(AppScreen.Wallet, null)
|
||||
}
|
||||
}
|
||||
store.onCardScanned(scanResponse)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, onFailure = {
|
||||
changeButtonState(ButtonState.ENABLED)
|
||||
}))
|
||||
},
|
||||
onFailure = {
|
||||
changeButtonState(ButtonState.ENABLED)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -79,17 +79,21 @@ private fun handleOtherCardsAction(action: Action, dispatch: DispatchFunction) {
|
|||
val primaryBlockchain = updatedResponse.getBlockchain()
|
||||
val blockchainNetworks = if (primaryBlockchain != Blockchain.Unknown) {
|
||||
val primaryToken = updatedResponse.getPrimaryToken()
|
||||
val blockchainNetwork = BlockchainNetwork(primaryBlockchain, updatedResponse.card).updateTokens(
|
||||
listOfNotNull(primaryToken))
|
||||
val blockchainNetwork =
|
||||
BlockchainNetwork(primaryBlockchain, updatedResponse.card).updateTokens(
|
||||
listOfNotNull(primaryToken),
|
||||
)
|
||||
listOf(blockchainNetwork)
|
||||
} else {
|
||||
listOf(
|
||||
BlockchainNetwork(Blockchain.Bitcoin, updatedResponse.card),
|
||||
BlockchainNetwork(Blockchain.Ethereum, updatedResponse.card)
|
||||
BlockchainNetwork(Blockchain.Ethereum, updatedResponse.card),
|
||||
)
|
||||
}
|
||||
|
||||
store.dispatch(WalletAction.MultiWallet.SaveCurrencies(blockchainNetworks))
|
||||
store.dispatch(
|
||||
WalletAction.MultiWallet.SaveCurrencies(blockchainNetworks, updatedResponse.card),
|
||||
)
|
||||
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
store.dispatch(OnboardingOtherCardsAction.SetStepOfScreen(OnboardingOtherCardsStep.Done))
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import com.tangem.tap.common.redux.AppState
|
|||
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.currenciesRepository
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.extensions.makePrimaryWalletManager
|
||||
import com.tangem.tap.domain.twins.TwinCardsManager
|
||||
|
|
@ -271,7 +270,6 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
|
|||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet))
|
||||
}
|
||||
CreateTwinWalletMode.RecreateWallet -> {
|
||||
currenciesRepository.removeCurrencies(scanResponse.card.cardId)
|
||||
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,8 +102,8 @@ private fun handleWalletAction(action: Action) {
|
|||
store.dispatchOnMain(
|
||||
WalletAction.MultiWallet.SaveCurrencies(
|
||||
blockchainNetworks = blockchainNetworks,
|
||||
cardId = result.data.card.cardId
|
||||
)
|
||||
card = result.data.card,
|
||||
),
|
||||
)
|
||||
onboardingManager.activationStarted(updatedResponse.card.cardId)
|
||||
store.dispatch(OnboardingWalletAction.ProceedBackup())
|
||||
|
|
@ -286,15 +286,6 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
|
|||
backupService.proceedBackup { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val blockchainNetworks = listOf(
|
||||
BlockchainNetwork(Blockchain.Bitcoin, result.data),
|
||||
BlockchainNetwork(Blockchain.Ethereum, result.data)
|
||||
)
|
||||
store.dispatchOnMain(
|
||||
WalletAction.MultiWallet.SaveCurrencies(
|
||||
blockchainNetworks = blockchainNetworks, cardId = result.data.cardId
|
||||
)
|
||||
)
|
||||
if (backupService.currentState == BackupService.State.Finished) {
|
||||
store.dispatchOnMain(BackupAction.FinishBackup)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -246,8 +246,7 @@ private fun sendTransaction(
|
|||
}
|
||||
is SimpleResult.Failure -> {
|
||||
store.state.globalState.feedbackManager?.infoHolder?.updateOnSendError(
|
||||
wallet = walletManager.wallet,
|
||||
host = walletManager.currentHost,
|
||||
walletManager = walletManager,
|
||||
amountToSend = amountToSend,
|
||||
feeAmount = feeAmount,
|
||||
destinationAddress = destinationAddress,
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ import com.tangem.domain.features.addCustomToken.CustomCurrency
|
|||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction
|
||||
import com.tangem.domain.redux.domainStore
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
|
||||
import com.tangem.tap.assetReader
|
||||
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
|
|
@ -32,6 +33,9 @@ import com.tangem.tap.domain.tokens.LoadAvailableCoinsService
|
|||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Middleware
|
||||
|
|
@ -77,7 +81,7 @@ class TokensMiddleware {
|
|||
|
||||
val loadCoinsService = LoadAvailableCoinsService(
|
||||
store.state.domainNetworks.tangemTechService,
|
||||
currenciesRepository
|
||||
assetReader,
|
||||
)
|
||||
|
||||
scope.launch {
|
||||
|
|
@ -285,22 +289,28 @@ class TokensMiddleware {
|
|||
else -> DerivationParams.Custom(derivationPath)
|
||||
}
|
||||
}
|
||||
|
||||
val walletManager = factory.makeWalletManagerForApp(
|
||||
scanResponse = scanResponse,
|
||||
blockchain = currency.blockchain,
|
||||
derivationParams = derivationParams
|
||||
derivationParams = derivationParams,
|
||||
) ?: return@mapNotNull null
|
||||
val blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager)
|
||||
WalletAction.MultiWallet.AddBlockchain(blockchainNetwork, walletManager)
|
||||
WalletAction.MultiWallet.AddBlockchain(
|
||||
blockchain = blockchainNetwork,
|
||||
walletManager = walletManager,
|
||||
save = true,
|
||||
)
|
||||
}
|
||||
is Currency.Token -> {
|
||||
val rawDerivationPath = currency.derivationPath
|
||||
?: currency.blockchain.derivationPath(derivationStyle)?.rawPath
|
||||
|
||||
val blockchainNetwork =
|
||||
BlockchainNetwork(currency.blockchain, rawDerivationPath, emptyList())
|
||||
WalletAction.MultiWallet.AddToken(currency.token, blockchainNetwork)
|
||||
BlockchainNetwork(currency.blockchain, rawDerivationPath, listOf(currency.token))
|
||||
WalletAction.MultiWallet.AddToken(
|
||||
token = currency.token,
|
||||
blockchain = blockchainNetwork,
|
||||
save = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,18 @@
|
|||
package com.tangem.tap.features.wallet.models
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.DerivationStyle
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.domain.common.extensions.toCoinId
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
import com.tangem.domain.features.addCustomToken.CustomCurrency
|
||||
import com.tangem.network.api.tangemTech.TokenResponse
|
||||
import com.tangem.tap.common.redux.global.CryptoCurrencyName
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
|
||||
|
||||
sealed interface Currency {
|
||||
|
||||
val coinId: String?
|
||||
get() = when (this) {
|
||||
is Blockchain -> blockchain.toCoinId()
|
||||
|
|
@ -17,24 +21,28 @@ sealed interface Currency {
|
|||
val blockchain: com.tangem.blockchain.common.Blockchain
|
||||
val currencySymbol: CryptoCurrencyName
|
||||
val derivationPath: String?
|
||||
|
||||
val currencyName: String
|
||||
get() = when (this) {
|
||||
is Blockchain -> blockchain.fullName
|
||||
is Token -> token.name
|
||||
}
|
||||
val decimals
|
||||
get() = when (this) {
|
||||
is Blockchain -> blockchain.decimals()
|
||||
is Token -> token.decimals
|
||||
}
|
||||
|
||||
data class Token(
|
||||
val token: com.tangem.blockchain.common.Token,
|
||||
override val blockchain: com.tangem.blockchain.common.Blockchain,
|
||||
override val derivationPath: String?
|
||||
override val derivationPath: String?,
|
||||
) : Currency {
|
||||
override val currencySymbol = token.symbol
|
||||
}
|
||||
|
||||
data class Blockchain(
|
||||
override val blockchain: com.tangem.blockchain.common.Blockchain,
|
||||
override val derivationPath: String?
|
||||
override val derivationPath: String?,
|
||||
) : Currency {
|
||||
override val currencySymbol: CryptoCurrencyName = blockchain.currency
|
||||
}
|
||||
|
|
@ -48,24 +56,35 @@ sealed interface Currency {
|
|||
}
|
||||
|
||||
fun isBlockchain(): Boolean = this is Blockchain
|
||||
|
||||
fun isToken(): Boolean = this is Token
|
||||
fun toTokenResponse(): TokenResponse {
|
||||
return TokenResponse(
|
||||
id = coinId ?: "",
|
||||
networkId = blockchain.toNetworkId(),
|
||||
derivationPath = derivationPath ?: DERIVATION_PATH_RAW_VALUE,
|
||||
name = currencyName,
|
||||
symbol = currencySymbol,
|
||||
decimals = decimals,
|
||||
contractAddress = if (this is Token) token.contractAddress else null,
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val DERIVATION_PATH_RAW_VALUE = "m/44/0'/0/0"
|
||||
fun fromBlockchainNetwork(
|
||||
blockchainNetwork: BlockchainNetwork,
|
||||
token: com.tangem.blockchain.common.Token? = null
|
||||
token: com.tangem.blockchain.common.Token? = null,
|
||||
): Currency {
|
||||
return if (token != null) {
|
||||
Token(
|
||||
token = token,
|
||||
blockchain = blockchainNetwork.blockchain,
|
||||
derivationPath = blockchainNetwork.derivationPath
|
||||
derivationPath = blockchainNetwork.derivationPath,
|
||||
)
|
||||
} else {
|
||||
Blockchain(
|
||||
blockchain = blockchainNetwork.blockchain,
|
||||
derivationPath = blockchainNetwork.derivationPath
|
||||
derivationPath = blockchainNetwork.derivationPath,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -74,7 +93,7 @@ sealed interface Currency {
|
|||
return when (customCurrency) {
|
||||
is CustomCurrency.CustomBlockchain -> Blockchain(
|
||||
blockchain = customCurrency.network,
|
||||
derivationPath = customCurrency.derivationPath?.rawPath
|
||||
derivationPath = customCurrency.derivationPath?.rawPath,
|
||||
)
|
||||
is CustomCurrency.CustomToken -> Token(
|
||||
token = customCurrency.token,
|
||||
|
|
@ -88,8 +107,53 @@ sealed interface Currency {
|
|||
return Token(
|
||||
token = tokenWithBlockchain.token,
|
||||
blockchain = tokenWithBlockchain.blockchain,
|
||||
derivationPath = null
|
||||
derivationPath = null,
|
||||
)
|
||||
}
|
||||
|
||||
fun fromTokenResponse(tokenResponse: TokenResponse): Currency {
|
||||
val derivationPath = if (tokenResponse.derivationPath == DERIVATION_PATH_RAW_VALUE) {
|
||||
null
|
||||
} else {
|
||||
tokenResponse.derivationPath
|
||||
}
|
||||
return when {
|
||||
tokenResponse.contractAddress != null -> Token(
|
||||
com.tangem.blockchain.common.Token(
|
||||
name = tokenResponse.name,
|
||||
symbol = tokenResponse.symbol,
|
||||
contractAddress = tokenResponse.contractAddress!!,
|
||||
decimals = tokenResponse.decimals,
|
||||
id = tokenResponse.id,
|
||||
),
|
||||
blockchain = com.tangem.blockchain.common.Blockchain.fromNetworkId(tokenResponse.networkId)!!,
|
||||
derivationPath = derivationPath,
|
||||
)
|
||||
else -> Blockchain(
|
||||
blockchain = com.tangem.blockchain.common.Blockchain.fromNetworkId(tokenResponse.networkId)!!,
|
||||
derivationPath = derivationPath,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun BlockchainNetwork.toCurrencies(): List<Currency> {
|
||||
val blockchain = Currency.fromBlockchainNetwork(this)
|
||||
val tokens = this.tokens.map { Currency.fromBlockchainNetwork(this, it) }
|
||||
return listOf(blockchain) + tokens
|
||||
}
|
||||
|
||||
fun List<BlockchainNetwork>.toCurrencies(): List<Currency> {
|
||||
return flatMap { it.toCurrencies() }
|
||||
}
|
||||
|
||||
fun List<Currency>.toBlockchainNetworks(): List<BlockchainNetwork> {
|
||||
return this.filter { it.isBlockchain() }.map { BlockchainNetwork(it.blockchain, it.derivationPath, getTokens(it)) }
|
||||
}
|
||||
|
||||
private fun List<Currency>.getTokens(currency: Currency): List<Token> {
|
||||
return this
|
||||
.filter { it.isToken() && it.blockchain == currency.blockchain && it.derivationPath == currency.derivationPath }
|
||||
.mapNotNull { if (it is Currency.Token) it.token else null }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,7 @@ sealed class WalletWarning(
|
|||
) {
|
||||
data class ExistentialDeposit(
|
||||
val currencyName: String,
|
||||
val currencySymbols: String,
|
||||
val existentialDepositString: String,
|
||||
val edStringValueWithSymbol: String,
|
||||
) : WalletWarning(1)
|
||||
|
||||
object TransactionInProgress : WalletWarning(10)
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ sealed class WalletAction : Action {
|
|||
data class Failure(val error: TapError) : WalletAction()
|
||||
}
|
||||
|
||||
|
||||
data class LoadWallet(
|
||||
val blockchain: BlockchainNetwork? = null,
|
||||
val walletManager: WalletManager? = null
|
||||
|
|
@ -40,7 +39,7 @@ sealed class WalletAction : Action {
|
|||
data class NoAccount(
|
||||
val wallet: Wallet,
|
||||
val blockchain: BlockchainNetwork,
|
||||
val amountToCreateAccount: String
|
||||
val amountToCreateAccount: String,
|
||||
) : WalletAction()
|
||||
|
||||
data class Failure(val wallet: Wallet, val errorMessage: String? = null) : WalletAction()
|
||||
|
|
@ -48,28 +47,31 @@ sealed class WalletAction : Action {
|
|||
|
||||
data class SetArtworkId(val artworkId: String?) : WalletAction()
|
||||
|
||||
sealed class UserTokens : WalletAction() {
|
||||
object Loading : UserTokens()
|
||||
object Loaded : UserTokens()
|
||||
}
|
||||
|
||||
sealed class MultiWallet : WalletAction() {
|
||||
data class SetIsMultiwalletAllowed(val isMultiwalletAllowed: Boolean) : MultiWallet()
|
||||
|
||||
data class AddBlockchain(
|
||||
val blockchain: BlockchainNetwork,
|
||||
val walletManager: WalletManager?
|
||||
val walletManager: WalletManager?,
|
||||
val save: Boolean,
|
||||
) : MultiWallet()
|
||||
|
||||
data class AddBlockchains(
|
||||
val blockchains: List<BlockchainNetwork>, val walletManagers: List<WalletManager>
|
||||
val blockchains: List<BlockchainNetwork>, val walletManagers: List<WalletManager>, val save: Boolean,
|
||||
) : MultiWallet()
|
||||
|
||||
data class AddTokens(val tokens: List<Token>, val blockchain: BlockchainNetwork) :
|
||||
data class AddTokens(val tokens: List<Token>, val blockchain: BlockchainNetwork, val save: Boolean) :
|
||||
MultiWallet()
|
||||
|
||||
data class AddToken(val token: Token, val blockchain: BlockchainNetwork) : MultiWallet()
|
||||
data class AddToken(val token: Token, val blockchain: BlockchainNetwork, val save: Boolean) : MultiWallet()
|
||||
data class SaveCurrencies(
|
||||
val blockchainNetworks: List<BlockchainNetwork>, val cardId: String? = null
|
||||
val blockchainNetworks: List<BlockchainNetwork>, val card: Card? = null,
|
||||
) : MultiWallet()
|
||||
// object FindTokensInUse : MultiWallet()
|
||||
// object FindBlockchainsInUse : MultiWallet()
|
||||
|
||||
data class TokenLoaded(
|
||||
val amount: Amount,
|
||||
|
|
@ -82,14 +84,15 @@ sealed class WalletAction : Action {
|
|||
data class TryToRemoveWallet(val currency: Currency) : MultiWallet()
|
||||
data class RemoveWallet(
|
||||
val currency: Currency,
|
||||
val fromScreen: AppScreen
|
||||
val fromScreen: AppScreen,
|
||||
) : MultiWallet()
|
||||
|
||||
data class SetPrimaryBlockchain(val blockchain: Blockchain) : MultiWallet()
|
||||
data class SetPrimaryToken(val token: Token) : MultiWallet()
|
||||
|
||||
data class ShowWalletBackupWarning(val show: Boolean) : MultiWallet()
|
||||
object BackupWallet : MultiWallet()
|
||||
data class AddMissingDerivations(val blockchains: List<BlockchainNetwork>) : MultiWallet()
|
||||
object ScanToGetDerivations : MultiWallet()
|
||||
}
|
||||
|
||||
sealed class Warnings : WalletAction() {
|
||||
|
|
|
|||
|
|
@ -19,9 +19,6 @@ import com.tangem.tap.features.wallet.models.PendingTransaction
|
|||
import com.tangem.tap.features.wallet.models.TotalBalance
|
||||
import com.tangem.tap.features.wallet.models.WalletRent
|
||||
import com.tangem.tap.features.wallet.models.WalletWarning
|
||||
import com.tangem.tap.features.wallet.models.hasPendingTransactions
|
||||
import com.tangem.tap.features.wallet.models.hasSendableAmounts
|
||||
import com.tangem.tap.features.wallet.models.isSendableAmount
|
||||
import com.tangem.tap.features.wallet.redux.reducers.calculateTotalFiatAmount
|
||||
import com.tangem.tap.features.wallet.redux.reducers.findProgressState
|
||||
import com.tangem.tap.features.wallet.ui.BalanceStatus
|
||||
|
|
@ -47,6 +44,8 @@ data class WalletState(
|
|||
val isTestnet: Boolean = false,
|
||||
val totalBalance: TotalBalance? = null,
|
||||
val showBackupWarning: Boolean = false,
|
||||
val missingDerivations: List<BlockchainNetwork> = emptyList(),
|
||||
val loadingUserTokens: Boolean = false,
|
||||
) : StateType {
|
||||
|
||||
// if you do not delegate - the application crashes on startup,
|
||||
|
|
@ -131,32 +130,6 @@ data class WalletState(
|
|||
return walletsData.find { it.currency == selectedCurrency }
|
||||
}
|
||||
|
||||
fun canBeRemoved(walletData: WalletData?): Boolean {
|
||||
if (walletData == null) return false
|
||||
|
||||
if (!isPrimaryCurrency(walletData)) {
|
||||
val walletManager = getWalletManager(walletData.currency)
|
||||
?: return true
|
||||
|
||||
if (walletData.currency is Currency.Blockchain &&
|
||||
walletManager.cardTokens.isNotEmpty()
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
val wallet = walletManager.wallet
|
||||
|
||||
return when (walletData.currency) {
|
||||
is Currency.Blockchain -> !wallet.hasPendingTransactions() && !wallet.hasSendableAmounts()
|
||||
is Currency.Token -> {
|
||||
val token = walletData.currency.token
|
||||
!wallet.hasPendingTransactions(token) && !wallet.isSendableAmount(token)
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun isPrimaryCurrency(walletData: WalletData): Boolean {
|
||||
return (walletData.currency is Currency.Blockchain &&
|
||||
walletData.currency.blockchain == store.state.walletState.primaryBlockchain)
|
||||
|
|
@ -220,21 +193,28 @@ data class WalletState(
|
|||
|
||||
fun removeWallet(walletData: WalletData?): WalletState {
|
||||
if (walletData == null) return this
|
||||
return if (walletData.currency is Currency.Blockchain) {
|
||||
val walletStores = wallets.filterNot {
|
||||
it.blockchainNetwork.blockchain == walletData.currency.blockchain
|
||||
&& it.blockchainNetwork.derivationPath == walletData.currency.derivationPath
|
||||
return when (val currency = walletData.currency) {
|
||||
is Currency.Blockchain -> {
|
||||
val walletStores = wallets.filterNot {
|
||||
it.blockchainNetwork.blockchain == currency.blockchain
|
||||
&& it.blockchainNetwork.derivationPath == currency.derivationPath
|
||||
}
|
||||
copy(wallets = walletStores)
|
||||
.updateTotalBalance()
|
||||
.updateProgressState()
|
||||
}
|
||||
is Currency.Token -> {
|
||||
val walletStore = getWalletStore(walletData.currency)
|
||||
val walletDataList = walletStore?.walletsData
|
||||
?.filterNot { it.currency == walletData.currency }
|
||||
?: emptyList()
|
||||
val updatedWalletManager = walletStore?.walletManager?.also { it.removeToken(currency.token) }
|
||||
val updatedWalletStore = walletStore?.copy(
|
||||
walletsData = walletDataList,
|
||||
walletManager = updatedWalletManager,
|
||||
)
|
||||
updateWalletStore(updatedWalletStore)
|
||||
}
|
||||
copy(wallets = walletStores)
|
||||
.updateTotalBalance()
|
||||
.updateProgressState()
|
||||
} else {
|
||||
val walletStore = getWalletStore(walletData.currency)
|
||||
val walletDataList = walletStore?.walletsData
|
||||
?.filterNot { it.currency == walletData.currency }
|
||||
?: emptyList()
|
||||
val updatedWalletStore = walletStore?.copy(walletsData = walletDataList)
|
||||
updateWalletStore(updatedWalletStore)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -380,8 +360,7 @@ data class WalletData(
|
|||
if (existentialDepositString != null) {
|
||||
val warning = WalletWarning.ExistentialDeposit(
|
||||
currencyName = currency.currencyName,
|
||||
currencySymbols = currency.currencySymbol,
|
||||
existentialDepositString = existentialDepositString,
|
||||
edStringValueWithSymbol = "$existentialDepositString ${currency.currencySymbol}",
|
||||
)
|
||||
walletWarnings.add(warning)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.tangem.blockchain.common.AmountType
|
|||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.extensions.dispatchErrorNotification
|
||||
import com.tangem.tap.common.extensions.safeUpdate
|
||||
|
|
@ -11,18 +12,20 @@ import com.tangem.tap.common.redux.global.GlobalAction
|
|||
import com.tangem.tap.common.redux.global.GlobalState
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.currenciesRepository
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.extensions.makeWalletManagerForApp
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.demo.isDemoCard
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.models.toCurrencies
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.redux.models.WalletDialog
|
||||
import com.tangem.tap.features.wallet.redux.reducers.toWallet
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.userTokensRepository
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
|
@ -44,41 +47,47 @@ class MultiWalletMiddleware {
|
|||
}
|
||||
}
|
||||
is WalletAction.MultiWallet.AddToken -> {
|
||||
addTokens(listOf(action.token), action.blockchain, walletState, globalState)
|
||||
addTokens(listOf(action.token), action.blockchain, walletState, globalState, action.save)
|
||||
}
|
||||
is WalletAction.MultiWallet.AddTokens -> {
|
||||
addTokens(action.tokens, action.blockchain, walletState, globalState)
|
||||
addTokens(action.tokens, action.blockchain, walletState, globalState, action.save)
|
||||
}
|
||||
is WalletAction.MultiWallet.AddBlockchain -> {
|
||||
action.walletManager?.let {
|
||||
handleAddingWalletManagers(globalState, listOf(action.walletManager))
|
||||
}
|
||||
val currencies: List<Currency> =
|
||||
(walletState?.currencies ?: emptyList()) + action.blockchain.toCurrencies()
|
||||
|
||||
globalState.scanResponse?.let {
|
||||
currenciesRepository.saveUpdatedCurrency(
|
||||
cardId = it.card.cardId,
|
||||
blockchainNetwork = action.blockchain
|
||||
)
|
||||
|
||||
if (action.save && globalState.scanResponse != null) {
|
||||
scope.launch {
|
||||
userTokensRepository.saveUserTokens(
|
||||
card = globalState.scanResponse.card,
|
||||
tokens = currencies,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
store.dispatch(
|
||||
WalletAction.LoadFiatRate(
|
||||
coinsList = listOf(
|
||||
Currency.Blockchain(
|
||||
action.blockchain.blockchain,
|
||||
action.blockchain.derivationPath
|
||||
)
|
||||
)
|
||||
)
|
||||
action.blockchain.derivationPath,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
store.dispatch(
|
||||
WalletAction.LoadWallet(
|
||||
action.blockchain, action.walletManager
|
||||
)
|
||||
action.blockchain, action.walletManager,
|
||||
),
|
||||
)
|
||||
}
|
||||
is WalletAction.MultiWallet.SaveCurrencies -> {
|
||||
val cardId = action.cardId ?: globalState.scanResponse?.card?.cardId ?: return
|
||||
currenciesRepository.saveCurrencies(cardId, action.blockchainNetworks)
|
||||
val card = action.card ?: globalState.scanResponse?.card ?: return
|
||||
scope.launch { userTokensRepository.saveUserTokens(card, action.blockchainNetworks.toCurrencies()) }
|
||||
}
|
||||
is WalletAction.MultiWallet.TryToRemoveWallet -> {
|
||||
val currency = action.currency
|
||||
|
|
@ -89,54 +98,44 @@ class MultiWalletMiddleware {
|
|||
}
|
||||
|
||||
if (currency.isBlockchain() && walletManager.cardTokens.isNotEmpty()) {
|
||||
store.dispatchDialogShow(WalletDialog.TokensAreLinkedDialog(
|
||||
currencyTitle = currency.currencyName,
|
||||
currencySymbol = currency.currencySymbol
|
||||
))
|
||||
store.dispatchDialogShow(
|
||||
WalletDialog.TokensAreLinkedDialog(
|
||||
currencyTitle = currency.currencyName,
|
||||
currencySymbol = currency.currencySymbol,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
store.dispatchDialogShow(WalletDialog.RemoveWalletDialog(
|
||||
currencyTitle = currency.currencyName,
|
||||
onOk = {
|
||||
store.dispatch(WalletAction.MultiWallet.RemoveWallet(
|
||||
currency = currency,
|
||||
fromScreen = AppScreen.WalletDetails
|
||||
))
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
}
|
||||
))
|
||||
store.dispatchDialogShow(
|
||||
WalletDialog.RemoveWalletDialog(
|
||||
currencyTitle = currency.currencyName,
|
||||
onOk = {
|
||||
store.dispatch(
|
||||
WalletAction.MultiWallet.RemoveWallet(
|
||||
currency = currency,
|
||||
fromScreen = AppScreen.WalletDetails,
|
||||
),
|
||||
)
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
is WalletAction.MultiWallet.RemoveWallet -> {
|
||||
val currency = action.currency
|
||||
val cardId = globalState.scanResponse?.card?.cardId.guard {
|
||||
store.dispatchErrorNotification(TapError.UnsupportedState("cardId is NULL"))
|
||||
val card = globalState.scanResponse?.card.guard {
|
||||
store.dispatchErrorNotification(TapError.UnsupportedState("card is NULL"))
|
||||
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
|
||||
return
|
||||
}
|
||||
|
||||
when (currency) {
|
||||
is Currency.Blockchain -> {
|
||||
currenciesRepository.removeBlockchain(
|
||||
cardId = cardId,
|
||||
blockchainNetwork = BlockchainNetwork(
|
||||
blockchain = currency.blockchain,
|
||||
derivationPath = currency.derivationPath,
|
||||
tokens = emptyList()
|
||||
)
|
||||
)
|
||||
}
|
||||
is Currency.Token -> {
|
||||
val walletManager = walletState?.getWalletManager(currency)
|
||||
if (walletManager != null) {
|
||||
walletManager.removeToken(currency.token)
|
||||
currenciesRepository.removeToken(
|
||||
cardId = cardId,
|
||||
token = currency.token,
|
||||
blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager)
|
||||
)
|
||||
}
|
||||
}
|
||||
var currencies = walletState?.currencies ?: emptyList()
|
||||
currencies = currencies.filterNot { it == currency }
|
||||
if (currency.isBlockchain()) {
|
||||
currencies
|
||||
.filter { it.blockchain == currency.blockchain && it.derivationPath == currency.derivationPath }
|
||||
}
|
||||
scope.launch { userTokensRepository.saveUserTokens(card, currencies) }
|
||||
|
||||
if (action.fromScreen == AppScreen.AddTokens) {
|
||||
store.dispatch(WalletAction.MultiWallet.SelectWallet(null))
|
||||
}
|
||||
|
|
@ -148,94 +147,9 @@ class MultiWalletMiddleware {
|
|||
store.dispatch(GlobalAction.Onboarding.Start(it, fromHomeScreen = false))
|
||||
}
|
||||
}
|
||||
// is WalletAction.MultiWallet.FindBlockchainsInUse -> {
|
||||
// val scanResponse = globalState.scanResponse ?: return
|
||||
// if (scanResponse.supportsHdWallet()) return
|
||||
//
|
||||
// val cardFirmware = scanResponse.card.firmwareVersion
|
||||
// val blockchains = currenciesRepository.getBlockchains(cardFirmware)
|
||||
// .filterNot { walletState?.blockchains?.contains(it) == true }
|
||||
// .map { BlockchainNetwork(it, null, emptyList()) }
|
||||
// val walletManagers =
|
||||
// tapWalletManager.walletManagerFactory.makeWalletManagersForApp(
|
||||
// scanResponse,
|
||||
// blockchains
|
||||
// )
|
||||
//
|
||||
// scope.launch {
|
||||
// walletManagers.map { walletManager ->
|
||||
// async(Dispatchers.IO) {
|
||||
// walletManager.safeUpdate()
|
||||
// val wallet = walletManager.wallet
|
||||
// val coinAmount = wallet.amounts[AmountType.Coin]?.value
|
||||
// if (coinAmount != null && !coinAmount.isZero()) {
|
||||
// scope.launch(Dispatchers.Main) {
|
||||
// val blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager)
|
||||
// if (walletState?.getWalletData(blockchainNetwork) == null) {
|
||||
// store.dispatch(WalletAction.MultiWallet.AddBlockchain(
|
||||
// blockchainNetwork, walletManager
|
||||
// ))
|
||||
// store.dispatch(WalletAction.LoadWallet.Success(
|
||||
// wallet = wallet,
|
||||
// blockchain = blockchainNetwork
|
||||
// ))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// is WalletAction.MultiWallet.FindTokensInUse -> {
|
||||
// val scanResponse = globalState.scanResponse ?: return
|
||||
// if (scanResponse.supportsHdWallet()) return
|
||||
//
|
||||
// val walletFactory = tapWalletManager.walletManagerFactory
|
||||
// val card = scanResponse.card
|
||||
//
|
||||
// val walletManager = walletState?.getWalletManager(
|
||||
// Currency.Blockchain(Blockchain.Ethereum, null)
|
||||
// )
|
||||
// ?: walletFactory.makeWalletManagerForApp(
|
||||
// scanResponse,
|
||||
// Currency.Blockchain(Blockchain.Ethereum, null)
|
||||
// )
|
||||
//
|
||||
// val tokenFinder = walletManager as? TokenFinder ?: return
|
||||
// scope.launch {
|
||||
// val result = tokenFinder.findTokens()
|
||||
//
|
||||
// withContext(Dispatchers.Main) {
|
||||
// when (result) {
|
||||
// is Result.Success -> {
|
||||
// if (result.data.isNotEmpty()) {
|
||||
// val blockchainNetwork = BlockchainNetwork(
|
||||
// walletManager.wallet.blockchain,
|
||||
// walletManager.wallet.publicKey.derivationPath?.rawPath,
|
||||
// walletManager.cardTokens.toList()
|
||||
// )
|
||||
// currenciesRepository.saveUpdatedCurrency(
|
||||
// card.cardId,
|
||||
// blockchainNetwork
|
||||
// )
|
||||
// store.dispatch(
|
||||
// WalletAction.MultiWallet.AddBlockchain(
|
||||
// blockchainNetwork,
|
||||
// walletManager
|
||||
// )
|
||||
// )
|
||||
// store.dispatch(
|
||||
// WalletAction.MultiWallet.AddTokens(
|
||||
// walletManager.cardTokens.toList(),
|
||||
// blockchainNetwork
|
||||
// )
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
is WalletAction.MultiWallet.ScanToGetDerivations -> {
|
||||
store.dispatch(WalletAction.Scan)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -249,7 +163,7 @@ class MultiWalletMiddleware {
|
|||
|
||||
private fun handleAddingWalletManagers(
|
||||
globalState: GlobalState,
|
||||
walletManagers: List<WalletManager>
|
||||
walletManagers: List<WalletManager>,
|
||||
) {
|
||||
globalState.feedbackManager?.infoHolder?.setWalletsInfo(walletManagers)
|
||||
if (globalState.scanResponse?.isDemoCard() == true) {
|
||||
|
|
@ -259,50 +173,59 @@ class MultiWalletMiddleware {
|
|||
|
||||
private fun addTokens(
|
||||
tokens: List<Token>, blockchainNetwork: BlockchainNetwork,
|
||||
walletState: WalletState?, globalState: GlobalState?
|
||||
walletState: WalletState?, globalState: GlobalState?,
|
||||
save: Boolean,
|
||||
) {
|
||||
if (tokens.isEmpty()) return
|
||||
val scanResponse = globalState?.scanResponse ?: return
|
||||
val wmFactory = globalState.tapWalletManager.walletManagerFactory
|
||||
val walletState = walletState ?: return
|
||||
val walletManager = walletState.getWalletManager(blockchainNetwork)?.also {
|
||||
if (save) {
|
||||
val wallets = tokens.mapNotNull { token -> token.toWallet(walletState, blockchainNetwork) }
|
||||
val currencies = walletState.updateWalletsData(wallets).currencies
|
||||
scope.launch { userTokensRepository.saveUserTokens(scanResponse.card, currencies) }
|
||||
}
|
||||
} ?: wmFactory.makeWalletManagerForApp(scanResponse, blockchainNetwork)?.also {
|
||||
store.dispatch(WalletAction.MultiWallet.AddBlockchain(blockchainNetwork.updateTokens(tokens), it, save))
|
||||
}
|
||||
|
||||
val walletManager = walletState?.getWalletManager(blockchainNetwork)
|
||||
?: wmFactory.makeWalletManagerForApp(scanResponse, blockchainNetwork)?.also {
|
||||
store.dispatch(WalletAction.MultiWallet.AddBlockchain(blockchainNetwork, it))
|
||||
} ?: return
|
||||
|
||||
store.dispatch(WalletAction.LoadFiatRate(coinsList = tokens.map { token ->
|
||||
Currency.Token(
|
||||
token, blockchainNetwork.blockchain, blockchainNetwork.derivationPath
|
||||
)
|
||||
}))
|
||||
if (tokens.isNotEmpty()) walletManager.addTokens(tokens)
|
||||
|
||||
currenciesRepository.saveUpdatedCurrency(
|
||||
cardId = scanResponse.card.cardId,
|
||||
blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager)
|
||||
store.dispatch(
|
||||
WalletAction.LoadFiatRate(
|
||||
coinsList = tokens.map { token ->
|
||||
Currency.Token(
|
||||
token, blockchainNetwork.blockchain, blockchainNetwork.derivationPath,
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
if (tokens.isNotEmpty()) walletManager?.addTokens(tokens)
|
||||
|
||||
scope.launch {
|
||||
when (val result = walletManager.safeUpdate()) {
|
||||
is com.tangem.common.services.Result.Success -> {
|
||||
val wallet = result.data
|
||||
wallet.getTokens()
|
||||
.filter { tokens.contains(it) }
|
||||
.mapNotNull { token ->
|
||||
wallet.getTokenAmount(token)?.let { Pair(token, it) }
|
||||
}
|
||||
.forEach {
|
||||
withContext(Dispatchers.Main) {
|
||||
store.dispatch(
|
||||
WalletAction.MultiWallet.TokenLoaded(
|
||||
it.second,
|
||||
it.first,
|
||||
blockchainNetwork
|
||||
)
|
||||
)
|
||||
val result = walletManager?.safeUpdate()
|
||||
withMainContext {
|
||||
when (result) {
|
||||
is com.tangem.common.services.Result.Success -> {
|
||||
val wallet = result.data
|
||||
wallet.getTokens()
|
||||
.filter { tokens.contains(it) }
|
||||
.mapNotNull { token ->
|
||||
wallet.getTokenAmount(token)?.let { Pair(token, it) }
|
||||
}
|
||||
}
|
||||
.forEach {
|
||||
withContext(Dispatchers.Main) {
|
||||
store.dispatch(
|
||||
WalletAction.MultiWallet.TokenLoaded(
|
||||
it.second,
|
||||
it.first,
|
||||
blockchainNetwork,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
is com.tangem.common.services.Result.Failure -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,10 +108,13 @@ class WalletMiddleware {
|
|||
val coinAmount = action.wallet.amounts[AmountType.Coin]?.value
|
||||
if (coinAmount != null && !coinAmount.isZero()) {
|
||||
if (walletState.getWalletData(action.blockchain) == null) {
|
||||
store.dispatch(WalletAction.MultiWallet.AddBlockchain(
|
||||
action.blockchain,
|
||||
null
|
||||
))
|
||||
store.dispatch(
|
||||
WalletAction.MultiWallet.AddBlockchain(
|
||||
action.blockchain,
|
||||
null,
|
||||
true,
|
||||
),
|
||||
)
|
||||
store.dispatch(WalletAction.LoadWallet.Success(
|
||||
action.wallet,
|
||||
action.blockchain
|
||||
|
|
|
|||
|
|
@ -34,11 +34,10 @@ class MultiWalletReducer {
|
|||
val walletManager = action.walletManagers.firstOrNull {
|
||||
it.wallet.blockchain == blockchain.blockchain &&
|
||||
(it.wallet.publicKey.derivationPath?.rawPath == blockchain.derivationPath)
|
||||
} ?: return@mapNotNull null
|
||||
|
||||
val wallet = walletManager.wallet
|
||||
}
|
||||
val wallet = walletManager?.wallet
|
||||
val cardToken = if (!state.isMultiwalletAllowed) {
|
||||
wallet.getFirstToken()?.symbol?.let { TokenData("", tokenSymbol = it) }
|
||||
wallet?.getFirstToken()?.symbol?.let { TokenData("", tokenSymbol = it) }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
|
@ -66,7 +65,7 @@ class MultiWalletReducer {
|
|||
}
|
||||
|
||||
val selectedCurrency = if (!state.isMultiwalletAllowed) {
|
||||
wallets[0].walletsData[0].currency
|
||||
wallets.firstOrNull()?.walletsData?.firstOrNull()?.currency
|
||||
} else {
|
||||
state.selectedCurrency
|
||||
}
|
||||
|
|
@ -168,9 +167,11 @@ class MultiWalletReducer {
|
|||
state.copy(primaryToken = action.token)
|
||||
is WalletAction.MultiWallet.SaveCurrencies -> state
|
||||
is WalletAction.MultiWallet.ShowWalletBackupWarning -> state.copy(
|
||||
showBackupWarning = action.show
|
||||
showBackupWarning = action.show,
|
||||
)
|
||||
is WalletAction.MultiWallet.AddMissingDerivations -> state.copy(missingDerivations = action.blockchains)
|
||||
is WalletAction.MultiWallet.BackupWallet -> state
|
||||
is WalletAction.MultiWallet.ScanToGetDerivations -> state
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -146,18 +146,19 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
BalanceStatus.Loading
|
||||
}
|
||||
if (action.blockchain == null) {
|
||||
val wallets = newState.wallets.map {
|
||||
it.copy(
|
||||
walletsData = it.walletsData.map { walletData ->
|
||||
val wallets = newState.wallets.map { walletStore ->
|
||||
walletStore.copy(
|
||||
walletsData = walletStore.walletsData.map { walletData ->
|
||||
walletData.copy(
|
||||
currencyData = walletData.currencyData.copy(
|
||||
status = balanceStatus,
|
||||
status =
|
||||
if (walletStore.walletManager != null) balanceStatus else BalanceStatus.Unreachable,
|
||||
currency = walletData.currencyData.currency,
|
||||
currencySymbol = walletData.currencyData.currencySymbol,
|
||||
),
|
||||
mainButton = WalletMainButton.SendButton(false),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
newState = newState.copy(
|
||||
|
|
@ -314,6 +315,8 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
is WalletAction.AppCurrencyAction -> {
|
||||
newState = appCurrencyReducer.reduce(action, newState)
|
||||
}
|
||||
is WalletAction.UserTokens.Loading -> newState = newState.copy(loadingUserTokens = true)
|
||||
is WalletAction.UserTokens.Loaded -> newState = newState.copy(loadingUserTokens = false)
|
||||
else -> { /* no-op */
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ class WalletWarningConverter(
|
|||
is WalletWarning.ExistentialDeposit -> {
|
||||
context.getString(
|
||||
R.string.warning_existential_deposit_message,
|
||||
message.currencyName, message.currencySymbols, message.existentialDepositString,
|
||||
message.currencyName, message.edStringValueWithSymbol,
|
||||
)
|
||||
}
|
||||
is WalletWarning.BalanceNotEnoughForFee -> {
|
||||
|
|
|
|||
|
|
@ -110,8 +110,12 @@ class WalletAdapter
|
|||
lContent.tvExchangeRate.text = wallet.fiatRateString
|
||||
?: root.getString(id = R.string.token_item_no_rate)
|
||||
|
||||
cardWallet.setOnClickListener {
|
||||
store.dispatch(WalletAction.MultiWallet.SelectWallet(wallet))
|
||||
if (wallet.walletAddresses != null) {
|
||||
cardWallet.setOnClickListener {
|
||||
store.dispatch(WalletAction.MultiWallet.SelectWallet(wallet))
|
||||
}
|
||||
} else {
|
||||
cardWallet.setOnClickListener(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import com.tangem.tap.common.extensions.hide
|
|||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.currenciesRepository
|
||||
import com.tangem.tap.domain.tokens.CurrenciesRepository
|
||||
import com.tangem.tap.features.tokens.redux.TokensAction
|
||||
import com.tangem.tap.features.wallet.models.TotalBalance
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
|
|
@ -78,13 +78,16 @@ class MultiWalletView : WalletView() {
|
|||
|
||||
handleTotalBalance(binding, state.totalBalance)
|
||||
handleBackupWarning(binding, state.showBackupWarning)
|
||||
handleRescanWarning(binding, state.missingDerivations.isNotEmpty())
|
||||
walletsAdapter.submitList(state.walletsData, state.primaryBlockchain, state.primaryToken)
|
||||
|
||||
binding.pbLoadingUserTokens.show(state.loadingUserTokens)
|
||||
|
||||
binding.btnAddToken.setOnClickListener {
|
||||
val card = store.state.globalState.scanResponse!!.card
|
||||
store.dispatch(
|
||||
TokensAction.LoadCurrencies(
|
||||
supportedBlockchains = currenciesRepository.getBlockchains(
|
||||
supportedBlockchains = CurrenciesRepository.getBlockchains(
|
||||
card.firmwareVersion,
|
||||
card.isTestCard,
|
||||
),
|
||||
|
|
@ -113,6 +116,16 @@ class MultiWalletView : WalletView() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleRescanWarning(
|
||||
binding: FragmentWalletBinding,
|
||||
showRescanWarning: Boolean,
|
||||
) = with(binding.lWalletRescanWarning) {
|
||||
root.isVisible = showRescanWarning
|
||||
root.setOnClickListener {
|
||||
store.dispatch(WalletAction.MultiWallet.ScanToGetDerivations)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleTotalBalance(
|
||||
binding: FragmentWalletBinding,
|
||||
totalBalance: TotalBalance?,
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ class SaltPaySingleWalletView : WalletView() {
|
|||
rowButtons.hide()
|
||||
rvPendingTransaction.hide()
|
||||
tvTwinCardNumber.hide()
|
||||
pbLoadingUserTokens.hide()
|
||||
lCardBalance.root.hide()
|
||||
lAddress.root.hide()
|
||||
lSingleWalletBalance.root.show()
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
|||
import com.tangem.domain.common.TwinCardNumber
|
||||
import com.tangem.tap.common.extensions.beginDelayedTransition
|
||||
import com.tangem.tap.common.extensions.fitChipsByGroupWidth
|
||||
import com.tangem.tap.common.extensions.getQuantityString
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState
|
||||
|
|
@ -36,6 +37,7 @@ class SingleWalletView : WalletView() {
|
|||
rvMultiwallet.hide()
|
||||
btnAddToken.hide()
|
||||
rvPendingTransaction.hide()
|
||||
pbLoadingUserTokens.hide()
|
||||
lCardBalance.root.show()
|
||||
lAddress.root.show()
|
||||
lSingleWalletBalance.root.hide()
|
||||
|
|
@ -85,11 +87,7 @@ class SingleWalletView : WalletView() {
|
|||
private fun setupTwinCards(twinCardsState: TwinCardsState?, binding: FragmentWalletBinding) = with(binding) {
|
||||
twinCardsState?.cardNumber?.let { cardNumber ->
|
||||
tvTwinCardNumber.show()
|
||||
val number = when (cardNumber) {
|
||||
TwinCardNumber.First -> 1
|
||||
TwinCardNumber.Second -> 2
|
||||
}
|
||||
tvTwinCardNumber.text = fragment?.getString(R.string.wallet_twins_chip_format, number, 2)
|
||||
tvTwinCardNumber.text = tvTwinCardNumber.getQuantityString(R.plurals.card_label_card_count, 2)
|
||||
}
|
||||
if (twinCardsState?.cardNumber == null) {
|
||||
tvTwinCardNumber.hide()
|
||||
|
|
|
|||
|
|
@ -76,7 +76,12 @@ class MercuryoService(
|
|||
if (!isBuyAllowed()) return false
|
||||
|
||||
// blockchains which cant be defined by mercuryo service
|
||||
val unsupportedBlockchains = listOf(Blockchain.Unknown, Blockchain.Binance, Blockchain.Arbitrum)
|
||||
val unsupportedBlockchains = listOf(
|
||||
Blockchain.Unknown,
|
||||
Blockchain.Binance,
|
||||
Blockchain.Arbitrum,
|
||||
Blockchain.Optimism,
|
||||
)
|
||||
val blockchain = currency.blockchain
|
||||
|
||||
return when (currency) {
|
||||
|
|
|
|||
40
app/src/main/res/drawable/ic_ethereumfair_no_color.xml
Normal file
40
app/src/main/res/drawable/ic_ethereumfair_no_color.xml
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="22dp"
|
||||
android:height="22dp"
|
||||
android:autoMirrored="true"
|
||||
android:viewportWidth="22"
|
||||
android:viewportHeight="22">
|
||||
<group>
|
||||
<clip-path android:pathData="M0,0h22v22h-22z" />
|
||||
<path
|
||||
android:fillColor="#F1F1F1"
|
||||
android:pathData="M11,22C17.075,22 22,17.075 22,11C22,4.925 17.075,0 11,0C4.925,0 0,4.925 0,11C0,17.075 4.925,22 11,22Z" />
|
||||
<path
|
||||
android:fillColor="#A1A1A4"
|
||||
android:pathData="M10.75,3.737L10.75,5.243L7.014,10.538L6.243,10.081L10.75,3.737Z" />
|
||||
<path
|
||||
android:fillColor="#A1A1A4"
|
||||
android:pathData="M7.445,10.793L10.75,6.111L10.749,12.751L7.445,10.793Z" />
|
||||
<path
|
||||
android:fillColor="#A1A1A4"
|
||||
android:pathData="M11.25,5.243L11.25,3.739L15.757,10.081L15.057,10.495L11.25,5.243Z" />
|
||||
<path
|
||||
android:fillColor="#A1A1A4"
|
||||
android:pathData="M11.25,6.095L11.249,12.752L14.625,10.752L11.25,6.095Z" />
|
||||
<path
|
||||
android:fillColor="#A1A1A4"
|
||||
android:pathData="M10.749,15.656L10.749,17.039L9.272,14.739L10.749,15.656Z" />
|
||||
<path
|
||||
android:fillColor="#A1A1A4"
|
||||
android:pathData="M11.249,17.035L11.249,15.657L12.701,14.756L11.249,17.035Z" />
|
||||
<path
|
||||
android:fillColor="#A1A1A4"
|
||||
android:pathData="M13.681,14.148L14.915,13.382L11.25,18.88L11.25,17.966L13.681,14.148Z" />
|
||||
<path
|
||||
android:fillColor="#A1A1A4"
|
||||
android:pathData="M10.75,18.879L10.75,17.965L8.29,14.135L8.284,14.126L7.085,13.382L10.75,18.879Z" />
|
||||
<path
|
||||
android:fillColor="#A1A1A4"
|
||||
android:pathData="M15.559,12.393L13.718,11.78L11,13.479L8.282,11.78L6.44,12.394L10.072,14.647L11,15.177L11.927,14.648L15.559,12.393Z" />
|
||||
</group>
|
||||
</vector>
|
||||
40
app/src/main/res/drawable/ic_ethereumfair_round.xml
Normal file
40
app/src/main/res/drawable/ic_ethereumfair_round.xml
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="22dp"
|
||||
android:height="22dp"
|
||||
android:autoMirrored="true"
|
||||
android:viewportWidth="22"
|
||||
android:viewportHeight="22">
|
||||
<group>
|
||||
<clip-path android:pathData="M0,0h22v22h-22z" />
|
||||
<path
|
||||
android:fillColor="#7F90F8"
|
||||
android:pathData="M11,22C17.075,22 22,17.075 22,11C22,4.925 17.075,0 11,0C4.925,0 0,4.925 0,11C0,17.075 4.925,22 11,22Z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M10.75,3.737L10.75,5.243L7.014,10.538L6.243,10.081L10.75,3.737Z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M7.445,10.793L10.75,6.111L10.749,12.751L7.445,10.793Z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M11.25,5.243L11.25,3.739L15.757,10.081L15.057,10.495L11.25,5.243Z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M11.25,6.095L11.249,12.752L14.625,10.752L11.25,6.095Z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M10.749,15.656L10.749,17.039L9.272,14.739L10.749,15.656Z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M11.249,17.035L11.249,15.657L12.701,14.756L11.249,17.035Z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M13.681,14.148L14.915,13.382L11.25,18.88L11.25,17.966L13.681,14.148Z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M10.75,18.879L10.75,17.965L8.29,14.135L8.284,14.126L7.085,13.382L10.75,18.879Z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M15.559,12.393L13.718,11.78L11,13.479L8.282,11.78L6.44,12.394L10.072,14.647L11,15.177L11.927,14.648L15.559,12.393Z" />
|
||||
</group>
|
||||
</vector>
|
||||
31
app/src/main/res/drawable/ic_ethereumpow_no_color.xml
Normal file
31
app/src/main/res/drawable/ic_ethereumpow_no_color.xml
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="22dp"
|
||||
android:height="22dp"
|
||||
android:autoMirrored="true"
|
||||
android:viewportWidth="22"
|
||||
android:viewportHeight="22">
|
||||
<group>
|
||||
<clip-path android:pathData="M0,0h22v22h-22z" />
|
||||
<path
|
||||
android:fillColor="#F1F1F1"
|
||||
android:pathData="M11,22C17.075,22 22,17.075 22,11C22,4.925 17.075,0 11,0C4.925,0 0,4.925 0,11C0,17.075 4.925,22 11,22Z" />
|
||||
<group>
|
||||
<clip-path android:pathData="M11,3.85L11,3.85A4.4,4.4 0,0 1,15.4 8.25L15.4,13.75A4.4,4.4 0,0 1,11 18.15L11,18.15A4.4,4.4 0,0 1,6.6 13.75L6.6,8.25A4.4,4.4 0,0 1,11 3.85z" />
|
||||
<path
|
||||
android:fillColor="#A1A1A4"
|
||||
android:pathData="M7.609,9.46L6.6,11.131L8.414,12.201C8.448,12.033 8.538,11.885 8.664,11.778L8.008,9.483C7.957,9.494 7.904,9.5 7.85,9.5C7.765,9.5 7.684,9.486 7.609,9.46Z" />
|
||||
<path
|
||||
android:fillColor="#A1A1A4"
|
||||
android:pathData="M8.366,8.206C8.51,8.343 8.6,8.536 8.6,8.75C8.6,9.021 8.456,9.258 8.241,9.39L8.886,11.647C8.968,11.617 9.057,11.6 9.149,11.6C9.22,11.6 9.289,11.61 9.354,11.628L10.487,9.647C10.341,9.51 10.25,9.316 10.25,9.1C10.25,8.686 10.586,8.35 11,8.35C11.414,8.35 11.75,8.686 11.75,9.1C11.75,9.316 11.659,9.51 11.514,9.647L12.646,11.628C12.71,11.61 12.779,11.6 12.85,11.6C12.943,11.6 13.031,11.617 13.113,11.648L13.759,9.39C13.543,9.258 13.399,9.021 13.399,8.75C13.399,8.537 13.488,8.344 13.631,8.208L10.998,3.85L8.366,8.206Z" />
|
||||
<path
|
||||
android:fillColor="#A1A1A4"
|
||||
android:pathData="M14.388,9.461C14.313,9.486 14.233,9.5 14.149,9.5C14.095,9.5 14.043,9.494 13.992,9.483L13.336,11.779C13.461,11.886 13.551,12.033 13.585,12.2L15.397,11.131L14.388,9.461Z" />
|
||||
<path
|
||||
android:fillColor="#A1A1A4"
|
||||
android:pathData="M12.363,12.92C12.202,12.783 12.1,12.578 12.1,12.35C12.1,12.096 12.226,11.871 12.419,11.736L11.305,9.785C11.212,9.827 11.109,9.85 11,9.85C10.892,9.85 10.788,9.827 10.695,9.785L9.58,11.736C9.773,11.872 9.899,12.096 9.899,12.35C9.899,12.579 9.797,12.784 9.635,12.921L10.998,13.725L12.363,12.92Z" />
|
||||
<path
|
||||
android:fillColor="#A1A1A4"
|
||||
android:pathData="M10.998,18.146L15.4,11.963L10.998,14.556L6.6,11.963L10.998,18.146Z" />
|
||||
</group>
|
||||
</group>
|
||||
</vector>
|
||||
166
app/src/main/res/drawable/ic_ethereumpow_round.xml
Normal file
166
app/src/main/res/drawable/ic_ethereumpow_round.xml
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="22dp"
|
||||
android:height="22dp"
|
||||
android:autoMirrored="true"
|
||||
android:viewportWidth="22"
|
||||
android:viewportHeight="22">
|
||||
<group>
|
||||
<clip-path android:pathData="M0,0h22v22h-22z" />
|
||||
<path
|
||||
android:fillColor="#F6F5FF"
|
||||
android:pathData="M11,22C17.075,22 22,17.075 22,11C22,4.925 17.075,0 11,0C4.925,0 0,4.925 0,11C0,17.075 4.925,22 11,22Z" />
|
||||
<group>
|
||||
<clip-path android:pathData="M11,3.85L11,3.85A4.4,4.4 0,0 1,15.4 8.25L15.4,13.75A4.4,4.4 0,0 1,11 18.15L11,18.15A4.4,4.4 0,0 1,6.6 13.75L6.6,8.25A4.4,4.4 0,0 1,11 3.85z" />
|
||||
<path android:pathData="M10.998,3.85L10.902,4.176V13.629L10.998,13.725L15.397,11.131L10.998,3.85Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="13.5"
|
||||
android:endY="12.5"
|
||||
android:startX="11.5"
|
||||
android:startY="6.5"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#FFE1F0F3"
|
||||
android:offset="0" />
|
||||
<item
|
||||
android:color="#FFB3DBF5"
|
||||
android:offset="1" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path android:pathData="M10.998,3.85L6.6,11.131L10.998,13.725V9.136V3.85Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="6.5"
|
||||
android:endY="11"
|
||||
android:startX="10.75"
|
||||
android:startY="4"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#FFE1F0F3"
|
||||
android:offset="0.161" />
|
||||
<item
|
||||
android:color="#FFA795F2"
|
||||
android:offset="1" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#C6C7FA"
|
||||
android:pathData="M10.998,14.556L10.944,14.622V17.989L10.998,18.147L15.4,11.963L10.998,14.556Z" />
|
||||
<path
|
||||
android:fillColor="#6E89D5"
|
||||
android:pathData="M10.998,18.147V14.556L6.6,11.963L10.998,18.147Z" />
|
||||
<path
|
||||
android:fillColor="#F2F6F4"
|
||||
android:pathData="M10.998,13.725L15.397,11.131L10.998,9.137V13.725Z" />
|
||||
<path android:pathData="M6.6,11.131L10.998,13.725V9.137L6.6,11.131Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="9"
|
||||
android:endY="12.5"
|
||||
android:startX="11"
|
||||
android:startY="9"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#FFDDE1F9"
|
||||
android:offset="0" />
|
||||
<item
|
||||
android:color="#FFD7D1FA"
|
||||
android:offset="0.993" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#B2B6E9"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M7.966,8.88C8.032,8.861 8.101,8.899 8.12,8.966L9.039,12.18L10.805,9.09C10.891,8.939 11.109,8.939 11.195,9.09L12.961,12.18L13.88,8.966C13.899,8.899 13.968,8.861 14.034,8.88C14.101,8.899 14.139,8.968 14.12,9.034L13.185,12.306C13.131,12.498 12.873,12.529 12.774,12.356L11,9.252L9.226,12.356C9.128,12.529 8.869,12.498 8.815,12.306L7.88,9.034C7.861,8.968 7.899,8.899 7.966,8.88Z" />
|
||||
<path android:pathData="M11.75,9.1C11.75,9.514 11.414,9.85 11,9.85C10.586,9.85 10.25,9.514 10.25,9.1C10.25,8.686 10.586,8.35 11,8.35C11.414,8.35 11.75,8.686 11.75,9.1Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="11"
|
||||
android:endY="9.85"
|
||||
android:startX="11"
|
||||
android:startY="8.35"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#FFC4C9F4"
|
||||
android:offset="0" />
|
||||
<item
|
||||
android:color="#FF859ADC"
|
||||
android:offset="1" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path android:pathData="M8.6,8.75C8.6,9.164 8.264,9.5 7.85,9.5C7.435,9.5 7.1,9.164 7.1,8.75C7.1,8.336 7.435,8 7.85,8C8.264,8 8.6,8.336 8.6,8.75Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="7.85"
|
||||
android:endY="9.5"
|
||||
android:startX="7.85"
|
||||
android:startY="8"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#FFC4C9F4"
|
||||
android:offset="0" />
|
||||
<item
|
||||
android:color="#FF859ADC"
|
||||
android:offset="1" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path android:pathData="M14.899,8.75C14.899,9.164 14.564,9.5 14.149,9.5C13.735,9.5 13.399,9.164 13.399,8.75C13.399,8.336 13.735,8 14.149,8C14.564,8 14.899,8.336 14.899,8.75Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="14.149"
|
||||
android:endY="9.5"
|
||||
android:startX="14.149"
|
||||
android:startY="8"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#FFC4C9F4"
|
||||
android:offset="0" />
|
||||
<item
|
||||
android:color="#FF859ADC"
|
||||
android:offset="1" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path android:pathData="M13.6,12.35C13.6,12.764 13.264,13.1 12.85,13.1C12.435,13.1 12.1,12.764 12.1,12.35C12.1,11.936 12.435,11.6 12.85,11.6C13.264,11.6 13.6,11.936 13.6,12.35Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="12.85"
|
||||
android:endY="13.1"
|
||||
android:startX="12.85"
|
||||
android:startY="11.6"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#FFC4C9F4"
|
||||
android:offset="0" />
|
||||
<item
|
||||
android:color="#FF859ADC"
|
||||
android:offset="1" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path android:pathData="M9.899,12.35C9.899,12.764 9.564,13.1 9.149,13.1C8.735,13.1 8.399,12.764 8.399,12.35C8.399,11.936 8.735,11.6 9.149,11.6C9.564,11.6 9.899,11.936 9.899,12.35Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="9.149"
|
||||
android:endY="13.1"
|
||||
android:startX="9.149"
|
||||
android:startY="11.6"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#FFC4C9F4"
|
||||
android:offset="0" />
|
||||
<item
|
||||
android:color="#FF859ADC"
|
||||
android:offset="1" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
</group>
|
||||
</group>
|
||||
</vector>
|
||||
21
app/src/main/res/drawable/ic_scan_card.xml
Normal file
21
app/src/main/res/drawable/ic_scan_card.xml
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="42dp"
|
||||
android:height="42dp"
|
||||
android:autoMirrored="true"
|
||||
android:viewportWidth="42"
|
||||
android:viewportHeight="42">
|
||||
<path
|
||||
android:fillColor="#F4F5F5"
|
||||
android:pathData="M21,21m-21,0a21,21 0,1 1,42 0a21,21 0,1 1,-42 0" />
|
||||
<group>
|
||||
<clip-path android:pathData="M21,42C32.598,42 42,32.598 42,21C42,9.402 32.598,0 21,0C9.402,0 0,9.402 0,21C0,32.598 9.402,42 21,42Z" />
|
||||
<path
|
||||
android:fillColor="#0F1820"
|
||||
android:pathData="M0,8L27,8A4,4 0,0 1,31 12L31,30A4,4 0,0 1,27 34L0,34A4,4 0,0 1,-4 30L-4,12A4,4 0,0 1,0 8z" />
|
||||
<path
|
||||
android:fillAlpha="0.3"
|
||||
android:fillColor="#ffffff"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M9.047,27H24.954C25.231,27 25.497,27.105 25.694,27.293C25.89,27.48 26,27.735 26,28C26,28.265 25.89,28.52 25.694,28.707C25.497,28.895 25.231,29 24.954,29H9.047C8.769,29 8.503,28.895 8.307,28.707C8.11,28.52 8,28.265 8,28C8,27.735 8.11,27.48 8.307,27.293C8.503,27.105 8.769,27 9.047,27Z" />
|
||||
</group>
|
||||
</vector>
|
||||
24
app/src/main/res/drawable/ic_tangem_warning.xml
Normal file
24
app/src/main/res/drawable/ic_tangem_warning.xml
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="44dp"
|
||||
android:height="44dp"
|
||||
android:autoMirrored="true"
|
||||
android:viewportWidth="44"
|
||||
android:viewportHeight="44">
|
||||
<path
|
||||
android:fillColor="#F5F5F5"
|
||||
android:pathData="M20,24m-20,0a20,20 0,1 1,40 0a20,20 0,1 1,-40 0" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M13.333,17.25C13.333,16.836 13.684,16.5 14.118,16.5H25.882C26.316,16.5 26.667,16.836 26.667,17.25V21H13.333V17.25Z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M13.333,24H18.039V31.5H14.118C13.684,31.5 13.333,31.164 13.333,30.75V24Z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M26.667,24H21.961V31.5H25.882C26.316,31.5 26.667,31.164 26.667,30.75V24Z" />
|
||||
<path
|
||||
android:fillColor="#FFB71B"
|
||||
android:pathData="M36,8m-6,0a6,6 0,1 1,12 0a6,6 0,1 1,-12 0"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff" />
|
||||
</vector>
|
||||
|
|
@ -92,17 +92,6 @@
|
|||
app:barrierDirection="bottom"
|
||||
app:constraint_referenced_ids="iv_card,tv_twin_card_number" />
|
||||
|
||||
<include
|
||||
android:id="@+id/l_wallet_backup_warning"
|
||||
layout="@layout/layout_wallet_backup_warning"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintTop_toBottomOf="@id/rv_warning_messages" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rv_warning_messages"
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -115,16 +104,47 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/barrier" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_warnings"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="8dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/rv_warning_messages">
|
||||
|
||||
<include
|
||||
android:id="@+id/l_wallet_rescan_warning"
|
||||
layout="@layout/layout_wallet_rescan_warning"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<include
|
||||
android:id="@+id/l_wallet_backup_warning"
|
||||
layout="@layout/layout_wallet_backup_warning"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<include
|
||||
android:id="@+id/l_card_total_balance"
|
||||
layout="@layout/layout_card_total_balance"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintTop_toBottomOf="@id/l_wallet_backup_warning"
|
||||
app:layout_constraintTop_toBottomOf="@id/ll_warnings"
|
||||
app:layout_goneMarginTop="16dp" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
|
|
@ -170,11 +190,31 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:nestedScrollingEnabled="false"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/rv_pending_transaction" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/pb_loading_user_tokens"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminate="true"
|
||||
android:indeterminateTint="@color/icon_accent"
|
||||
android:indeterminateTintMode="src_atop"
|
||||
android:nestedScrollingEnabled="false"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/rv_pending_transaction" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/barrier_wallets"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="bottom"
|
||||
app:constraint_referenced_ids="rv_multiwallet,pb_loading_user_tokens" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_add_token"
|
||||
style="@style/BaseTapButton"
|
||||
|
|
@ -189,7 +229,7 @@
|
|||
android:visibility="gone"
|
||||
app:cornerRadius="8dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/rv_multiwallet"
|
||||
app:layout_constraintTop_toBottomOf="@id/barrier_wallets"
|
||||
app:layout_constraintVertical_bias="0.0"
|
||||
tools:layout_editor_absoluteX="16dp" />
|
||||
|
||||
|
|
|
|||
70
app/src/main/res/layout/layout_wallet_rescan_warning.xml
Normal file
70
app/src/main/res/layout/layout_wallet_rescan_warning.xml
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingBottom="12dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_rescan_warning_icon"
|
||||
android:layout_width="42dp"
|
||||
android:layout_height="42dp"
|
||||
android:background="@drawable/shape_ellipse"
|
||||
android:backgroundTint="@color/buttonGray"
|
||||
android:contentDescription="@null"
|
||||
android:paddingBottom="4dp"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_tangem_warning"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_rescan_warning_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/main_scan_card_warning_view_title"
|
||||
android:textColor="@color/text_primary_1"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toTopOf="@id/tv_rescan_warning_subtitle"
|
||||
app:layout_constraintEnd_toStartOf="@id/iv_rescan_warning_end_icon"
|
||||
app:layout_constraintStart_toEndOf="@id/iv_rescan_warning_icon"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_rescan_warning_subtitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/main_scan_card_warning_view_subtitle"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="14sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@id/tv_rescan_warning_title"
|
||||
app:layout_constraintStart_toStartOf="@id/tv_rescan_warning_title"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_rescan_warning_title" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_rescan_warning_end_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_arrow_angle_right"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:tint="@color/darkGray1" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
|
@ -18,10 +18,6 @@
|
|||
<string name="send_validation_invalid_total">Der Gesamtbetrag geht über die Bilanz hinaus</string>
|
||||
<string name="send_validation_invalid_fee">Die Gebühr geht über die Bilanz hinaus</string>
|
||||
<string name="send_error_minimum_balance_format">Minimum balance is %s</string>
|
||||
<string name="send_extras_hint_memo">Memo</string>
|
||||
<string name="send_extras_hint_destination_tag">Tag</string>
|
||||
<string name="send_extras_error_invalid_memo">Invalid Memo. It won\'t be added to the transaction</string>
|
||||
<string name="send_extras_error_invalid_destination_tag">Invalid Tag. It won\'t be added to the transaction</string>
|
||||
<string name="warning_existential_deposit_message">%s network has a concept of Existential Deposit. If your account drops below %s %s it will be deactivated and any remaining funds will be destroyed.</string>
|
||||
<string name="warning_existential_deposit_message">%s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed.</string>
|
||||
<string name="no_account_polkadot">Destination account is not active. Send %s or more to activate the account.</string>
|
||||
</resources>
|
||||
|
|
@ -86,5 +86,13 @@
|
|||
<string name="card_settings_reset_card_to_factory">Reset to Factory Settings</string>
|
||||
<string name="card_settings_reset_card_to_factory_footer">Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code.</string>
|
||||
<string name="wallet_connect_select_network">Select network</string>
|
||||
<string name="main_scan_card_warning_view_title">Scan your card</string>
|
||||
<string name="main_scan_card_warning_view_subtitle">To access all the networks you need to scan the card</string>
|
||||
<string name="wallet_connect_error_failed_to_connect">Failed to establish WalletConnect session</string>
|
||||
<string name="send_extras_hint_memo">Memo</string>
|
||||
<string name="send_extras_hint_destination_tag">Tag</string>
|
||||
<string name="send_extras_error_invalid_memo">Invalid Memo. It won\'t be added to the transaction.</string>
|
||||
<string name="send_extras_error_invalid_destination_tag">Invalid Tag. It won\'t be added to the transaction.</string>
|
||||
<string name="wallet_connect_error_unsupported_dapp">Connection with this Dapp cannot be established due to its technical implementation.</string>
|
||||
<string name="saltpay_backup_warning" translatable="false">To start working with the card scan the first card and make a backup</string>
|
||||
</resources>
|
||||
|
|
@ -18,10 +18,6 @@
|
|||
<string name="xtz_withdrawal_message_reduce">Réduire de %s XTZ</string>
|
||||
<string name="xtz_withdrawal_message_ignore">Non, envoyer toute la somme</string>
|
||||
<string name="send_error_minimum_balance_format">Minimum balance is %s</string>
|
||||
<string name="send_extras_hint_memo">Memo</string>
|
||||
<string name="send_extras_hint_destination_tag">Tag</string>
|
||||
<string name="send_extras_error_invalid_memo">Invalid Memo. It won\'t be added to the transaction</string>
|
||||
<string name="send_extras_error_invalid_destination_tag">Invalid Tag. It won\'t be added to the transaction</string>
|
||||
<string name="warning_existential_deposit_message">%s network has a concept of Existential Deposit. If your account drops below %s %s it will be deactivated and any remaining funds will be destroyed.</string>
|
||||
<string name="warning_existential_deposit_message">%s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed.</string>
|
||||
<string name="no_account_polkadot">Destination account is not active. Send %s or more to activate the account.</string>
|
||||
</resources>
|
||||
|
|
@ -86,5 +86,13 @@
|
|||
<string name="card_settings_reset_card_to_factory">Reset to Factory Settings</string>
|
||||
<string name="card_settings_reset_card_to_factory_footer">Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code.</string>
|
||||
<string name="wallet_connect_select_network">Select network</string>
|
||||
<string name="main_scan_card_warning_view_title">Scan your card</string>
|
||||
<string name="main_scan_card_warning_view_subtitle">To access all the networks you need to scan the card</string>
|
||||
<string name="wallet_connect_error_failed_to_connect">Failed to establish WalletConnect session</string>
|
||||
<string name="send_extras_hint_memo">Memo</string>
|
||||
<string name="send_extras_hint_destination_tag">Tag</string>
|
||||
<string name="send_extras_error_invalid_memo">Invalid Memo. It won\'t be added to the transaction.</string>
|
||||
<string name="send_extras_error_invalid_destination_tag">Invalid Tag. It won\'t be added to the transaction.</string>
|
||||
<string name="wallet_connect_error_unsupported_dapp">Connection with this Dapp cannot be established due to its technical implementation.</string>
|
||||
<string name="saltpay_backup_warning" translatable="false">To start working with the card scan the first card and make a backup</string>
|
||||
</resources>
|
||||
|
|
@ -18,10 +18,6 @@
|
|||
<string name="xtz_withdrawal_message_reduce">Riduci di %s XTZ</string>
|
||||
<string name="xtz_withdrawal_message_ignore">No, invia l\'intero importo</string>
|
||||
<string name="send_error_minimum_balance_format">Minimum balance is %s</string>
|
||||
<string name="send_extras_hint_memo">Memo</string>
|
||||
<string name="send_extras_hint_destination_tag">Tag</string>
|
||||
<string name="send_extras_error_invalid_memo">Invalid Memo. It won\'t be added to the transaction</string>
|
||||
<string name="send_extras_error_invalid_destination_tag">Invalid Tag. It won\'t be added to the transaction</string>
|
||||
<string name="warning_existential_deposit_message">%s network has a concept of Existential Deposit. If your account drops below %s %s it will be deactivated and any remaining funds will be destroyed.</string>
|
||||
<string name="warning_existential_deposit_message">%s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed.</string>
|
||||
<string name="no_account_polkadot">Destination account is not active. Send %s or more to activate the account.</string>
|
||||
</resources>
|
||||
|
|
@ -86,5 +86,13 @@
|
|||
<string name="card_settings_reset_card_to_factory">Reset to Factory Settings</string>
|
||||
<string name="card_settings_reset_card_to_factory_footer">Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code.</string>
|
||||
<string name="wallet_connect_select_network">Select network</string>
|
||||
<string name="main_scan_card_warning_view_title">Scan your card</string>
|
||||
<string name="main_scan_card_warning_view_subtitle">To access all the networks you need to scan the card</string>
|
||||
<string name="wallet_connect_error_failed_to_connect">Failed to establish WalletConnect session</string>
|
||||
<string name="send_extras_hint_memo">Memo</string>
|
||||
<string name="send_extras_hint_destination_tag">Tag</string>
|
||||
<string name="send_extras_error_invalid_memo">Invalid Memo. It won\'t be added to the transaction.</string>
|
||||
<string name="send_extras_error_invalid_destination_tag">Invalid Tag. It won\'t be added to the transaction.</string>
|
||||
<string name="wallet_connect_error_unsupported_dapp">Connection with this Dapp cannot be established due to its technical implementation.</string>
|
||||
<string name="saltpay_backup_warning" translatable="false">To start working with the card scan the first card and make a backup</string>
|
||||
</resources>
|
||||
|
|
@ -129,7 +129,6 @@
|
|||
<string name="twins_onboarding_title">Tangem Twin</string>
|
||||
<string name="twins_onboarding_subtitle">Один кошелек. Две карты.</string>
|
||||
<string name="twins_onboarding_description_format">Одна, которую Вы держите в руках, и вторая карта с номером #%s</string>
|
||||
<string name="wallet_twins_chip_format">Карта %1d из %2d</string>
|
||||
<string name="details_row_title_twins_recreate">Пересоздать Twin кошелек</string>
|
||||
<string name="details_row_title_card_tou">Условия использования карты</string>
|
||||
<string name="twins_recreate_toolbar">Tangem Twin</string>
|
||||
|
|
|
|||
|
|
@ -18,5 +18,6 @@
|
|||
<string name="xtz_withdrawal_message_reduce">Уменьшить на %s XTZ</string>
|
||||
<string name="xtz_withdrawal_message_ignore">Нет, отправить все</string>
|
||||
<string name="send_error_minimum_balance_format">Минимальный баланс: %s</string>
|
||||
<string name="warning_existential_deposit_message">Сеть %s использует концепцию экзистенциального депозита. Если баланс вашего счета опустится ниже %s, он будет деактивирован, а все оставшиеся средства будут уничтожены.</string>
|
||||
<string name="no_account_polkadot">Аккаунт получателя не активирован. Отправьте %s или более для активации аккаунта.</string>
|
||||
</resources>
|
||||
|
|
@ -97,9 +97,13 @@
|
|||
<string name="card_settings_reset_card_to_factory">Сброс к заводским настройкам</string>
|
||||
<string name="card_settings_reset_card_to_factory_footer">Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек или использовать эту карту для восстановления кода доступа.</string>
|
||||
<string name="wallet_connect_select_network">Выберите сеть</string>
|
||||
<string name="main_scan_card_warning_view_title">Отсканируйте карту</string>
|
||||
<string name="main_scan_card_warning_view_subtitle">Чтобы получить доступ ко всем сетям, вам необходимо отсканировать карту</string>
|
||||
<string name="send_extras_hint_memo">Memo</string>
|
||||
<string name="send_extras_hint_destination_tag">Tag</string>
|
||||
<string name="send_extras_error_invalid_memo">Недопустимый Memo. Он не будет добавлен в транзакцию.</string>
|
||||
<string name="send_extras_error_invalid_destination_tag">Недопустимый Tag. Он не будет добавлен в транзакцию.</string>
|
||||
<string name="wallet_connect_error_failed_to_connect">Не удалось установить сессию WalletConnect</string>
|
||||
<string name="wallet_connect_error_unsupported_dapp">Cоединение с этим Dapp сервисом не может быть установлено из-за его технической реализации.</string>
|
||||
<string name="saltpay_backup_warning" translatable="false">Для начала работы с картой сначала отсканируйте первую карту и сделайте бэкап</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="ZendeskTheme" parent="ZendeskSdkTheme.Light">
|
||||
<style name="ZendeskTheme" parent="ZendeskThemeBase">
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -18,10 +18,6 @@
|
|||
<string name="xtz_withdrawal_message_reduce">Reduce by %s XTZ</string>
|
||||
<string name="xtz_withdrawal_message_ignore">No, send all</string>
|
||||
<string name="send_error_minimum_balance_format">Minimum balance is %s</string>
|
||||
<string name="send_extras_hint_memo">Memo</string>
|
||||
<string name="send_extras_hint_destination_tag">Tag</string>
|
||||
<string name="send_extras_error_invalid_memo">Invalid Memo. It won\'t be added to the transaction</string>
|
||||
<string name="send_extras_error_invalid_destination_tag">Invalid Tag. It won\'t be added to the transaction</string>
|
||||
<string name="warning_existential_deposit_message">%s network has a concept of Existential Deposit. If your account drops below %s %s it will be deactivated and any remaining funds will be destroyed.</string>
|
||||
<string name="warning_existential_deposit_message">%s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed.</string>
|
||||
<string name="no_account_polkadot">Destination account is not active. Send %s or more to activate the account.</string>
|
||||
</resources>
|
||||
|
|
@ -99,5 +99,13 @@
|
|||
<string name="card_settings_reset_card_to_factory">Reset to Factory Settings</string>
|
||||
<string name="card_settings_reset_card_to_factory_footer">Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code.</string>
|
||||
<string name="wallet_connect_select_network">Select network</string>
|
||||
<string name="main_scan_card_warning_view_title">Scan your card</string>
|
||||
<string name="main_scan_card_warning_view_subtitle">To access all the networks you need to scan the card</string>
|
||||
<string name="wallet_connect_error_failed_to_connect">Failed to establish WalletConnect session</string>
|
||||
<string name="send_extras_hint_memo">Memo</string>
|
||||
<string name="send_extras_hint_destination_tag">Tag</string>
|
||||
<string name="send_extras_error_invalid_memo">Invalid Memo. It won\'t be added to the transaction.</string>
|
||||
<string name="send_extras_error_invalid_destination_tag">Invalid Tag. It won\'t be added to the transaction.</string>
|
||||
<string name="wallet_connect_error_unsupported_dapp">Connection with this Dapp cannot be established due to its technical implementation.</string>
|
||||
<string name="saltpay_backup_warning" translatable="false">To start working with the card scan the first card and make a backup</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -16,8 +16,6 @@
|
|||
<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 and the other one with number #%s.</string>
|
||||
|
||||
<string name="wallet_twins_chip_format" translatable="false">Card %1d of %2d</string>
|
||||
|
||||
<string name="details_row_title_twins_recreate" translatable="false">Re-create twin wallet</string>
|
||||
<string name="details_row_title_card_tou" translatable="false">Card terms of use</string>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="ZendeskTheme" parent="ZendeskSdkTheme.Light">
|
||||
|
||||
<style name="ZendeskTheme" parent="ZendeskThemeBase" />
|
||||
|
||||
<style name="ZendeskThemeBase" parent="ZendeskSdkTheme.Light">
|
||||
<item name="colorPrimary">@color/darkGray6</item>
|
||||
<item name="colorPrimaryDark">@color/darkGray6</item>
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
ext.versions = [
|
||||
kotlin : '1.6.10',
|
||||
build_gradle : '7.1.3',
|
||||
// tangem_card_sdk : 'develop-159',
|
||||
tangem_card_sdk: '0.0.1',
|
||||
tangem_blockchain_sdk: 'develop-122',
|
||||
tangem_card_sdk : 'develop-163',
|
||||
tangem_blockchain_sdk: 'develop-121',
|
||||
// tangem_blockchain_sdk: '0.0.1',
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import com.tangem.blockchain.common.Blockchain
|
|||
fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? {
|
||||
return when (networkId) {
|
||||
"arbitrum-one" -> Blockchain.Arbitrum
|
||||
"arbitrum/test" -> Blockchain.ArbitrumTestnet
|
||||
"arbitrum-one/test" -> Blockchain.ArbitrumTestnet
|
||||
"avalanche", "avalanche-2" -> Blockchain.Avalanche
|
||||
"avalanche/test", "avalanche-2/test" -> Blockchain.AvalancheTestnet
|
||||
"binancecoin" -> Blockchain.Binance
|
||||
|
|
@ -38,6 +38,9 @@ fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? {
|
|||
"tron/test" -> Blockchain.TronTestnet
|
||||
"xrp", "ripple" -> Blockchain.XRP
|
||||
"xdai" -> Blockchain.Gnosis
|
||||
"ethereum-pow-iou" -> Blockchain.EthereumPow
|
||||
"ethereum-pow-iou/test" -> Blockchain.EthereumPowTestnet
|
||||
"ethereumfair" -> Blockchain.EthereumFair
|
||||
"polkadot" -> Blockchain.Polkadot
|
||||
"polkadot/test" -> Blockchain.PolkadotTestnet
|
||||
"kusama" -> Blockchain.Kusama
|
||||
|
|
@ -87,6 +90,9 @@ fun Blockchain.toNetworkId(): String {
|
|||
Blockchain.Tron -> "tron"
|
||||
Blockchain.TronTestnet -> "tron/test"
|
||||
Blockchain.Gnosis -> "xdai"
|
||||
Blockchain.EthereumPow -> "ethereum-pow-iou"
|
||||
Blockchain.EthereumPowTestnet -> "ethereum-pow-iou/test"
|
||||
Blockchain.EthereumFair -> "ethereumfair"
|
||||
Blockchain.Polkadot -> "polkadot"
|
||||
Blockchain.PolkadotTestnet -> "polkadot/test"
|
||||
Blockchain.Kusama -> "kusama"
|
||||
|
|
@ -94,7 +100,6 @@ fun Blockchain.toNetworkId(): String {
|
|||
Blockchain.OptimismTestnet -> "optimistic-ethereum/test"
|
||||
Blockchain.Dash -> "dash"
|
||||
Blockchain.SaltPay -> "wxdai"
|
||||
else -> "unknown" // TODO
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -120,11 +125,13 @@ fun Blockchain.toCoinId(): String {
|
|||
Blockchain.Tezos -> "tezos"
|
||||
Blockchain.XRP -> "ripple"
|
||||
Blockchain.Dogecoin -> "dogecoin"
|
||||
Blockchain.Gnosis, Blockchain.SaltPay -> "xdai"
|
||||
Blockchain.Gnosis -> "xdai"
|
||||
Blockchain.EthereumPow, Blockchain.EthereumPowTestnet -> "ethereum-pow-iou"
|
||||
Blockchain.EthereumFair -> "ethereumfair"
|
||||
Blockchain.Kusama -> "kusama"
|
||||
Blockchain.Optimism, Blockchain.OptimismTestnet -> "ethereum"
|
||||
Blockchain.Dash -> "dash"
|
||||
Blockchain.SaltPay -> "xdai"
|
||||
Blockchain.Unknown -> "unknown"
|
||||
else -> "unknown" // TODO
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.domain.common.extensions
|
||||
|
||||
import javax.crypto.Mac
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
fun ByteArray.calculateHmacSha256(key: ByteArray): ByteArray {
|
||||
val mac: Mac = Mac.getInstance("HmacSHA256")
|
||||
mac.init(SecretKeySpec(key, "HmacSHA256"))
|
||||
return mac.doFinal(this)
|
||||
}
|
||||
|
|
@ -26,7 +26,7 @@ class AddCustomTokenService(
|
|||
result.data.coins.forEach { coin ->
|
||||
val networksWithTheSameAddress = coin.networks
|
||||
.filter { it.contractAddress != null || it.decimalCount != null }
|
||||
.filter { it.contractAddress == contractAddress }
|
||||
.filter { it.contractAddress?.equals(contractAddress, ignoreCase = true) == true }
|
||||
.filter { supportedTokenNetworkIds.contains(it.networkId) }
|
||||
if (networksWithTheSameAddress.isNotEmpty()) {
|
||||
val newToken = coin.copy(networks = networksWithTheSameAddress)
|
||||
|
|
@ -45,6 +45,5 @@ class AddCustomTokenService(
|
|||
|
||||
private fun selectNetworksForSearch(networkId: String?): String {
|
||||
return networkId ?: supportedTokenNetworkIds.joinToString(",")
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,9 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.domain.AddCustomTokenError
|
||||
import com.tangem.domain.AddCustomTokenError.Warning.*
|
||||
import com.tangem.domain.AddCustomTokenError.Warning.PotentialScamToken
|
||||
import com.tangem.domain.AddCustomTokenError.Warning.TokenAlreadyAdded
|
||||
import com.tangem.domain.AddCustomTokenError.Warning.UnsupportedSolanaToken
|
||||
import com.tangem.domain.AddCustomTokenException
|
||||
import com.tangem.domain.DomainDialog
|
||||
import com.tangem.domain.DomainWrapped
|
||||
|
|
@ -14,10 +16,40 @@ import com.tangem.domain.common.extensions.canHandleToken
|
|||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.domain.common.extensions.supportedBlockchains
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
import com.tangem.domain.common.form.*
|
||||
import com.tangem.domain.features.addCustomToken.*
|
||||
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.*
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.*
|
||||
import com.tangem.domain.common.form.Field
|
||||
import com.tangem.domain.common.form.Form
|
||||
import com.tangem.domain.common.form.TokenContractAddressValidator
|
||||
import com.tangem.domain.common.form.TokenDecimalsValidator
|
||||
import com.tangem.domain.common.form.TokenNameValidator
|
||||
import com.tangem.domain.common.form.TokenNetworkValidator
|
||||
import com.tangem.domain.common.form.TokenSymbolValidator
|
||||
import com.tangem.domain.features.addCustomToken.AddCustomTokenService
|
||||
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId
|
||||
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.ContractAddress
|
||||
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Decimals
|
||||
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.DerivationPath
|
||||
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Name
|
||||
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Network
|
||||
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Symbol
|
||||
import com.tangem.domain.features.addCustomToken.TokenBlockchainField
|
||||
import com.tangem.domain.features.addCustomToken.TokenDerivationPathField
|
||||
import com.tangem.domain.features.addCustomToken.TokenField
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.FieldError
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.Init
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnAddCustomTokenClicked
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnCreate
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnDestroy
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenContractAddressChanged
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenDecimalsChanged
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenDerivationPathChanged
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenNameChanged
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenNetworkChanged
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenSymbolChanged
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.Screen
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.SetFoundTokenInfo
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.UpdateForm
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.Warning
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenState.Companion.createInitialScreenState
|
||||
import com.tangem.domain.redux.BaseStoreHub
|
||||
import com.tangem.domain.redux.DomainState
|
||||
import com.tangem.domain.redux.ReStoreReducer
|
||||
|
|
@ -569,12 +601,12 @@ private class AddCustomTokenReducer(
|
|||
tangemTechService = globalState.networkServices.tangemTechService,
|
||||
supportedTokenNetworkIds = supportedTokenNetworkIds
|
||||
)
|
||||
|
||||
val form = Form(AddCustomTokenState.createFormFields(card, CustomTokenType.Blockchain))
|
||||
state.copy(
|
||||
cardDerivationStyle = card.derivationStyle,
|
||||
form = form,
|
||||
tangemTechServiceManager = tangemTechServiceManager,
|
||||
screenState = createInitialScreenState(card.settings.isHDWalletAllowed),
|
||||
)
|
||||
}
|
||||
is OnDestroy -> {
|
||||
|
|
|
|||
|
|
@ -9,9 +9,31 @@ import com.tangem.domain.DomainWrapped
|
|||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.domain.common.extensions.supportedBlockchains
|
||||
import com.tangem.domain.common.extensions.supportedTokens
|
||||
import com.tangem.domain.common.form.*
|
||||
import com.tangem.domain.features.addCustomToken.*
|
||||
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.*
|
||||
import com.tangem.domain.common.form.CustomTokenValidator
|
||||
import com.tangem.domain.common.form.DataField
|
||||
import com.tangem.domain.common.form.FieldDataConverter
|
||||
import com.tangem.domain.common.form.FieldId
|
||||
import com.tangem.domain.common.form.FieldToJsonConverter
|
||||
import com.tangem.domain.common.form.Form
|
||||
import com.tangem.domain.common.form.StringIsEmptyValidator
|
||||
import com.tangem.domain.common.form.StringIsNotEmptyValidator
|
||||
import com.tangem.domain.common.form.TokenContractAddressValidator
|
||||
import com.tangem.domain.common.form.TokenDecimalsValidator
|
||||
import com.tangem.domain.common.form.TokenNameValidator
|
||||
import com.tangem.domain.common.form.TokenNetworkValidator
|
||||
import com.tangem.domain.common.form.TokenSymbolValidator
|
||||
import com.tangem.domain.features.addCustomToken.AddCustomTokenService
|
||||
import com.tangem.domain.features.addCustomToken.CustomCurrency
|
||||
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId
|
||||
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.ContractAddress
|
||||
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Decimals
|
||||
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.DerivationPath
|
||||
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Name
|
||||
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Network
|
||||
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Symbol
|
||||
import com.tangem.domain.features.addCustomToken.TokenBlockchainField
|
||||
import com.tangem.domain.features.addCustomToken.TokenDerivationPathField
|
||||
import com.tangem.domain.features.addCustomToken.TokenField
|
||||
import com.tangem.domain.redux.DomainState
|
||||
import com.tangem.domain.redux.state.StringActionStateConverter
|
||||
import com.tangem.network.api.tangemTech.CoinsResponse
|
||||
|
|
@ -119,7 +141,7 @@ data class AddCustomTokenState(
|
|||
formErrors = emptyMap(),
|
||||
foundToken = null,
|
||||
warnings = emptySet(),
|
||||
screenState = createInitialScreenState(),
|
||||
screenState = createInitialScreenState(card.settings.isHDWalletAllowed),
|
||||
tangemTechServiceManager = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -223,15 +245,15 @@ data class AddCustomTokenState(
|
|||
return (listOf(Blockchain.Unknown) + evmBlockchains).sortByName()
|
||||
}
|
||||
|
||||
private fun createInitialScreenState(): ScreenState {
|
||||
internal fun createInitialScreenState(showDerivationPathField: Boolean = false): ScreenState {
|
||||
return ScreenState(
|
||||
contractAddressField = ViewStates.TokenField(),
|
||||
network = ViewStates.TokenField(),
|
||||
name = ViewStates.TokenField(isEnabled = false),
|
||||
symbol = ViewStates.TokenField(isEnabled = false),
|
||||
decimals = ViewStates.TokenField(isEnabled = false),
|
||||
derivationPath = ViewStates.TokenField(),
|
||||
addButton = ViewStates.AddButton(isEnabled = false)
|
||||
derivationPath = ViewStates.TokenField(isVisible = showDerivationPathField),
|
||||
addButton = ViewStates.AddButton(isEnabled = false),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,5 +46,27 @@ data class CurrenciesResponse(val currencies: List<Currency>) {
|
|||
}
|
||||
|
||||
data class GeoResponse(
|
||||
val code: String
|
||||
) : TangemTechResponse
|
||||
val code: String,
|
||||
) : TangemTechResponse
|
||||
|
||||
data class UserTokensResponse(
|
||||
val version: Int = 0,
|
||||
val group: String = "",
|
||||
val sort: String = "",
|
||||
val tokens: List<TokenResponse> = emptyList(),
|
||||
) : TangemTechResponse
|
||||
|
||||
data class TokenResponse(
|
||||
val id: String,
|
||||
val networkId: String,
|
||||
val derivationPath: String,
|
||||
val name: String,
|
||||
val symbol: String,
|
||||
val decimals: Int,
|
||||
val contractAddress: String?,
|
||||
) : TangemTechResponse
|
||||
|
||||
data class TangemTechError(
|
||||
val code: Int,
|
||||
val description: String,
|
||||
)
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
package com.tangem.network.api.tangemTech
|
||||
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.PUT
|
||||
import retrofit2.http.Path
|
||||
import retrofit2.http.Query
|
||||
|
||||
/**
|
||||
|
|
@ -29,4 +32,10 @@ interface TangemTechApi {
|
|||
|
||||
@GET("geo")
|
||||
suspend fun geo(): GeoResponse
|
||||
|
||||
@GET("user-tokens/{user-id}")
|
||||
suspend fun getUserTokens(@Path(value = "user-id") userId: String): UserTokensResponse
|
||||
|
||||
@PUT("user-tokens/{user-id}")
|
||||
suspend fun putUserTokens(@Path(value = "user-id") userId: String, @Body userTokens: UserTokensResponse)
|
||||
}
|
||||
|
|
@ -58,6 +58,15 @@ class TangemTechService(
|
|||
performRequest { api.currencies() }
|
||||
}
|
||||
|
||||
suspend fun getUserTokens(userId: String): Result<UserTokensResponse> = withContext(Dispatchers.IO) {
|
||||
performRequest { api.getUserTokens(userId) }
|
||||
}
|
||||
|
||||
suspend fun putUserTokens(userId: String, userTokens: UserTokensResponse): Result<Unit> =
|
||||
withContext(Dispatchers.IO) {
|
||||
performRequest { api.putUserTokens(userId, userTokens) }
|
||||
}
|
||||
|
||||
fun addHeaderInterceptors(interceptors: List<AddHeaderInterceptor>) {
|
||||
headerInterceptors.removeAll(interceptors)
|
||||
headerInterceptors.addAll(interceptors)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue