Updated on 2026-08-14

This commit is contained in:
Tangem 2023-02-15 17:14:00 +03:00
parent b23c147a22
commit 306a9bb0ec
26 changed files with 828 additions and 110 deletions

@ -1 +1 @@
Subproject commit 2b8aa19f58e17a78015629943d49df0da0b4fd4b
Subproject commit f5f6ac954894ef66331db8fe383b9773feb69ef4

View file

@ -57,6 +57,7 @@ import com.tangem.tap.persistence.PreferencesStorage
import com.tangem.tap.proxy.AppStateHolder
import com.tangem.wallet.BuildConfig
import dagger.hilt.android.HiltAndroidApp
import okhttp3.logging.HttpLoggingInterceptor
import org.rekotlin.Store
import timber.log.Timber
import javax.inject.Inject
@ -146,7 +147,12 @@ class TapApplication : Application(), ImageLoaderFactory {
initConfigManager(configLoader, ::initWithConfigDependency)
initWarningMessagesManager()
BlockchainSdkRetrofitBuilder.enableNetworkLogging = LogConfig.network.blockchainSdkNetwork
if (LogConfig.network.blockchainSdkNetwork) {
BlockchainSdkRetrofitBuilder.interceptors = listOf(
HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY },
)
}
userTokensRepository = UserTokensRepository.init(
context = this,

View file

@ -2,6 +2,7 @@ package com.tangem.tap.domain.configurable.config
import com.tangem.blockchain.common.BlockchainSdkConfig
import com.tangem.blockchain.common.BlockchairCredentials
import com.tangem.blockchain.common.BlockscoutCredentials
import com.tangem.blockchain.common.GetBlockCredentials
import com.tangem.blockchain.common.NowNodeCredentials
import com.tangem.blockchain.common.QuickNodeCredentials
@ -93,6 +94,10 @@ class ConfigManager {
authToken = values.blockchairAuthorizationToken,
),
blockcypherTokens = values.blockcypherTokens,
blockscoutCredentials = BlockscoutCredentials(
userName = values.saltPay.blockscoutCredentials.user,
password = values.saltPay.blockscoutCredentials.password,
),
quickNodeSolanaCredentials = QuickNodeCredentials(
apiKey = values.quiknodeApiKey,
subdomain = values.quiknodeSubdomain,

View file

@ -15,6 +15,7 @@ class FeatureModel(
val isCreatingTwinCardsAllowed: Boolean,
)
@Suppress("LongParameterList")
class ConfigValueModel(
val coinMarketCapKey: String,
val mercuryoWidgetId: String,
@ -37,6 +38,7 @@ class ConfigValueModel(
val saltPay: SaltPayConfig,
val tronGridApiKey: String,
val amplitudeApiKey: String,
val swapReferrerAccount: SwapReferrerAccount?,
)
data class AppsFlyer(
@ -44,6 +46,11 @@ data class AppsFlyer(
val appsFlyerAppID: String,
)
data class SwapReferrerAccount(
val address: String,
val fee: String,
)
class ConfigModel(
val features: FeatureModel?,
val configValues: ConfigValueModel?,

View file

@ -9,6 +9,7 @@ data class SaltPayConfig(
val sprinklrAppID: String,
val kycProvider: KYCProvider,
val credentials: Credentials,
val blockscoutCredentials: Credentials,
) {
companion object {
fun stub(): SaltPayConfig {
@ -16,6 +17,7 @@ data class SaltPayConfig(
sprinklrAppID = "",
kycProvider = KYCProvider("", "", "", ""),
credentials = Credentials("", ""),
blockscoutCredentials = Credentials("", ""),
)
}
}

View file

@ -84,7 +84,7 @@ data class WalletState(
get() = if (isMultiwalletAllowed || walletsStores.isEmpty() || walletsStores.size > 1) null
else walletsStores[0]
private val primaryWalletManager: WalletManager?
val primaryWalletManager: WalletManager?
get() = primaryWalletStore?.walletManager
val primaryWalletData: WalletData?

View file

@ -249,6 +249,8 @@ class WalletMiddleware {
}
is NetworkStateChanged -> {
store.dispatch(WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline)
if (!action.isOnline) return
val selectedUserWallet = userWalletsListManagerSafe?.selectedUserWalletSync
if (selectedUserWallet != null) {
scope.launch { globalState.tapWalletManager.loadData(selectedUserWallet) }

View file

@ -39,9 +39,9 @@ import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.features.wallet.ui.adapters.WarningMessagesAdapter
import com.tangem.tap.features.wallet.ui.wallet.MultiWalletView
import com.tangem.tap.features.wallet.ui.wallet.SaltPayWalletView
import com.tangem.tap.features.wallet.ui.wallet.SingleWalletView
import com.tangem.tap.features.wallet.ui.wallet.WalletView
import com.tangem.tap.features.wallet.ui.wallet.saltPay.SaltPayWalletView
import com.tangem.tap.store
import com.tangem.tap.userWalletsListManager
import com.tangem.wallet.BuildConfig
@ -150,15 +150,18 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
when {
isSaltPay && (walletView !is SaltPayWalletView) -> {
walletView.onViewDestroy()
walletView = SaltPayWalletView()
walletView.changeWalletView(this, binding)
}
state.isMultiwalletAllowed && state.primaryWalletData?.currencyData?.status != BalanceStatus.EmptyCard &&
walletView !is MultiWalletView -> {
walletView.onViewDestroy()
walletView = MultiWalletView()
walletView.changeWalletView(this, binding)
}
!state.isMultiwalletAllowed && !isSaltPay && walletView !is SingleWalletView -> {
walletView.onViewDestroy()
walletView = SingleWalletView()
walletView.changeWalletView(this, binding)
}
@ -174,7 +177,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
}
setupNoInternetHandling(state)
setupCardImage(state)
setupCardImage(state, isSaltPay)
if (!isSaltPay) showWarningsIfPresent(state.mainWarningsList)
@ -183,6 +186,8 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
if (state.state != ProgressState.Loading &&
state.state != ProgressState.Refreshing
) {
//TODO: blueprint
walletView.pullToRefreshListener?.invoke()
Analytics.send(Portfolio.Refreshed())
store.dispatch(WalletAction.LoadData.Refresh)
}
@ -211,9 +216,9 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
}
}
private fun setupCardImage(state: WalletState) {
private fun setupCardImage(state: WalletState, isSaltPay: Boolean) {
//TODO: SaltPay: remove hardCode
if (store.state.globalState.scanResponse?.isSaltPay() == true) {
if (isSaltPay) {
binding.ivCard.load(R.drawable.img_salt_pay_visa) {
scale(Scale.FIT)
crossfade(enable = true)

View file

@ -1,88 +0,0 @@
package com.tangem.tap.features.wallet.ui.wallet
import com.tangem.domain.common.extensions.debounce
import com.tangem.tap.common.extensions.animateVisibility
import com.tangem.tap.common.extensions.formatAmountAsSpannedString
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.show
import com.tangem.tap.features.wallet.redux.ProgressState
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.features.wallet.ui.WalletFragment
import com.tangem.tap.mainScope
import com.tangem.tap.store
import com.tangem.wallet.databinding.FragmentWalletBinding
import com.tangem.wallet.databinding.LayoutSaltPayWalletBinding
import org.rekotlin.Action
class SaltPayWalletView : WalletView() {
private lateinit var saltPayBinding: LayoutSaltPayWalletBinding
private val actionDebouncer = debounce<Action>(500, mainScope) { store.dispatch(it) }
override fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding) {
saltPayBinding = binding.lSaltPayWallet
setFragment(fragment, binding)
onViewCreated()
showSaltPayView(binding)
}
private fun showSaltPayView(binding: FragmentWalletBinding) = with(binding) {
rvWarningMessages.hide()
rvPendingTransaction.hide()
rvMultiwallet.hide()
tvTwinCardNumber.hide()
lCardBalance.root.hide()
lSingleWalletBalance.root.hide()
lAddress.root.hide()
rowButtons.hide()
btnAddToken.hide()
pbLoadingUserTokens.hide()
lSaltPayWallet.root.show()
}
override fun onViewCreated() {
}
override fun onNewState(state: WalletState) {
setupBalanceWidget(state)
}
private fun setupBalanceWidget(state: WalletState) = with(saltPayBinding.lSaltPayBalance) {
val tokenData = state.primaryTokenData ?: return@with
val appCurrency = store.state.globalState.appCurrency
val mainProgressState = state.state
if (mainProgressState == ProgressState.Loading) {
veilBalance.veil()
veilBalanceCrypto.veil()
} else {
veilBalanceCrypto.unVeil()
}
tvProcessing.animateVisibility(show = mainProgressState == ProgressState.Error)
veilBalanceCrypto.animateVisibility(show = mainProgressState != ProgressState.Error)
if (tokenData.currencyData.fiatAmount == null) {
actionDebouncer(WalletAction.LoadFiatRate())
} else {
veilBalance.unVeil()
tvBalance.text = tokenData.currencyData.fiatAmount.formatAmountAsSpannedString(
currencySymbol = appCurrency.symbol,
)
}
tvBalanceCrypto.text = tokenData.currencyData.amountFormatted
tvCurrencyName.text = appCurrency.code
tvCurrencyName.setOnClickListener {
store.dispatch(WalletAction.AppCurrencyAction.ChooseAppCurrency)
}
btnBuy.show(tokenData.isAvailableToBuy)
btnBuy.setOnClickListener {
store.dispatch(WalletAction.TradeCryptoAction.Buy(false))
}
}
}

View file

@ -5,8 +5,13 @@ import com.tangem.tap.features.wallet.ui.WalletFragment
import com.tangem.wallet.databinding.FragmentWalletBinding
abstract class WalletView {
//TODO: blueprint
var pullToRefreshListener: (() -> Unit)? = null
protected var fragment: WalletFragment? = null
protected var binding: FragmentWalletBinding? = null
fun setFragment(fragment: WalletFragment, binding: FragmentWalletBinding) {
this.fragment = fragment
this.binding = binding
@ -17,6 +22,11 @@ abstract class WalletView {
binding = null
}
//TODO: blueprint
open fun onViewDestroy() {
pullToRefreshListener = null
}
open fun onDestroyFragment() {}
abstract fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding)

View file

@ -0,0 +1,21 @@
package com.tangem.tap.features.wallet.ui.wallet.saltPay
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.TransactionStatus
import com.tangem.tap.features.wallet.models.PendingTransactionType
/**
[REDACTED_AUTHOR]
*/
data class HistoryTransactionData(
val transactionData: TransactionData,
private val walletAddress: String,
) {
fun isInProgress(): Boolean = transactionData.status == TransactionStatus.Unconfirmed
fun getTransactionType(): PendingTransactionType = when {
transactionData.sourceAddress.lowercase() == walletAddress.lowercase() -> PendingTransactionType.Outgoing
transactionData.destinationAddress.lowercase() == walletAddress.lowercase() -> PendingTransactionType.Incoming
else -> PendingTransactionType.Unknown
}
}

View file

@ -0,0 +1,218 @@
package com.tangem.tap.features.wallet.ui.wallet.saltPay
import android.view.LayoutInflater
import android.view.ViewGroup
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
import com.tangem.blockchain.extensions.Result
import com.tangem.domain.common.extensions.debounce
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.tap.common.extensions.animateVisibility
import com.tangem.tap.common.extensions.beginDelayedTransition
import com.tangem.tap.common.extensions.formatAmountAsSpannedString
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.recyclerView.SpaceItemDecoration
import com.tangem.tap.domain.getFirstToken
import com.tangem.tap.features.wallet.redux.ProgressState
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.features.wallet.ui.WalletFragment
import com.tangem.tap.features.wallet.ui.wallet.WalletView
import com.tangem.tap.features.wallet.ui.wallet.saltPay.rv.ShimmerData
import com.tangem.tap.features.wallet.ui.wallet.saltPay.rv.ShimmerRecyclerAdapter
import com.tangem.tap.features.wallet.ui.wallet.saltPay.rv.TransactionHistoryAdapter
import com.tangem.tap.mainScope
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.wallet.databinding.FragmentWalletBinding
import com.tangem.wallet.databinding.ItemSaltPayTxHistoryShimmerBinding
import com.tangem.wallet.databinding.LayoutSaltPayBalanceBinding
import com.tangem.wallet.databinding.LayoutSaltPayTxHistoryBinding
import com.tangem.wallet.databinding.LayoutSaltPayWalletBinding
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.rekotlin.Action
import java.util.concurrent.atomic.AtomicBoolean
/**
[REDACTED_AUTHOR]
*/
class SaltPayWalletView : WalletView() {
private lateinit var saltPayBinding: LayoutSaltPayWalletBinding
private val actionDebouncer = debounce<Action>(500, mainScope) { store.dispatch(it) }
private val balanceWidget: LayoutSaltPayBalanceBinding
get() = saltPayBinding.lSaltPayBalance
private val txWidget: LayoutSaltPayTxHistoryBinding
get() = saltPayBinding.lSaltPayTxHistory
private var initialized = AtomicBoolean(false)
override fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding) {
saltPayBinding = binding.lSaltPayWallet
setFragment(fragment, binding)
onViewCreated()
showSaltPayView(binding)
}
override fun onViewCreated() {
this.pullToRefreshListener = this::handlePullToRefresh
prepareTxRecyclers()
}
private fun showSaltPayView(binding: FragmentWalletBinding) = with(binding) {
rvWarningMessages.hide()
rvPendingTransaction.hide()
rvMultiwallet.hide()
tvTwinCardNumber.hide()
lCardBalance.root.hide()
lSingleWalletBalance.root.hide()
lAddress.root.hide()
rowButtons.hide()
btnAddToken.hide()
pbLoadingUserTokens.hide()
lSaltPayWallet.root.show()
}
private fun prepareTxRecyclers() {
val vhViewFactory: (ViewGroup) -> ViewGroup = {
val inflater = LayoutInflater.from(it.context)
ItemSaltPayTxHistoryShimmerBinding.inflate(inflater, it, false).root
}
val adapter = ShimmerRecyclerAdapter(vhViewFactory)
txWidget.rvTxHistoryShimmer.adapter = adapter
txWidget.rvTxHistoryShimmer.addItemDecoration(SpaceItemDecoration.vertical(10F))
txWidget.rvTxHistory.adapter = TransactionHistoryAdapter()
txWidget.rvTxHistory.addItemDecoration(SpaceItemDecoration.vertical(10F))
handleTxInit()
}
private fun handlePullToRefresh() {
requestTxHistory(store.state.walletState)
}
override fun onNewState(state: WalletState) {
if (!initialized.getAndSet(true)) requestTxHistory(state)
setupBalanceWidget(state)
}
private fun setupBalanceWidget(state: WalletState) = with(balanceWidget) {
val tokenData = state.primaryTokenData ?: return@with
val appCurrency = store.state.globalState.appCurrency
val mainProgressState = state.state
if (mainProgressState == ProgressState.Loading) {
veilBalance.veil()
veilBalanceCrypto.veil()
} else {
veilBalanceCrypto.unVeil()
}
tvUnreachable.animateVisibility(show = mainProgressState == ProgressState.Error)
veilBalanceCrypto.animateVisibility(show = mainProgressState != ProgressState.Error)
if (tokenData.currencyData.fiatAmount == null) {
actionDebouncer(WalletAction.LoadFiatRate())
} else {
veilBalance.unVeil()
tvBalance.text = tokenData.currencyData.fiatAmount.formatAmountAsSpannedString(
currencySymbol = appCurrency.symbol,
)
}
tvBalanceCrypto.text = tokenData.currencyData.amountFormatted
tvCurrencyName.text = appCurrency.code
tvCurrencyName.setOnClickListener {
store.dispatch(WalletAction.AppCurrencyAction.ChooseAppCurrency)
}
btnBuy.show(tokenData.isAvailableToBuy)
btnBuy.setOnClickListener {
store.dispatch(WalletAction.TradeCryptoAction.Buy(false))
}
}
private fun requestTxHistory(state: WalletState) {
scope.launch {
val walletManager = state.primaryWalletManager as? EthereumWalletManager ?: return@launch
val wallet = walletManager.wallet
val token = wallet.getFirstToken() ?: return@launch
// val walletAddress = wallet.address
val walletAddress = "0xDA94Aae02a4Db0e09E1Cf240E3a0973ba89052cf"
when (val result = walletManager.getTransactionHistory(walletAddress, wallet.blockchain, setOf(token))) {
is Result.Success -> {
val tokensHistory = result.data
.filter { it.contractAddress == token.contractAddress }
.sortedBy { it.date?.timeInMillis ?: 0 }
.map { HistoryTransactionData(it, walletAddress) }
// .toMutableList().apply { clear() }
delay(300)
withMainContext {
if (tokensHistory.isEmpty()) {
handleTxEmpty()
} else {
handleTxSuccess(tokensHistory)
}
}
}
is Result.Failure -> {
delay(300)
withMainContext { handleTxError() }
}
}
}
}
private fun handleTxInit() = with(txWidget) {
(rvTxHistoryShimmer.adapter as? ShimmerRecyclerAdapter)?.submitList(
listOf(
ShimmerData(),
ShimmerData(),
ShimmerData(),
),
)
groupEmpty.hide()
groupError.hide()
groupSuccess.hide()
root.beginDelayedTransition()
txWidget.groupShimmer.show()
}
private fun handleTxSuccess(dataList: List<HistoryTransactionData>) = with(txWidget) {
groupShimmer.hide()
groupEmpty.hide()
groupError.hide()
root.beginDelayedTransition()
updateTxHistoryWidget(dataList)
groupSuccess.show()
}
private fun handleTxEmpty() = with(txWidget) {
groupShimmer.hide()
groupError.hide()
groupSuccess.hide()
root.beginDelayedTransition()
updateTxHistoryWidget(emptyList())
groupEmpty.show()
}
private fun handleTxError() = with(txWidget) {
groupShimmer.hide()
groupEmpty.hide()
groupSuccess.hide()
root.beginDelayedTransition()
updateTxHistoryWidget(emptyList())
groupError.show()
}
private fun updateTxHistoryWidget(dataList: List<HistoryTransactionData>) = with(txWidget) {
(rvTxHistory.adapter as TransactionHistoryAdapter).submitList(dataList)
}
}

View file

@ -0,0 +1,33 @@
package com.tangem.tap.features.wallet.ui.wallet.saltPay.rv
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
/**
[REDACTED_AUTHOR]
*/
open class ShimmerRecyclerAdapter(
private val viewHolderViewFactory: (ViewGroup) -> ViewGroup,
) : ListAdapter<ShimmerData, ShimmerVH>(DiffUtilCallback) {
override fun getItemId(position: Int): Long {
return if (currentList.isEmpty()) 0 else currentList[position].hashCode().toLong()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ShimmerVH {
return ShimmerVH(viewHolderViewFactory.invoke(parent))
}
override fun onBindViewHolder(holder: ShimmerVH, position: Int) {}
object DiffUtilCallback : DiffUtil.ItemCallback<ShimmerData>() {
override fun areContentsTheSame(oldItem: ShimmerData, newItem: ShimmerData) = oldItem == newItem
override fun areItemsTheSame(oldItem: ShimmerData, newItem: ShimmerData) = oldItem == newItem
}
}
class ShimmerVH(viewGroup: ViewGroup) : RecyclerView.ViewHolder(viewGroup)
data class ShimmerData(private val any: String = "")

View file

@ -0,0 +1,103 @@
package com.tangem.tap.features.wallet.ui.wallet.saltPay.rv
import android.graphics.PorterDuff
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.tangem.tap.common.extensions.getColor
import com.tangem.tap.common.extensions.setDrawable
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.extensions.toFormattedCurrencyString
import com.tangem.tap.features.wallet.models.PendingTransactionType
import com.tangem.tap.features.wallet.ui.wallet.saltPay.HistoryTransactionData
import com.tangem.wallet.R
import com.tangem.wallet.databinding.ItemSaltPayTxHistoryBinding
/**
[REDACTED_AUTHOR]
*/
internal class TransactionHistoryAdapter : ListAdapter<HistoryTransactionData, TransactionItemVH>(DiffUtilCallback) {
override fun getItemId(position: Int): Long {
return if (currentList.isEmpty()) 0
else currentList[position].transactionData.hash.hashCode().toLong()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TransactionItemVH {
val inflater = LayoutInflater.from(parent.context)
val layout = ItemSaltPayTxHistoryBinding.inflate(inflater, parent, false)
return TransactionItemVH(layout)
}
override fun onBindViewHolder(holder: TransactionItemVH, position: Int) {
holder.bind(currentList[position])
}
object DiffUtilCallback : DiffUtil.ItemCallback<HistoryTransactionData>() {
override fun areContentsTheSame(
oldItem: HistoryTransactionData,
newItem: HistoryTransactionData,
) = oldItem == newItem
override fun areItemsTheSame(
oldItem: HistoryTransactionData,
newItem: HistoryTransactionData,
) = oldItem == newItem
}
}
internal class TransactionItemVH(
private val binding: ItemSaltPayTxHistoryBinding,
) : RecyclerView.ViewHolder(binding.root) {
fun bind(data: HistoryTransactionData) = with(binding) {
setupImage(data)
setupTitle(data)
setupSubtitle(data)
setupBalance(data)
}
private fun setupImage(data: HistoryTransactionData) = with(binding) {
val drawable = when (data.getTransactionType()) {
PendingTransactionType.Incoming -> R.drawable.ic_tx_incoming
PendingTransactionType.Outgoing -> R.drawable.ic_tx_outgoing
PendingTransactionType.Unknown -> null
}
drawable?.let { imvTx.setDrawable(it) }
if (data.isInProgress()) {
imvTx.setColorFilter(imvTx.getColor(R.color.icon_attention), PorterDuff.Mode.MULTIPLY)
} else {
imvTx.clearColorFilter()
}
}
private fun setupTitle(data: HistoryTransactionData) = with(binding) {
val hash = data.transactionData.hash ?: return@with
val formattedHash = "${hash.substring(0..5)}...${hash.substring(hash.length - 4)}"
tvTxHash.text = formattedHash
}
private fun setupSubtitle(data: HistoryTransactionData) = with(binding) {
val inProgress = data.isInProgress()
tvTxStatus.show(inProgress)
tvTxTime.show(!inProgress)
val time = data.transactionData.date?.timeInMillis.toString() ?: "none"
tvTxTime.text = time
}
private fun setupBalance(data: HistoryTransactionData) = with(binding) {
val sign = when (data.getTransactionType()) {
PendingTransactionType.Incoming -> "+"
PendingTransactionType.Outgoing -> "-"
PendingTransactionType.Unknown -> ""
}
val amount = data.transactionData.amount
tvTxAmountSign.text = sign
tvTxAmountValue.text = amount.value?.toFormattedCurrencyString(8, amount.currencySymbol)
}
}

View file

@ -53,7 +53,6 @@ class UtorgExchangeService(
override fun isSellAllowed(): Boolean = false
override fun availableForBuy(currency: Currency): Boolean {
return true
if (!isBuyAllowed()) return false
val foundUtorgCurrency = utorgCurrencies.firstOrNull { utorgCurrency ->

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18dp"
android:height="18dp"
android:viewportWidth="18"
android:viewportHeight="18">
<path
android:pathData="M4.833,13.167L7.5,7.5L13.167,4.833L10.5,10.5L4.833,13.167ZM9,8.25C8.801,8.25 8.61,8.329 8.47,8.47C8.329,8.61 8.25,8.801 8.25,9C8.25,9.199 8.329,9.39 8.47,9.53C8.61,9.671 8.801,9.75 9,9.75C9.199,9.75 9.39,9.671 9.53,9.53C9.671,9.39 9.75,9.199 9.75,9C9.75,8.801 9.671,8.61 9.53,8.47C9.39,8.329 9.199,8.25 9,8.25ZM9,0.667C10.094,0.667 11.178,0.882 12.189,1.301C13.2,1.72 14.119,2.334 14.892,3.107C15.666,3.881 16.28,4.8 16.699,5.811C17.118,6.822 17.333,7.906 17.333,9C17.333,11.21 16.455,13.33 14.892,14.892C13.33,16.455 11.21,17.333 9,17.333C7.906,17.333 6.822,17.118 5.811,16.699C4.8,16.28 3.881,15.666 3.107,14.892C1.545,13.33 0.667,11.21 0.667,9C0.667,6.79 1.545,4.67 3.107,3.107C4.67,1.545 6.79,0.667 9,0.667ZM9,2.333C7.232,2.333 5.536,3.036 4.286,4.286C3.036,5.536 2.333,7.232 2.333,9C2.333,10.768 3.036,12.464 4.286,13.714C5.536,14.964 7.232,15.667 9,15.667C10.768,15.667 12.464,14.964 13.714,13.714C14.964,12.464 15.667,10.768 15.667,9C15.667,7.232 14.964,5.536 13.714,4.286C12.464,3.036 10.768,2.333 9,2.333Z"
android:fillColor="#B0B0B0"/>
</vector>

View file

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportWidth="40"
android:viewportHeight="40">
<path
android:pathData="M20,20m-20,0a20,20 0,1 1,40 0a20,20 0,1 1,-40 0"
android:strokeAlpha="0.12"
android:fillColor="#B0B0B0"
android:fillAlpha="0.12"/>
<path
android:pathData="M19.08,12.08L21.08,12.08V24.08L26.58,18.58L28,20L20.08,27.92L12.16,20L13.58,18.58L19.08,24.08L19.08,12.08Z"
android:fillColor="#C9C9C9"/>
</vector>

View file

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportWidth="40"
android:viewportHeight="40">
<path
android:pathData="M20,20m-20,0a20,20 0,1 1,40 0a20,20 0,1 1,-40 0"
android:strokeAlpha="0.12"
android:fillColor="#B0B0B0"
android:fillAlpha="0.12"/>
<path
android:pathData="M21.08,27.92H19.08L19.08,15.92L13.58,21.42L12.16,20L20.08,12.08L28,20L26.58,21.42L21.08,15.92V27.92Z"
android:fillColor="#C9C9C9"/>
</vector>

View file

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="56dp"
android:animateLayoutChanges="true">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/imv_tx"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@drawable/ic_tx_incoming" />
<TextView
android:id="@+id/tv_tx_hash"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="68dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="24dp"
android:letterSpacing="0.01"
android:singleLine="true"
android:textColor="@color/text_primary_1"
android:textStyle="bold"
app:layout_constraintEnd_toStartOf="@+id/tv_tx_amount_sign"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="33BdfSkjandfjkajksjjkgkajsbga2B" />
<TextView
android:id="@+id/tv_tx_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:letterSpacing="0.03"
android:textColor="@color/text_tertiary"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@+id/tv_tx_hash"
tools:text="12:43" />
<TextView
android:id="@+id/tv_tx_status"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="24dp"
android:layout_marginBottom="8dp"
android:ellipsize="end"
android:letterSpacing="0.03"
android:singleLine="true"
android:textColor="@color/text_attention"
android:textSize="12sp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/tv_tx_amount_sign"
app:layout_constraintStart_toStartOf="@+id/tv_tx_hash"
tools:text="In progress..." />
<TextView
android:id="@+id/tv_tx_amount_sign"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:letterSpacing="0.02"
android:textColor="@color/text_primary_1"
android:textSize="15sp"
app:layout_constraintBottom_toBottomOf="@+id/tv_tx_amount_value"
app:layout_constraintEnd_toStartOf="@+id/tv_tx_amount_value"
app:layout_constraintTop_toTopOf="@+id/tv_tx_amount_value"
tools:text="+" />
<TextView
android:id="@+id/tv_tx_amount_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:letterSpacing="0.02"
android:textColor="@color/text_primary_1"
android:textSize="15sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="443" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/tv_title_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:letterSpacing="0.02"
android:textColor="@color/text_tertiary"
tools:text="Today" />

View file

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<com.skydoves.androidveil.VeilLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:veilLayout_baseColor="@color/lightGray0"
app:veilLayout_highlightColor="@color/lightGray1"
app:veilLayout_radius="4dp"
app:veilLayout_shimmerEnable="true"
app:veilLayout_veiled="true"
tools:veilLayout_veiled="false">
<!--android:background="@drawable/rectangle_twin_background"-->
<!--android:backgroundTint="@color/text_attention"-->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="56dp">
<View
android:id="@+id/imv_tx"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/tv_tx_hash"
android:layout_width="0dp"
android:layout_height="20dp"
android:layout_marginStart="68dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="24dp"
app:layout_constraintEnd_toStartOf="@+id/tv_tx_amount"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/tv_tx_time"
android:layout_width="50dp"
android:layout_height="16dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@+id/tv_tx_hash" />
<View
android:id="@+id/tv_tx_amount"
android:layout_width="80dp"
android:layout_height="20dp"
android:layout_marginEnd="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.skydoves.androidveil.VeilLayout>

View file

@ -21,12 +21,10 @@
android:id="@+id/tv_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="@string/onboarding_balance_title"
android:textColor="@color/text_tertiary"
android:textSize="14sp"
android:textStyle="bold"
app:layout_constraintEnd_toStartOf="@id/tv_currency_name"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
@ -34,8 +32,8 @@
android:id="@+id/veil_balance"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
app:layout_constraintBottom_toTopOf="@id/tv_processing"
android:layout_marginTop="6dp"
app:layout_constraintBottom_toTopOf="@id/tv_unreachable"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_title"
@ -65,7 +63,7 @@
android:id="@+id/veil_balance_crypto"
android:layout_width="wrap_content"
android:layout_height="18dp"
android:layout_marginTop="4dp"
android:layout_marginTop="6dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/veil_balance"
app:veilLayout_baseColor="@color/lightGray0"
@ -81,7 +79,6 @@
android:id="@+id/tv_balance_crypto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="2dp"
android:maxLines="1"
android:minWidth="152dp"
android:textColor="@color/text_tertiary"
@ -92,14 +89,14 @@
</com.skydoves.androidveil.VeilLayout>
<TextView
android:id="@+id/tv_processing"
android:id="@+id/tv_unreachable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_height="18dp"
android:layout_marginTop="6dp"
android:text="@string/wallet_balance_blockchain_unreachable"
android:textColor="@color/warning"
android:textSize="12sp"
android:visibility="gone"
android:visibility="visible"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/veil_balance" />
@ -108,7 +105,7 @@
style="@style/BaseSaltPayButton"
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_marginTop="12dp"
android:layout_marginTop="76dp"
android:text="@string/wallet_button_buy"
android:visibility="gone"
app:icon="@drawable/ic_add"
@ -116,7 +113,7 @@
app:iconTint="@color/selector_saltpay_btn_text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/veil_balance_crypto"
app:layout_constraintTop_toBottomOf="@+id/tv_title"
tools:visibility="visible" />
<TextView

View file

@ -0,0 +1,136 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/card_balance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="12dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:clipToPadding="false"
android:minHeight="272dp"
android:paddingBottom="8dp">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_tx_history_shimmer"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="42dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:listitem="@layout/item_salt_pay_tx_history_shimmer" />
<TextView
android:id="@+id/tv_title_tx"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="12dp"
android:letterSpacing="0.01"
android:text="Transactions"
android:textColor="@color/text_tertiary"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_title_explore"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:layout_marginEnd="16dp"
android:drawableLeft="@drawable/ic_tx_explore"
android:drawablePadding="4dp"
android:letterSpacing="0.01"
android:text="Explore"
android:textColor="@color/text_tertiary"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/imv_tx_list_empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@+id/tv_tx_list_empty"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
app:srcCompat="@drawable/ic_accept_coin" />
<TextView
android:id="@+id/tv_tx_list_empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="24dp"
android:text="You don't have any transactions yet"
android:textColor="@color/text_tertiary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imv_tx_list_empty" />
<TextView
android:id="@+id/tv_tx_list_error"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:text="Failed to load transactions"
android:textColor="@color/text_tertiary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_tx_history"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="42dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:listitem="@layout/item_salt_pay_tx_history" />
<androidx.constraintlayout.widget.Group
android:id="@+id/group_shimmer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:constraint_referenced_ids="rv_tx_history_shimmer" />
<androidx.constraintlayout.widget.Group
android:id="@+id/group_empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:constraint_referenced_ids="imv_tx_list_empty, tv_tx_list_empty" />
<androidx.constraintlayout.widget.Group
android:id="@+id/group_error"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:constraint_referenced_ids="tv_tx_list_error" />
<androidx.constraintlayout.widget.Group
android:id="@+id/group_success"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:constraint_referenced_ids="rv_tx_history" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>

View file

@ -19,4 +19,14 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<include
android:id="@+id/l_salt_pay_tx_history"
layout="@layout/layout_salt_pay_tx_history"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/l_salt_pay_balance" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -59,8 +59,8 @@ object Versions {
// endregion Other libraries
// region Tangem
const val tangemBlockchainSdk = "develop-152-hotfix-4.0.1-156"
// const val tangemBlockchainSdk = "0.0.1"
// const val tangemBlockchainSdk = "develop-152-hotfix-4.0.1-156"
const val tangemBlockchainSdk = "0.0.1"
const val tangemCardSdk = "develop-179-hotfix-4.0.1-189"
// endregion Tangem