From 306a9bb0ec861ee126114af373378970ed7f3cf5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 15 Feb 2023 17:14:00 +0300 Subject: [PATCH 1/9] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- .../java/com/tangem/tap/TapApplication.kt | 8 +- .../configurable/config/ConfigManager.kt | 5 + .../domain/configurable/config/JsonModels.kt | 7 + .../products/wallet/saltPay/SaltPayConfig.kt | 2 + .../tap/features/wallet/redux/WalletState.kt | 2 +- .../redux/middlewares/WalletMiddleware.kt | 2 + .../tap/features/wallet/ui/WalletFragment.kt | 13 +- .../wallet/ui/wallet/SaltPayWalletView.kt | 88 ------- .../features/wallet/ui/wallet/WalletView.kt | 10 + .../wallet/saltPay/HistoryTransactionData.kt | 21 ++ .../ui/wallet/saltPay/SaltPayWalletView.kt | 218 ++++++++++++++++++ .../saltPay/rv/ShimmerRecyclerAdapter.kt | 33 +++ .../saltPay/rv/TransactionHistoryAdapter.kt | 103 +++++++++ .../utorg/UtorgExchangeService.kt | 1 - app/src/main/res/drawable/ic_accept_coin.xml | 57 +++++ app/src/main/res/drawable/ic_tx_explore.xml | 9 + app/src/main/res/drawable/ic_tx_incoming.xml | 14 ++ app/src/main/res/drawable/ic_tx_outgoing.xml | 14 ++ .../res/layout/item_salt_pay_tx_history.xml | 89 +++++++ .../layout/item_salt_pay_tx_history_date.xml | 9 + .../item_salt_pay_tx_history_shimmer.xml | 60 +++++ .../res/layout/layout_salt_pay_balance.xml | 21 +- .../res/layout/layout_salt_pay_tx_history.xml | 136 +++++++++++ .../res/layout/layout_salt_pay_wallet.xml | 10 + buildSrc/src/main/java/Versions.kt | 4 +- 26 files changed, 828 insertions(+), 110 deletions(-) delete mode 100644 app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPayWalletView.kt create mode 100644 app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/HistoryTransactionData.kt create mode 100644 app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt create mode 100644 app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/ShimmerRecyclerAdapter.kt create mode 100644 app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TransactionHistoryAdapter.kt create mode 100644 app/src/main/res/drawable/ic_accept_coin.xml create mode 100644 app/src/main/res/drawable/ic_tx_explore.xml create mode 100644 app/src/main/res/drawable/ic_tx_incoming.xml create mode 100644 app/src/main/res/drawable/ic_tx_outgoing.xml create mode 100644 app/src/main/res/layout/item_salt_pay_tx_history.xml create mode 100644 app/src/main/res/layout/item_salt_pay_tx_history_date.xml create mode 100644 app/src/main/res/layout/item_salt_pay_tx_history_shimmer.xml create mode 100644 app/src/main/res/layout/layout_salt_pay_tx_history.xml diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index 2b8aa19f58..f5f6ac9548 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit 2b8aa19f58e17a78015629943d49df0da0b4fd4b +Subproject commit f5f6ac954894ef66331db8fe383b9773feb69ef4 diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index 78cf3956f2..097ee1994f 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -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, diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt index 2b661de566..a45fc5c289 100644 --- a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt @@ -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, diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt index defa61e170..4de75aae58 100644 --- a/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt @@ -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?, diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayConfig.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayConfig.kt index 412087154d..9a0b4e1f64 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayConfig.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayConfig.kt @@ -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("", ""), ) } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt index 1b2a47181a..7e37e27a5a 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt @@ -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? diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index b49a33c1f1..f75992fe2b 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -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) } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index 85de69de54..31f63630c1 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -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 { + 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(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)) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/WalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/WalletView.kt index d7d7cbb405..6bcdb961e5 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/WalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/WalletView.kt @@ -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) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/HistoryTransactionData.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/HistoryTransactionData.kt new file mode 100644 index 0000000000..50b80f0b37 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/HistoryTransactionData.kt @@ -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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt new file mode 100644 index 0000000000..2a05484270 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt @@ -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(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) = 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) = with(txWidget) { + (rvTxHistory.adapter as TransactionHistoryAdapter).submitList(dataList) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/ShimmerRecyclerAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/ShimmerRecyclerAdapter.kt new file mode 100644 index 0000000000..fc59d49155 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/ShimmerRecyclerAdapter.kt @@ -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(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() { + 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 = "") \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TransactionHistoryAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TransactionHistoryAdapter.kt new file mode 100644 index 0000000000..bc5f923aff --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TransactionHistoryAdapter.kt @@ -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(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() { + 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) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgExchangeService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgExchangeService.kt index 21c8c48424..820751a14b 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgExchangeService.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgExchangeService.kt @@ -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 -> diff --git a/app/src/main/res/drawable/ic_accept_coin.xml b/app/src/main/res/drawable/ic_accept_coin.xml new file mode 100644 index 0000000000..6df78b4b77 --- /dev/null +++ b/app/src/main/res/drawable/ic_accept_coin.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_tx_explore.xml b/app/src/main/res/drawable/ic_tx_explore.xml new file mode 100644 index 0000000000..0c0c7403f5 --- /dev/null +++ b/app/src/main/res/drawable/ic_tx_explore.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_tx_incoming.xml b/app/src/main/res/drawable/ic_tx_incoming.xml new file mode 100644 index 0000000000..1f8d16c0ff --- /dev/null +++ b/app/src/main/res/drawable/ic_tx_incoming.xml @@ -0,0 +1,14 @@ + + + + diff --git a/app/src/main/res/drawable/ic_tx_outgoing.xml b/app/src/main/res/drawable/ic_tx_outgoing.xml new file mode 100644 index 0000000000..e6b391e594 --- /dev/null +++ b/app/src/main/res/drawable/ic_tx_outgoing.xml @@ -0,0 +1,14 @@ + + + + diff --git a/app/src/main/res/layout/item_salt_pay_tx_history.xml b/app/src/main/res/layout/item_salt_pay_tx_history.xml new file mode 100644 index 0000000000..bde605f95d --- /dev/null +++ b/app/src/main/res/layout/item_salt_pay_tx_history.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_salt_pay_tx_history_date.xml b/app/src/main/res/layout/item_salt_pay_tx_history_date.xml new file mode 100644 index 0000000000..8e45e44e0f --- /dev/null +++ b/app/src/main/res/layout/item_salt_pay_tx_history_date.xml @@ -0,0 +1,9 @@ + + diff --git a/app/src/main/res/layout/item_salt_pay_tx_history_shimmer.xml b/app/src/main/res/layout/item_salt_pay_tx_history_shimmer.xml new file mode 100644 index 0000000000..72aff47fa0 --- /dev/null +++ b/app/src/main/res/layout/item_salt_pay_tx_history_shimmer.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_salt_pay_balance.xml b/app/src/main/res/layout/layout_salt_pay_balance.xml index ff1772d79b..cdf2d00c6a 100644 --- a/app/src/main/res/layout/layout_salt_pay_balance.xml +++ b/app/src/main/res/layout/layout_salt_pay_balance.xml @@ -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 @@ @@ -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" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/layout_salt_pay_wallet.xml b/app/src/main/res/layout/layout_salt_pay_wallet.xml index c27f1b8c8a..4712e76937 100644 --- a/app/src/main/res/layout/layout_salt_pay_wallet.xml +++ b/app/src/main/res/layout/layout_salt_pay_wallet.xml @@ -19,4 +19,14 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt index 3ea750baf4..3fd7975b23 100644 --- a/buildSrc/src/main/java/Versions.kt +++ b/buildSrc/src/main/java/Versions.kt @@ -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 From 4c3d7613f00984e65cde01d55a9538fc52493fd0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 16 Feb 2023 13:02:01 +0300 Subject: [PATCH 2/9] Updated on 2026-08-14 --- .../features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt index 2a05484270..e1d9cbb642 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt @@ -143,8 +143,8 @@ class SaltPayWalletView : WalletView() { 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" + val walletAddress = wallet.address + // val walletAddress = "0xDA94Aae02a4Db0e09E1Cf240E3a0973ba89052cf" when (val result = walletManager.getTransactionHistory(walletAddress, wallet.blockchain, setOf(token))) { is Result.Success -> { val tokensHistory = result.data From 44b1c289c0117e712460174c662ab0d42615f661 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 16 Feb 2023 13:03:17 +0300 Subject: [PATCH 3/9] Updated on 2026-08-14 --- .../wallet/saltPay/GnosisRegistrator.kt | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt index ddf80bdc07..8a6403aa27 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt @@ -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 { 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 { + 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>.extractFeeAmount(): Result { return when (this) { is Result.Success -> { From 24edaba88f1cc66da0ed01bcdb79565fdd365e39 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 16 Feb 2023 16:36:59 +0300 Subject: [PATCH 4/9] Updated on 2026-08-14 --- .../redux/OnboardingSaltPayMiddleware.kt | 58 ++++++++++--------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt index dfdfb619b5..00575f7e6e 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt @@ -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,30 @@ 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 { + // mb in the next steps we decide to claim again, but now we going to ClaimSuccess step + dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.ClaimSuccess)) + } } } is OnboardingSaltPayAction.SetStep -> { From 26fe4e24b79def24b306644b53e7f437928a3f8d Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 Feb 2023 07:34:32 +0300 Subject: [PATCH 5/9] Updated on 2026-08-14 --- .../wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt index 00575f7e6e..5f39361096 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt @@ -252,8 +252,7 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App if (tokenAmountValue.isPositive() && amountToClaim == null) { dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.ClaimSuccess)) } else { - // mb in the next steps we decide to claim again, but now we going to ClaimSuccess step - dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.ClaimSuccess)) + // dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.ClaimSuccess)) } } } From d6b583e5fd957b97ecc1f9cee60484bfea08946f Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 Feb 2023 07:53:30 +0300 Subject: [PATCH 6/9] Updated on 2026-08-14 --- .../domain/extensions/WalletManagerFactory.kt | 23 ---- .../tap/domain/scanCard/ScanCardProcessor.kt | 9 +- .../redux/OnboardingWalletMiddleware.kt | 11 +- .../saltPay/SaltPayActivationManager.kt | 101 ++++++++++++++++++ .../saltPay/redux/OnboardingSaltPayAction.kt | 2 - .../saltPay/redux/OnboardingSaltPayReducer.kt | 1 - .../saltPay/redux/OnboardingSaltPayState.kt | 60 ----------- 7 files changed, 114 insertions(+), 93 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt index 989793e59a..a2801c5a8f 100644 --- a/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt +++ b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt @@ -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? { 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 } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt index 6caafef969..acf77e3bdb 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt @@ -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 { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index 939236339c..39b9347a99 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -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) } } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt index ff6ec003d6..1ae879918a 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt @@ -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) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayAction.kt index a2194e9568..73c1700fc6 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayAction.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayAction.kt @@ -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() diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayReducer.kt index 71570fd52e..e6ea4af732 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayReducer.kt @@ -21,7 +21,6 @@ private fun internalReduce(anyAction: Action, onboardingWalletState: OnboardingW return onboardingWalletState.copy( onboardingSaltPayState = OnboardingSaltPayState( saltPayManager = action.registrationManager, - saltPayConfig = action.saltPayConfig, ), ) } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayState.kt index a14ad10252..9bedb49011 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayState.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayState.kt @@ -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 { - 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 { From 28d40bd7e7eb9a40f3cec5b78e2fd9af1639ac80 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 Feb 2023 10:22:48 +0300 Subject: [PATCH 7/9] Updated on 2026-08-14 --- app/src/main/res/layout/item_salt_pay_tx_history.xml | 4 ++-- app/src/main/res/layout/layout_salt_pay_tx_history.xml | 6 +++--- core/res/src/main/res/values-de/strings.xml | 4 ++++ core/res/src/main/res/values-fr/strings.xml | 4 ++++ core/res/src/main/res/values-it/strings.xml | 4 ++++ core/res/src/main/res/values-ru/strings.xml | 4 ++++ core/res/src/main/res/values/strings.xml | 4 ++++ 7 files changed, 25 insertions(+), 5 deletions(-) diff --git a/app/src/main/res/layout/item_salt_pay_tx_history.xml b/app/src/main/res/layout/item_salt_pay_tx_history.xml index bde605f95d..5e41bb38d1 100644 --- a/app/src/main/res/layout/item_salt_pay_tx_history.xml +++ b/app/src/main/res/layout/item_salt_pay_tx_history.xml @@ -53,13 +53,13 @@ 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" - tools:text="In progress..." /> + app:layout_constraintStart_toStartOf="@+id/tv_tx_hash" /> Go to settings to enable biometric authentication in the Tangem App Scan the card Check your internet connection or switch to a different network + In progress… + Failed to load transactions + You don\'t have any transactions yet + Transactions %s diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index c243f24360..8080d5b398 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -449,6 +449,10 @@ Go to settings to enable biometric authentication in the Tangem App Scan the card Check your internet connection or switch to a different network + In progress… + Failed to load transactions + You don\'t have any transactions yet + Transactions %s diff --git a/core/res/src/main/res/values-it/strings.xml b/core/res/src/main/res/values-it/strings.xml index d643c5f258..1fb5d0c132 100644 --- a/core/res/src/main/res/values-it/strings.xml +++ b/core/res/src/main/res/values-it/strings.xml @@ -449,6 +449,10 @@ Go to settings to enable biometric authentication in the Tangem App Scan the card Check your internet connection or switch to a different network + In progress… + Failed to load transactions + You don\'t have any transactions yet + Transactions %s diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 62a7e8b559..d94ea6208e 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -449,6 +449,10 @@ Перейдите в настройки, чтобы включить биометрическую аутентификацию в приложении Tangem App Отсканируйте карту Проверьте подключение с интернетом или переключитесь на другую сеть + В процессе… + Не удалось загрузить транзакции + У вас еще нет транзакций + Транзакции %s diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index db08b93d2b..483c6f6caf 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -450,6 +450,10 @@ Go to settings to enable biometric authentication in the Tangem App Scan the card Check your internet connection or switch to a different network + In progress… + Failed to load transactions + You don\'t have any transactions yet + Transactions %s From e2ddd446d291177b2d5b9866b805e7885ea3fdb4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 Feb 2023 10:23:15 +0300 Subject: [PATCH 8/9] Updated on 2026-08-14 --- .../layout/item_salt_pay_tx_history_date.xml | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/app/src/main/res/layout/item_salt_pay_tx_history_date.xml b/app/src/main/res/layout/item_salt_pay_tx_history_date.xml index 8e45e44e0f..a21c2dbd6e 100644 --- a/app/src/main/res/layout/item_salt_pay_tx_history_date.xml +++ b/app/src/main/res/layout/item_salt_pay_tx_history_date.xml @@ -1,9 +1,20 @@ - + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + + + From 73c1f6988d735b00dd328dd9bb02566f987e3e96 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 Feb 2023 10:25:07 +0300 Subject: [PATCH 9/9] Updated on 2026-08-14 --- .../rv => common}/ShimmerRecyclerAdapter.kt | 2 +- .../wallet/saltPay/HistoryTransactionData.kt | 21 ---- .../ui/wallet/saltPay/SaltPayWalletView.kt | 39 ++++-- .../ui/wallet/saltPay/rv/HistoryItemData.kt | 74 ++++++++++++ .../saltPay/rv/TransactionHistoryAdapter.kt | 103 ---------------- .../ui/wallet/saltPay/rv/TxHistoryAdapter.kt | 112 ++++++++++++++++++ 6 files changed, 214 insertions(+), 137 deletions(-) rename app/src/main/java/com/tangem/tap/{features/wallet/ui/wallet/saltPay/rv => common}/ShimmerRecyclerAdapter.kt (95%) delete mode 100644 app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/HistoryTransactionData.kt create mode 100644 app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/HistoryItemData.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TransactionHistoryAdapter.kt create mode 100644 app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TxHistoryAdapter.kt diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/ShimmerRecyclerAdapter.kt b/app/src/main/java/com/tangem/tap/common/ShimmerRecyclerAdapter.kt similarity index 95% rename from app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/ShimmerRecyclerAdapter.kt rename to app/src/main/java/com/tangem/tap/common/ShimmerRecyclerAdapter.kt index fc59d49155..4d56172588 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/ShimmerRecyclerAdapter.kt +++ b/app/src/main/java/com/tangem/tap/common/ShimmerRecyclerAdapter.kt @@ -1,4 +1,4 @@ -package com.tangem.tap.features.wallet.ui.wallet.saltPay.rv +package com.tangem.tap.common import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/HistoryTransactionData.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/HistoryTransactionData.kt deleted file mode 100644 index 50b80f0b37..0000000000 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/HistoryTransactionData.kt +++ /dev/null @@ -1,21 +0,0 @@ -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 - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt index e1d9cbb642..62dccf8f6d 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt @@ -6,6 +6,8 @@ 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 @@ -18,9 +20,9 @@ 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.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 @@ -85,7 +87,7 @@ class SaltPayWalletView : WalletView() { txWidget.rvTxHistoryShimmer.adapter = adapter txWidget.rvTxHistoryShimmer.addItemDecoration(SpaceItemDecoration.vertical(10F)) - txWidget.rvTxHistory.adapter = TransactionHistoryAdapter() + txWidget.rvTxHistory.adapter = TxHistoryAdapter() txWidget.rvTxHistory.addItemDecoration(SpaceItemDecoration.vertical(10F)) handleTxInit() @@ -147,18 +149,31 @@ class SaltPayWalletView : WalletView() { // val walletAddress = "0xDA94Aae02a4Db0e09E1Cf240E3a0973ba89052cf" when (val result = walletManager.getTransactionHistory(walletAddress, wallet.blockchain, setOf(token))) { is Result.Success -> { - val tokensHistory = result.data + val dateAssociatedHistory = mutableMapOf>() + + result.data .filter { it.contractAddress == token.contractAddress } - .sortedBy { it.date?.timeInMillis ?: 0 } - .map { HistoryTransactionData(it, walletAddress) } + .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() + dateAssociatedHistory.forEach { entry -> + dataList.add(HistoryItemData.Date(entry.key)) + entry.value.forEach { dataList.add(HistoryItemData.TransactionData(it)) } + } // .toMutableList().apply { clear() } delay(300) withMainContext { - if (tokensHistory.isEmpty()) { + if (dataList.isEmpty()) { handleTxEmpty() } else { - handleTxSuccess(tokensHistory) + handleTxSuccess(dataList) } } } @@ -185,7 +200,7 @@ class SaltPayWalletView : WalletView() { txWidget.groupShimmer.show() } - private fun handleTxSuccess(dataList: List) = with(txWidget) { + private fun handleTxSuccess(dataList: List) = with(txWidget) { groupShimmer.hide() groupEmpty.hide() groupError.hide() @@ -212,7 +227,7 @@ class SaltPayWalletView : WalletView() { groupError.show() } - private fun updateTxHistoryWidget(dataList: List) = with(txWidget) { - (rvTxHistory.adapter as TransactionHistoryAdapter).submitList(dataList) + private fun updateTxHistoryWidget(dataList: List) = with(txWidget) { + (rvTxHistory.adapter as TxHistoryAdapter).submitList(dataList) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/HistoryItemData.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/HistoryItemData.kt new file mode 100644 index 0000000000..0aa75abfba --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/HistoryItemData.kt @@ -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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TransactionHistoryAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TransactionHistoryAdapter.kt deleted file mode 100644 index bc5f923aff..0000000000 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TransactionHistoryAdapter.kt +++ /dev/null @@ -1,103 +0,0 @@ -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(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() { - 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) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TxHistoryAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TxHistoryAdapter.kt new file mode 100644 index 0000000000..60e9d1dfd7 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TxHistoryAdapter.kt @@ -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(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() { + 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 + } +} \ No newline at end of file