Updated on 2026-08-14
This commit is contained in:
commit
2c32846da1
40 changed files with 1119 additions and 230 deletions
|
|
@ -1 +1 @@
|
|||
Subproject commit 2b8aa19f58e17a78015629943d49df0da0b4fd4b
|
||||
Subproject commit f5f6ac954894ef66331db8fe383b9773feb69ef4
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
package com.tangem.tap.common
|
||||
|
||||
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 = "")
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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?,
|
||||
|
|
|
|||
|
|
@ -1,19 +1,15 @@
|
|||
package com.tangem.tap.domain.extensions
|
||||
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.DerivationParams
|
||||
import com.tangem.blockchain.common.DerivationStyle
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchain.common.WalletManagerFactory
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.extensions.toMapKey
|
||||
import com.tangem.common.hdWallet.DerivationPath
|
||||
import com.tangem.domain.common.CardDTO
|
||||
import com.tangem.domain.common.SaltPayWorkaround
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
|
||||
|
|
@ -137,23 +133,4 @@ private fun selectWallet(wallets: List<CardDTO.Wallet>): CardDTO.Wallet? {
|
|||
1 -> wallets[0]
|
||||
else -> wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: wallets[0]
|
||||
}
|
||||
}
|
||||
|
||||
fun WalletManagerFactory.makeSaltPayWalletManager(
|
||||
scanResponse: ScanResponse,
|
||||
): EthereumWalletManager {
|
||||
val blockchain = scanResponse.getBlockchain()
|
||||
if (blockchain != Blockchain.SaltPay)
|
||||
throw IllegalArgumentException()
|
||||
|
||||
val token = SaltPayWorkaround.tokenFrom(blockchain)
|
||||
val cardWallet = scanResponse.card.wallets.firstOrNull().guard {
|
||||
throw NullPointerException("SaltPay card must have one wallet at least")
|
||||
}
|
||||
|
||||
return makeWalletManager(
|
||||
blockchain = blockchain,
|
||||
publicKey = Wallet.PublicKey(cardWallet.publicKey, null, null),
|
||||
tokens = listOf(token),
|
||||
) as EthereumWalletManager
|
||||
}
|
||||
|
|
@ -27,10 +27,10 @@ import com.tangem.tap.features.onboarding.OnboardingHelper
|
|||
import com.tangem.tap.features.onboarding.OnboardingSaltPayHelper
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManagerFactory
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayExceptionHandler
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayState
|
||||
import com.tangem.tap.preferencesStorage
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -187,7 +187,10 @@ object ScanCardProcessor {
|
|||
|
||||
if (scanResponse.isSaltPay()) {
|
||||
if (scanResponse.isSaltPayVisa()) {
|
||||
val (manager, config) = OnboardingSaltPayState.initDependency(scanResponse)
|
||||
val manager = SaltPayActivationManagerFactory(
|
||||
blockchain = scanResponse.getBlockchain(),
|
||||
card = scanResponse.card,
|
||||
).create()
|
||||
val result = OnboardingSaltPayHelper.isOnboardingCase(scanResponse, manager)
|
||||
delay(500)
|
||||
withMainContext {
|
||||
|
|
@ -197,7 +200,7 @@ object ScanCardProcessor {
|
|||
if (isOnboardingCase) {
|
||||
onWalletNotCreated()
|
||||
store.dispatch(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = false))
|
||||
store.dispatch(OnboardingSaltPayAction.SetDependencies(manager, config))
|
||||
store.dispatch(OnboardingSaltPayAction.SetDependencies(manager))
|
||||
store.dispatch(OnboardingSaltPayAction.Update)
|
||||
navigateTo(AppScreen.OnboardingWallet) { onProgressStateChange(it) }
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@ package com.tangem.tap.features.onboarding.products.wallet.redux
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.extensions.ifNotNull
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.common.CardDTO
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.TapWorkarounds.isSaltPay
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.operations.backup.BackupService
|
||||
import com.tangem.tap.backupService
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
|
|
@ -22,8 +22,8 @@ import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
|||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
import com.tangem.tap.features.onboarding.OnboardingHelper
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManagerFactory
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayState
|
||||
import com.tangem.tap.features.wallet.redux.Artwork
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.preferencesStorage
|
||||
|
|
@ -396,8 +396,11 @@ private fun initSaltPayOnBackupFinishedIfNeeded(
|
|||
if (onboardingWalletState.isSaltPay && onboardingWalletState.onboardingSaltPayState == null) {
|
||||
if (scanResponse == null) throw IllegalArgumentException()
|
||||
|
||||
val (manager, config) = OnboardingSaltPayState.initDependency(scanResponse)
|
||||
store.dispatchOnMain(OnboardingSaltPayAction.SetDependencies(manager, config))
|
||||
val manager = SaltPayActivationManagerFactory(
|
||||
blockchain = scanResponse.getBlockchain(),
|
||||
card = scanResponse.card,
|
||||
).create()
|
||||
store.dispatchOnMain(OnboardingSaltPayAction.SetDependencies(manager))
|
||||
store.dispatchOnMain(OnboardingSaltPayAction.Update)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ import kotlinx.coroutines.awaitAll
|
|||
import kotlinx.coroutines.coroutineScope
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
import java.math.MathContext
|
||||
import java.math.RoundingMode
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
|
||||
/**
|
||||
|
|
@ -33,11 +35,12 @@ class GnosisRegistrator(
|
|||
) {
|
||||
|
||||
private val walletAddress = walletManager.wallet.address
|
||||
private val blockchain = walletManager.wallet.blockchain
|
||||
private val token = walletManager.wallet.getFirstToken().guard {
|
||||
throw NullPointerException("WalletManager for GnosisRegistrator must contain token")
|
||||
}
|
||||
|
||||
private val otpProcessorContractAddress: String = when (walletManager.wallet.blockchain) {
|
||||
private val otpProcessorContractAddress: String = when (blockchain) {
|
||||
Blockchain.SaltPay -> "0xc659f4FEd7A84a188F54cBA4A7a49D77c1a20522"
|
||||
else -> throw IllegalArgumentException("GnosisRegistrator supports only the SaltPay blockchain")
|
||||
}
|
||||
|
|
@ -154,6 +157,12 @@ class GnosisRegistrator(
|
|||
return Result.Failure(BlockchainSdkError.WrappedThrowable(it.error))
|
||||
}
|
||||
}
|
||||
|
||||
// return transferFromStandardWay(amountToClaim, signer)
|
||||
return transferFromHardcodeWay(amountToClaim, signer)
|
||||
}
|
||||
|
||||
private suspend fun transferFromStandardWay(amountToClaim: BigDecimal, signer: TransactionSigner): Result<Unit> {
|
||||
val amount = Amount(token, amountToClaim)
|
||||
val feeAmount = walletManager.getFeeToTransferFrom(amount, addressTreasureSafe)
|
||||
.extractFeeAmount().successOr { return it }
|
||||
|
|
@ -164,6 +173,34 @@ class GnosisRegistrator(
|
|||
return Result.Success(Unit)
|
||||
}
|
||||
|
||||
private suspend fun transferFromHardcodeWay(amountToClaim: BigDecimal, signer: TransactionSigner): Result<Unit> {
|
||||
val amount = Amount(token, amountToClaim)
|
||||
val gasPrice = walletManager.getGasPrice().successOr { return it }
|
||||
|
||||
val hardcodeGasLimit = BigInteger("300000")
|
||||
val hardcodeFeeValue = (hardcodeGasLimit * gasPrice).toBigDecimal(
|
||||
scale = blockchain.decimals(),
|
||||
mathContext = MathContext(blockchain.decimals(), RoundingMode.HALF_EVEN),
|
||||
).movePointLeft(blockchain.decimals())
|
||||
val hardcodeFeeAmount = Amount(hardcodeFeeValue, blockchain)
|
||||
|
||||
val transactionData = walletManager.createTransferFromTransaction(
|
||||
amount = amount,
|
||||
fee = hardcodeFeeAmount,
|
||||
source = addressTreasureSafe,
|
||||
)
|
||||
val transactionToSign = walletManager.transactionBuilder.buildTransferFromToSign(
|
||||
transactionData,
|
||||
walletManager.txCount.toBigInteger(),
|
||||
hardcodeGasLimit,
|
||||
) ?: return Result.Failure(BlockchainSdkError.CustomError("Not enough data"))
|
||||
|
||||
return when (val result = walletManager.signAndSend(transactionToSign, signer)) {
|
||||
SimpleResult.Success -> Result.Success(Unit)
|
||||
is SimpleResult.Failure -> Result.Failure(result.error)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Result<List<Amount>>.extractFeeAmount(): Result<Amount> {
|
||||
return when (this) {
|
||||
is Result.Success -> {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,20 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.saltPay
|
||||
|
||||
import android.net.Uri
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
|
||||
import com.tangem.blockchain.blockchains.ethereum.SignedEthereumTransaction
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.BlockchainSdkError
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchain.common.TransactionSigner
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.blockchain.common.WalletManagerFactory
|
||||
import com.tangem.blockchain.extensions.successOr
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.datasource.api.paymentology.AttestationResponse
|
||||
|
|
@ -18,12 +24,15 @@ import com.tangem.datasource.api.paymentology.RegisterWalletRequest
|
|||
import com.tangem.datasource.api.paymentology.RegisterWalletResponse
|
||||
import com.tangem.datasource.api.paymentology.RegistrationResponse
|
||||
import com.tangem.datasource.api.paymentology.tryExtractError
|
||||
import com.tangem.domain.common.CardDTO
|
||||
import com.tangem.domain.common.SaltPayWorkaround
|
||||
import com.tangem.domain.common.extensions.successOr
|
||||
import com.tangem.operations.attestation.AttestWalletKeyResponse
|
||||
import com.tangem.tap.common.extensions.safeUpdate
|
||||
import com.tangem.tap.domain.getFirstToken
|
||||
import com.tangem.tap.domain.model.builders.UserWalletIdBuilder
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError
|
||||
import com.tangem.tap.store
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
|
|
@ -200,4 +209,96 @@ class KYCUrlProvider(
|
|||
.appendQueryParameter(kycProvider.externalIdParameterKey, kycRefId)
|
||||
.build().toString()
|
||||
}
|
||||
}
|
||||
|
||||
class SaltPayActivationManagerFactory(
|
||||
private val blockchain: Blockchain,
|
||||
private val card: CardDTO,
|
||||
) {
|
||||
|
||||
fun create(): SaltPayActivationManager {
|
||||
val wallet = card.wallets.first()
|
||||
val saltPayConfig = store.state.globalState.configManager?.config?.saltPayConfig.guard {
|
||||
throw NullPointerException("SaltPayConfig is not initialized")
|
||||
}
|
||||
val wmFactory = store.state.globalState.tapWalletManager.walletManagerFactory
|
||||
val paymentologyService = store.state.domainNetworks.paymentologyService
|
||||
|
||||
// return createDummyActivationManager(saltPayConfig, wmFactory, paymentologyService)
|
||||
return createActivationPayManager(
|
||||
cardId = card.cardId,
|
||||
cardPublicKey = card.cardPublicKey,
|
||||
kycProvider = saltPayConfig.kycProvider,
|
||||
gnosisRegistrator = GnosisRegistrator(
|
||||
walletManager = makeSaltPayWalletManager(
|
||||
blockchain = blockchain,
|
||||
walletPublicKey = wallet.publicKey,
|
||||
wmFactory = wmFactory,
|
||||
),
|
||||
),
|
||||
paymentologyService = paymentologyService,
|
||||
)
|
||||
}
|
||||
|
||||
private fun makeSaltPayWalletManager(
|
||||
blockchain: Blockchain,
|
||||
walletPublicKey: ByteArray,
|
||||
wmFactory: WalletManagerFactory,
|
||||
): EthereumWalletManager {
|
||||
return wmFactory.makeWalletManager(
|
||||
blockchain = blockchain,
|
||||
publicKey = Wallet.PublicKey(walletPublicKey, null, null),
|
||||
tokens = listOf(SaltPayWorkaround.tokenFrom(blockchain)),
|
||||
) as EthereumWalletManager
|
||||
}
|
||||
|
||||
private fun createActivationPayManager(
|
||||
cardId: String,
|
||||
cardPublicKey: ByteArray,
|
||||
kycProvider: KYCProvider,
|
||||
gnosisRegistrator: GnosisRegistrator,
|
||||
paymentologyService: PaymentologyApiService,
|
||||
): SaltPayActivationManager {
|
||||
return SaltPayActivationManager(
|
||||
cardId = cardId,
|
||||
cardPublicKey = cardPublicKey,
|
||||
kycProvider = kycProvider,
|
||||
paymentologyService = paymentologyService,
|
||||
gnosisRegistrator = gnosisRegistrator,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createDummyActivationManager(
|
||||
saltPayConfig: SaltPayConfig,
|
||||
wmFactory: WalletManagerFactory,
|
||||
paymentologyService: PaymentologyApiService,
|
||||
): SaltPayActivationManager {
|
||||
val fakeSource = DummySaltPayCardSource()
|
||||
|
||||
val walletManager = wmFactory.makeWalletManager(
|
||||
blockchain = fakeSource.blockchain,
|
||||
publicKey = fakeSource.blockchainPublicKey,
|
||||
tokens = listOf(fakeSource.token),
|
||||
) as EthereumWalletManager
|
||||
|
||||
return createActivationPayManager(
|
||||
cardId = fakeSource.cardId,
|
||||
cardPublicKey = fakeSource.cardPublicKey,
|
||||
kycProvider = saltPayConfig.kycProvider,
|
||||
gnosisRegistrator = GnosisRegistrator(walletManager),
|
||||
paymentologyService = paymentologyService,
|
||||
)
|
||||
}
|
||||
|
||||
private data class DummySaltPayCardSource(
|
||||
val blockchain: Blockchain = Blockchain.SaltPay,
|
||||
val cardId: String = "FF03000001001057",
|
||||
private val cardPublicKeyString: String = "039C2D2F9B68003766BFC29766761F55F4084E48B8B012BF439E115A79AB122D48",
|
||||
private val walletPublicKeyString: String = "0399B2DF5B129FBF69097DAA7C44FB5849E578CCFC22228A409870C74B56C4C3D1",
|
||||
) {
|
||||
val cardPublicKey: ByteArray = cardPublicKeyString.hexToBytes()
|
||||
val walletPublicKey: ByteArray = walletPublicKeyString.hexToBytes()
|
||||
val blockchainPublicKey = Wallet.PublicKey(walletPublicKey, null, null)
|
||||
val token: Token = SaltPayWorkaround.tokenFrom(blockchain)
|
||||
}
|
||||
}
|
||||
|
|
@ -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("", ""),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.tangem.tap.features.onboarding.products.wallet.saltPay.redux
|
|||
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManager
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayConfig
|
||||
import org.rekotlin.Action
|
||||
import java.math.BigDecimal
|
||||
|
||||
|
|
@ -14,7 +13,6 @@ sealed class OnboardingSaltPayAction : Action {
|
|||
|
||||
data class SetDependencies(
|
||||
val registrationManager: SaltPayActivationManager,
|
||||
val saltPayConfig: SaltPayConfig,
|
||||
) : OnboardingSaltPayAction()
|
||||
|
||||
data class SetInProgress(val isInProgress: Boolean) : OnboardingSaltPayAction()
|
||||
|
|
|
|||
|
|
@ -91,6 +91,17 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App
|
|||
|
||||
handleInProgress = false
|
||||
dispatchOnMain(OnboardingSaltPayAction.SetStep(updateStep))
|
||||
|
||||
if (updateStep == SaltPayActivationStep.Claim) {
|
||||
handleClaimRefreshInProgress = true
|
||||
val tokenAmountValue = state.saltPayManager.getTokenAmount().successOr {
|
||||
SaltPayExceptionHandler.handle(it.error)
|
||||
handleClaimRefreshInProgress = false
|
||||
return@launch
|
||||
}
|
||||
dispatchOnMain(OnboardingSaltPayAction.SetTokenBalance(tokenAmountValue))
|
||||
handleClaimRefreshInProgress = false
|
||||
}
|
||||
}
|
||||
}
|
||||
is OnboardingSaltPayAction.RegisterCard -> {
|
||||
|
|
@ -220,35 +231,29 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App
|
|||
|
||||
val state = getState()
|
||||
scope.launch {
|
||||
when (val amountToClaimResult = state.saltPayManager.getAmountToClaim()) {
|
||||
is Result.Success -> {
|
||||
// need to re claim
|
||||
handleClaimRefreshInProgress = false
|
||||
dispatchOnMain(OnboardingSaltPayAction.SetAmountToClaim(amountToClaimResult.data))
|
||||
dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.Claim))
|
||||
}
|
||||
is Result.Failure -> {
|
||||
when (amountToClaimResult.error) {
|
||||
is SaltPayActivationError.NoFundsToClaim -> {
|
||||
val tokenAmountValue = state.saltPayManager.getTokenAmount().successOr {
|
||||
SaltPayExceptionHandler.handle(it.error)
|
||||
handleClaimRefreshInProgress = false
|
||||
return@launch
|
||||
}
|
||||
val tokenAmountValue = state.saltPayManager.getTokenAmount().successOr {
|
||||
SaltPayExceptionHandler.handle(it.error)
|
||||
handleClaimRefreshInProgress = false
|
||||
return@launch
|
||||
}
|
||||
dispatchOnMain(OnboardingSaltPayAction.SetTokenBalance(tokenAmountValue))
|
||||
|
||||
handleClaimRefreshInProgress = false
|
||||
if (tokenAmountValue.isPositive()) {
|
||||
dispatchOnMain(OnboardingSaltPayAction.SetTokenBalance(tokenAmountValue))
|
||||
dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.ClaimSuccess))
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
handleClaimRefreshInProgress = false
|
||||
SaltPayExceptionHandler.handle(amountToClaimResult.error)
|
||||
}
|
||||
}
|
||||
val amountToClaim = state.saltPayManager.getAmountToClaim().successOr {
|
||||
if (it.error !is SaltPayActivationError.NoFundsToClaim) {
|
||||
handleClaimRefreshInProgress = false
|
||||
SaltPayExceptionHandler.handle(it.error)
|
||||
return@launch
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
handleClaimRefreshInProgress = false
|
||||
|
||||
if (tokenAmountValue.isPositive() && amountToClaim == null) {
|
||||
dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.ClaimSuccess))
|
||||
} else {
|
||||
// dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.ClaimSuccess))
|
||||
}
|
||||
}
|
||||
}
|
||||
is OnboardingSaltPayAction.SetStep -> {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ private fun internalReduce(anyAction: Action, onboardingWalletState: OnboardingW
|
|||
return onboardingWalletState.copy(
|
||||
onboardingSaltPayState = OnboardingSaltPayState(
|
||||
saltPayManager = action.registrationManager,
|
||||
saltPayConfig = action.saltPayConfig,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,19 +2,10 @@ package com.tangem.tap.features.onboarding.products.wallet.saltPay.redux
|
|||
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.WalletManagerFactory
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.domain.common.SaltPayWorkaround
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.datasource.api.paymentology.PaymentologyApiService
|
||||
import com.tangem.tap.common.toggleWidget.WidgetState
|
||||
import com.tangem.tap.domain.extensions.makeSaltPayWalletManager
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.GnosisRegistrator
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.KYCProvider
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManager
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayConfig
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.store
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
|
|
@ -23,7 +14,6 @@ import java.math.BigDecimal
|
|||
data class OnboardingSaltPayState(
|
||||
@Transient
|
||||
val saltPayManager: SaltPayActivationManager = SaltPayActivationManager.stub(),
|
||||
val saltPayConfig: SaltPayConfig = SaltPayConfig.stub(),
|
||||
val pinCode: String? = null,
|
||||
val accessCode: String? = null,
|
||||
val amountToClaim: Amount? = null,
|
||||
|
|
@ -37,57 +27,7 @@ data class OnboardingSaltPayState(
|
|||
val mainButtonState: WidgetState
|
||||
get() = if (inProgress) ProgressState.Loading else ProgressState.Done
|
||||
|
||||
fun readyToClaim(): Boolean = amountToClaim != null
|
||||
|
||||
val pinLength: Int = 4
|
||||
|
||||
companion object {
|
||||
fun initDependency(scanResponse: ScanResponse): Pair<SaltPayActivationManager, SaltPayConfig> {
|
||||
val globalState = store.state.globalState
|
||||
val saltPayConfig = globalState.configManager?.config?.saltPayConfig.guard {
|
||||
throw NullPointerException("SaltPayConfig is not initialized")
|
||||
}
|
||||
val gnosisRegistrator = makeGnosisRegistrator(
|
||||
scanResponse = scanResponse,
|
||||
wmFactory = globalState.tapWalletManager.walletManagerFactory,
|
||||
)
|
||||
val registrationManager = makeSaltPayRegistrationManager(
|
||||
scanResponse = scanResponse,
|
||||
gnosisRegistrator = gnosisRegistrator,
|
||||
paymentologyService = store.state.domainNetworks.paymentologyService,
|
||||
kycProvider = saltPayConfig.kycProvider,
|
||||
)
|
||||
return registrationManager to saltPayConfig
|
||||
}
|
||||
|
||||
fun makeSaltPayRegistrationManager(
|
||||
scanResponse: ScanResponse,
|
||||
gnosisRegistrator: GnosisRegistrator,
|
||||
paymentologyService: PaymentologyApiService,
|
||||
kycProvider: KYCProvider,
|
||||
): SaltPayActivationManager {
|
||||
if (!scanResponse.isSaltPay()) {
|
||||
throw IllegalArgumentException("Can't initialize the OnboardingSaltPayMiddleware if card is not SalPay")
|
||||
}
|
||||
|
||||
return SaltPayActivationManager(
|
||||
cardId = scanResponse.card.cardId,
|
||||
cardPublicKey = scanResponse.card.cardPublicKey,
|
||||
kycProvider = kycProvider,
|
||||
paymentologyService = paymentologyService,
|
||||
gnosisRegistrator = gnosisRegistrator,
|
||||
)
|
||||
}
|
||||
|
||||
fun makeGnosisRegistrator(
|
||||
scanResponse: ScanResponse,
|
||||
wmFactory: WalletManagerFactory,
|
||||
): GnosisRegistrator {
|
||||
return GnosisRegistrator(
|
||||
walletManager = wmFactory.makeSaltPayWalletManager(scanResponse),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class SaltPayActivationStep {
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,233 @@
|
|||
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.ShimmerData
|
||||
import com.tangem.tap.common.ShimmerRecyclerAdapter
|
||||
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.HistoryItemData
|
||||
import com.tangem.tap.features.wallet.ui.wallet.saltPay.rv.HistoryTransactionData
|
||||
import com.tangem.tap.features.wallet.ui.wallet.saltPay.rv.TxHistoryAdapter
|
||||
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 = TxHistoryAdapter()
|
||||
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 dateAssociatedHistory = mutableMapOf<String, MutableList<HistoryTransactionData>>()
|
||||
|
||||
result.data
|
||||
.filter { it.contractAddress == token.contractAddress }
|
||||
.sortedByDescending { it.date?.timeInMillis ?: 0 }
|
||||
.forEach {
|
||||
val txData = HistoryTransactionData(it, walletAddress)
|
||||
val list = dateAssociatedHistory[txData.date] ?: mutableListOf()
|
||||
list.add(txData)
|
||||
dateAssociatedHistory[txData.date] = list
|
||||
}
|
||||
|
||||
val dataList = mutableListOf<HistoryItemData>()
|
||||
dateAssociatedHistory.forEach { entry ->
|
||||
dataList.add(HistoryItemData.Date(entry.key))
|
||||
entry.value.forEach { dataList.add(HistoryItemData.TransactionData(it)) }
|
||||
}
|
||||
// .toMutableList().apply { clear() }
|
||||
|
||||
delay(300)
|
||||
withMainContext {
|
||||
if (dataList.isEmpty()) {
|
||||
handleTxEmpty()
|
||||
} else {
|
||||
handleTxSuccess(dataList)
|
||||
}
|
||||
}
|
||||
}
|
||||
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<HistoryItemData>) = 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<HistoryItemData>) = with(txWidget) {
|
||||
(rvTxHistory.adapter as TxHistoryAdapter).submitList(dataList)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
package com.tangem.tap.features.wallet.ui.wallet.saltPay.rv
|
||||
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.blockchain.common.TransactionStatus
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.tap.common.extensions.toFormattedCurrencyString
|
||||
import com.tangem.tap.features.wallet.models.PendingTransactionType
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class HistoryItemData(
|
||||
val viewType: Int,
|
||||
val itemId: Long,
|
||||
) {
|
||||
data class Date(val date: String) : HistoryItemData(0, date.hashCode().toLong())
|
||||
data class TransactionData(val data: HistoryTransactionData) : HistoryItemData(1, data.hash.hashCode().toLong())
|
||||
}
|
||||
|
||||
data class HistoryTransactionData(
|
||||
private val transactionData: TransactionData,
|
||||
private val walletAddress: String,
|
||||
) {
|
||||
|
||||
val isInProgress: Boolean = transactionData.status == TransactionStatus.Unconfirmed
|
||||
|
||||
val transactionType: PendingTransactionType = when {
|
||||
transactionData.sourceAddress.lowercase() == walletAddress.lowercase() -> PendingTransactionType.Outgoing
|
||||
transactionData.destinationAddress.lowercase() == walletAddress.lowercase() -> PendingTransactionType.Incoming
|
||||
else -> PendingTransactionType.Unknown
|
||||
}
|
||||
|
||||
val hash: String = transactionData.hash?.let {
|
||||
"${it.substring(0..5)}...${it.substring(it.length - 4)}"
|
||||
} ?: ""
|
||||
|
||||
val time: String = transactionData.date?.let {
|
||||
SimpleDateFormat("hh:MM").format(it.time)
|
||||
} ?: "00:00"
|
||||
|
||||
val date: String = calculateDate()
|
||||
|
||||
val txSign: String = when (transactionType) {
|
||||
PendingTransactionType.Incoming -> "+"
|
||||
PendingTransactionType.Outgoing -> "-"
|
||||
PendingTransactionType.Unknown -> ""
|
||||
}
|
||||
|
||||
val amountValue: String = transactionData.amount.value
|
||||
?.toFormattedCurrencyString(8, transactionData.amount.currencySymbol) ?: "0"
|
||||
|
||||
private fun calculateDate(): String {
|
||||
val calendarTx = transactionData.date.guard { return "Unknown" }
|
||||
|
||||
val calendarNow = Calendar.getInstance()
|
||||
val oldDateFormatter = SimpleDateFormat("dd.MM.yyyy")
|
||||
|
||||
val date = if (calendarTx.get(Calendar.YEAR) == calendarNow.get(Calendar.YEAR)) {
|
||||
val dayOfYearTx = calendarTx.get(Calendar.DAY_OF_YEAR)
|
||||
val dayOfYearNow = calendarNow.get(Calendar.DAY_OF_YEAR)
|
||||
when {
|
||||
dayOfYearNow == dayOfYearTx -> "Today"
|
||||
dayOfYearNow - dayOfYearTx == 1 -> "Tomorrow"
|
||||
else -> oldDateFormatter.format(calendarTx.time)
|
||||
}
|
||||
} else {
|
||||
oldDateFormatter.format(calendarTx.time)
|
||||
}
|
||||
|
||||
return date
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
package com.tangem.tap.features.wallet.ui.wallet.saltPay.rv
|
||||
|
||||
import android.graphics.PorterDuff
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
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.features.wallet.models.PendingTransactionType
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.ItemSaltPayTxHistoryBinding
|
||||
import com.tangem.wallet.databinding.ItemSaltPayTxHistoryDateBinding
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class TxHistoryAdapter : ListAdapter<HistoryItemData, BaseTxHistoryVH>(DiffUtilCallback) {
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
return if (currentList.isEmpty()) 0L
|
||||
else currentList[position].itemId
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseTxHistoryVH {
|
||||
val inflater = LayoutInflater.from(parent.context)
|
||||
|
||||
return when (viewType) {
|
||||
0 -> {
|
||||
val layout = ItemSaltPayTxHistoryDateBinding.inflate(inflater, parent, false)
|
||||
DateItemVH(layout)
|
||||
}
|
||||
1 -> {
|
||||
val layout = ItemSaltPayTxHistoryBinding.inflate(inflater, parent, false)
|
||||
TransactionItemVH(layout)
|
||||
}
|
||||
else -> throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int = currentList[position].viewType
|
||||
|
||||
override fun onBindViewHolder(holder: BaseTxHistoryVH, position: Int) {
|
||||
holder.bind(currentList[position])
|
||||
}
|
||||
|
||||
private object DiffUtilCallback : DiffUtil.ItemCallback<HistoryItemData>() {
|
||||
override fun areContentsTheSame(oldItem: HistoryItemData, newItem: HistoryItemData) = oldItem == newItem
|
||||
override fun areItemsTheSame(oldItem: HistoryItemData, newItem: HistoryItemData) = oldItem == newItem
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract class BaseTxHistoryVH(layout: View) : RecyclerView.ViewHolder(layout) {
|
||||
abstract fun bind(data: HistoryItemData)
|
||||
}
|
||||
|
||||
private class DateItemVH(
|
||||
private val binding: ItemSaltPayTxHistoryDateBinding,
|
||||
) : BaseTxHistoryVH(binding.root) {
|
||||
|
||||
override fun bind(data: HistoryItemData) = with(binding) {
|
||||
val vhData = (data as? HistoryItemData.Date)?.date ?: return
|
||||
|
||||
tvTitleDate.text = vhData
|
||||
}
|
||||
}
|
||||
|
||||
private class TransactionItemVH(
|
||||
private val binding: ItemSaltPayTxHistoryBinding,
|
||||
) : BaseTxHistoryVH(binding.root) {
|
||||
|
||||
override fun bind(data: HistoryItemData) {
|
||||
val vhData = (data as? HistoryItemData.TransactionData)?.data ?: return
|
||||
setupImage(vhData)
|
||||
setupTitle(vhData)
|
||||
setupSubtitle(vhData)
|
||||
setupBalance(vhData)
|
||||
}
|
||||
|
||||
private fun setupImage(data: HistoryTransactionData) = with(binding) {
|
||||
val drawable = when (data.transactionType) {
|
||||
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.SRC_ATOP)
|
||||
} else {
|
||||
imvTx.clearColorFilter()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupTitle(data: HistoryTransactionData) = with(binding) {
|
||||
tvTxHash.text = data.hash
|
||||
}
|
||||
|
||||
private fun setupSubtitle(data: HistoryTransactionData) = with(binding) {
|
||||
tvTxStatus.show(data.isInProgress)
|
||||
tvTxTime.show(!data.isInProgress)
|
||||
tvTxTime.text = data.time
|
||||
}
|
||||
|
||||
private fun setupBalance(data: HistoryTransactionData) = with(binding) {
|
||||
tvTxAmountSign.text = data.txSign
|
||||
tvTxAmountValue.text = data.amountValue
|
||||
}
|
||||
}
|
||||
|
|
@ -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 ->
|
||||
|
|
|
|||
57
app/src/main/res/drawable/ic_accept_coin.xml
Normal file
57
app/src/main/res/drawable/ic_accept_coin.xml
Normal file
File diff suppressed because one or more lines are too long
9
app/src/main/res/drawable/ic_tx_explore.xml
Normal file
9
app/src/main/res/drawable/ic_tx_explore.xml
Normal 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>
|
||||
14
app/src/main/res/drawable/ic_tx_incoming.xml
Normal file
14
app/src/main/res/drawable/ic_tx_incoming.xml
Normal 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>
|
||||
14
app/src/main/res/drawable/ic_tx_outgoing.xml
Normal file
14
app/src/main/res/drawable/ic_tx_outgoing.xml
Normal 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>
|
||||
89
app/src/main/res/layout/item_salt_pay_tx_history.xml
Normal file
89
app/src/main/res/layout/item_salt_pay_tx_history.xml
Normal 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:text="@string/transaction_history_tx_in_progress"
|
||||
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" />
|
||||
|
||||
<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>
|
||||
20
app/src/main/res/layout/item_salt_pay_tx_history_date.xml
Normal file
20
app/src/main/res/layout/item_salt_pay_tx_history_date.xml
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<?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="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_title_date"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:letterSpacing="0.02"
|
||||
android:textColor="@color/text_tertiary"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Today" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
60
app/src/main/res/layout/item_salt_pay_tx_history_shimmer.xml
Normal file
60
app/src/main/res/layout/item_salt_pay_tx_history_shimmer.xml
Normal 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>
|
||||
|
|
@ -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
|
||||
|
|
|
|||
136
app/src/main/res/layout/layout_salt_pay_tx_history.xml
Normal file
136
app/src/main/res/layout/layout_salt_pay_tx_history.xml
Normal 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="@string/transaction_history_title"
|
||||
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="@string/transaction_history_empty_transactions"
|
||||
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="@string/transaction_history_error_failed_to_load"
|
||||
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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -449,6 +449,10 @@
|
|||
<string name="app_settings_enable_biometrics_description">Go to settings to enable biometric authentication in the Tangem App</string>
|
||||
<string name="wallet_balance_missing_derivation">Scan the card</string>
|
||||
<string name="disclaimer_error_loading">Check your internet connection or switch to a different network</string>
|
||||
<string name="transaction_history_tx_in_progress">In progress…</string>
|
||||
<string name="transaction_history_error_failed_to_load">Failed to load transactions</string>
|
||||
<string name="transaction_history_empty_transactions">You don\'t have any transactions yet</string>
|
||||
<string name="transaction_history_title">Transactions</string>
|
||||
|
||||
<!-- Special string -->
|
||||
<string name="common_custom_string">%s</string>
|
||||
|
|
|
|||
|
|
@ -449,6 +449,10 @@
|
|||
<string name="app_settings_enable_biometrics_description">Go to settings to enable biometric authentication in the Tangem App</string>
|
||||
<string name="wallet_balance_missing_derivation">Scan the card</string>
|
||||
<string name="disclaimer_error_loading">Check your internet connection or switch to a different network</string>
|
||||
<string name="transaction_history_tx_in_progress">In progress…</string>
|
||||
<string name="transaction_history_error_failed_to_load">Failed to load transactions</string>
|
||||
<string name="transaction_history_empty_transactions">You don\'t have any transactions yet</string>
|
||||
<string name="transaction_history_title">Transactions</string>
|
||||
|
||||
<!-- Special string -->
|
||||
<string name="common_custom_string">%s</string>
|
||||
|
|
|
|||
|
|
@ -449,6 +449,10 @@
|
|||
<string name="app_settings_enable_biometrics_description">Go to settings to enable biometric authentication in the Tangem App</string>
|
||||
<string name="wallet_balance_missing_derivation">Scan the card</string>
|
||||
<string name="disclaimer_error_loading">Check your internet connection or switch to a different network</string>
|
||||
<string name="transaction_history_tx_in_progress">In progress…</string>
|
||||
<string name="transaction_history_error_failed_to_load">Failed to load transactions</string>
|
||||
<string name="transaction_history_empty_transactions">You don\'t have any transactions yet</string>
|
||||
<string name="transaction_history_title">Transactions</string>
|
||||
|
||||
<!-- Special string -->
|
||||
<string name="common_custom_string">%s</string>
|
||||
|
|
|
|||
|
|
@ -449,6 +449,10 @@
|
|||
<string name="app_settings_enable_biometrics_description">Перейдите в настройки, чтобы включить биометрическую аутентификацию в приложении Tangem App</string>
|
||||
<string name="wallet_balance_missing_derivation">Отсканируйте карту</string>
|
||||
<string name="disclaimer_error_loading">Проверьте подключение с интернетом или переключитесь на другую сеть</string>
|
||||
<string name="transaction_history_tx_in_progress">В процессе…</string>
|
||||
<string name="transaction_history_error_failed_to_load">Не удалось загрузить транзакции</string>
|
||||
<string name="transaction_history_empty_transactions">У вас еще нет транзакций</string>
|
||||
<string name="transaction_history_title">Транзакции</string>
|
||||
|
||||
<!-- Special string -->
|
||||
<string name="common_custom_string">%s</string>
|
||||
|
|
|
|||
|
|
@ -450,6 +450,10 @@
|
|||
<string name="app_settings_enable_biometrics_description">Go to settings to enable biometric authentication in the Tangem App</string>
|
||||
<string name="wallet_balance_missing_derivation">Scan the card</string>
|
||||
<string name="disclaimer_error_loading">Check your internet connection or switch to a different network</string>
|
||||
<string name="transaction_history_tx_in_progress">In progress…</string>
|
||||
<string name="transaction_history_error_failed_to_load">Failed to load transactions</string>
|
||||
<string name="transaction_history_empty_transactions">You don\'t have any transactions yet</string>
|
||||
<string name="transaction_history_title">Transactions</string>
|
||||
|
||||
<!-- Special string -->
|
||||
<string name="common_custom_string">%s</string>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue