Updated on 2026-08-14

This commit is contained in:
Tangem 2023-01-17 12:32:55 +03:00
commit ad443ca39a
12 changed files with 89 additions and 109 deletions

View file

@ -10,7 +10,6 @@ import androidx.core.view.WindowInsetsControllerCompat
import by.kirich1409.viewbindingdelegate.viewBinding
import com.google.android.material.snackbar.Snackbar
import com.tangem.TangemSdk
import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler
import com.tangem.operations.backup.BackupService
import com.tangem.tangem_sdk_new.extensions.init
import com.tangem.tap.common.ActivityResultCallbackHolder
@ -27,9 +26,11 @@ import com.tangem.tap.common.shop.googlepay.GooglePayUtil.createPaymentsClient
import com.tangem.tap.domain.TangemSdkManager
import com.tangem.tap.domain.userWalletList.UserWalletsListManager
import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
import com.tangem.tap.features.shop.redux.ShopAction
import com.tangem.tap.features.welcome.redux.WelcomeAction
import com.tangem.tap.proxy.AppStateHolder
import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler
import com.tangem.wallet.R
import com.tangem.wallet.databinding.ActivityMainBinding
import dagger.hilt.android.AndroidEntryPoint
@ -127,6 +128,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Home))
intentHandler.handleWalletConnectLink(intent)
}
store.dispatch(BackupAction.CheckForUnfinishedBackup)
}
intentHandler.handleBackgroundScan(intent)
intentHandler.handleSellCurrencyCallback(intent)

View file

@ -2,33 +2,23 @@ package com.tangem.tap.domain.totalBalance
import com.tangem.tap.domain.model.TotalFiatBalance
import com.tangem.tap.domain.model.WalletStoreModel
import java.math.BigDecimal
interface TotalFiatBalanceCalculator {
/**
* Calculate total fiat balance for list of [WalletStoreModel]
* @param prevAmount Previous amount, used in [TotalFiatBalance.Refreshing]
* @param walletStores List of [WalletStoreModel] to calculate fiat amount
* @param initial Initial [TotalFiatBalance] state, used when list of [WalletStoreModel] is empty
* @return [TotalFiatBalance] with state found with the [WalletStoreModel] list
* */
suspend fun calculate(
prevAmount: BigDecimal?,
walletStores: List<WalletStoreModel>,
initial: TotalFiatBalance,
): TotalFiatBalance
suspend fun calculate(walletStores: List<WalletStoreModel>, initial: TotalFiatBalance): TotalFiatBalance
/**
* Same as [TotalFiatBalanceCalculator.calculate] but returns null if list of [WalletStoreModel] is empty
* @param prevAmount Previous amount, used in [TotalFiatBalance.Refreshing]
* @param walletStores List of [WalletStoreModel] to calculate fiat amount
* @return [TotalFiatBalance] with state found with the [WalletStoreModel] list
* */
suspend fun calculateOrNull(
prevAmount: BigDecimal?,
walletStores: List<WalletStoreModel>,
): TotalFiatBalance?
suspend fun calculateOrNull(walletStores: List<WalletStoreModel>): TotalFiatBalance?
companion object
}

View file

@ -10,18 +10,11 @@ import kotlinx.coroutines.withContext
import java.math.BigDecimal
internal class DefaultTotalFiatBalanceCalculator : TotalFiatBalanceCalculator {
override suspend fun calculate(
prevAmount: BigDecimal?,
walletStores: List<WalletStoreModel>,
initial: TotalFiatBalance,
): TotalFiatBalance {
return calculateOrNull(prevAmount, walletStores) ?: initial
override suspend fun calculate(walletStores: List<WalletStoreModel>, initial: TotalFiatBalance): TotalFiatBalance {
return calculateOrNull(walletStores) ?: initial
}
override suspend fun calculateOrNull(
prevAmount: BigDecimal?,
walletStores: List<WalletStoreModel>,
): TotalFiatBalance? {
override suspend fun calculateOrNull(walletStores: List<WalletStoreModel>): TotalFiatBalance? {
return if (walletStores.isEmpty()) {
null
} else {

View file

@ -128,34 +128,35 @@ internal class DefaultWalletCurrenciesManager(
private fun List<Currency>.addMissingBlockchains(card: CardDTO): List<Currency> {
val newCurrencies = arrayListOf<Currency>()
for (currency in this.sortedByDescending { it is Currency.Blockchain }) {
when (currency) {
is Currency.Blockchain -> {
newCurrencies.add(currency.updateDerivationPath(card.derivationStyle))
this
.groupBy { it.blockchain }
.forEach { (blockchain, currencies) ->
val rawDerivationPath: String?
val blockchainCurrency = currencies
.firstOrNull { it is Currency.Blockchain }
as? Currency.Blockchain
// Add blockchain currency
if (blockchainCurrency != null) {
rawDerivationPath = findDerivationPath(blockchainCurrency, card.derivationStyle)
newCurrencies.add(blockchainCurrency.copy(derivationPath = rawDerivationPath))
} else {
rawDerivationPath = findDerivationPath(currencies.first(), card.derivationStyle)
newCurrencies.add(
Currency.Blockchain(
blockchain = blockchain,
derivationPath = rawDerivationPath,
),
)
}
is Currency.Token -> {
val containsTokenBlockchain = newCurrencies.any {
it.isBlockchain() && it.blockchain == currency.blockchain
// Add tokens currencies
currencies
.filterIsInstance<Currency.Token>()
.forEach { currency ->
newCurrencies.add(currency.copy(derivationPath = rawDerivationPath))
}
if (containsTokenBlockchain) {
newCurrencies.add(currency.updateDerivationPath(card.derivationStyle))
} else {
val derivationPath = findDerivationPath(currency, card.derivationStyle)
newCurrencies.add(
Currency.Blockchain(
blockchain = currency.blockchain,
derivationPath = derivationPath,
),
)
newCurrencies.add(
currency.copy(derivationPath = derivationPath),
)
}
}
}
}
return newCurrencies
}
@ -183,22 +184,6 @@ internal class DefaultWalletCurrenciesManager(
.fold()
}
private fun Currency.updateDerivationPath(cardDerivationStyle: DerivationStyle?): Currency {
val findDerivationPath: () -> String? = {
findDerivationPath(this, cardDerivationStyle)
}
return when (this) {
is Currency.Blockchain -> this.copy(
derivationPath = findDerivationPath(),
)
is Currency.Token -> this.copy(
derivationPath = findDerivationPath(),
)
}
}
private fun findDerivationPath(currency: Currency, cardDerivationStyle: DerivationStyle?): String? {
return currency.derivationPath ?: currency.blockchain.derivationPath(cardDerivationStyle)?.rawPath
}

View file

@ -53,16 +53,16 @@ class Start2CoinDisclaimer(dataProvider: DisclaimerDataProvider) : BaseDisclaime
private fun filename(languageCode: String, regionCode: String?): String {
return when {
languageCode == "fr" && regionCode == "ch" -> "Start2Coin-fr-ch-tangem.html"
languageCode == "de" && regionCode == "ch" -> "Start2Coin-de-ch-tangem.html"
languageCode == "en" && regionCode == "ch" -> "Start2Coin-en-ch-tangem.html"
languageCode == "it" && regionCode == "ch" -> "Start2Coin-it-ch-tangem.html"
languageCode == "fr" && regionCode == "fr" -> "Start2Coin-fr-fr-atangem.html"
languageCode == "de" && regionCode == "at" -> "Start2Coin-de-at-tangem.html"
regionCode == "fr" -> "Start2Coin-fr-fr-atangem.html"
regionCode == "ch" -> "Start2Coin-en-ch-tangem.html"
regionCode == "at" -> "Start2Coin-de-at-tangem.html"
else -> "Start2Coin-fr-fr-atangem.html"
languageCode == "fr" && regionCode == "ch" -> "start2coin-fr-ch-tangem.html"
languageCode == "de" && regionCode == "ch" -> "start2coin-de-ch-tangem.html"
languageCode == "en" && regionCode == "ch" -> "start2coin-en-ch-tangem.html"
languageCode == "it" && regionCode == "ch" -> "start2coin-it-ch-tangem.html"
languageCode == "fr" && regionCode == "fr" -> "start2coin-fr-fr-tangem.html"
languageCode == "de" && regionCode == "at" -> "start2coin-de-at-tangem.html"
regionCode == "fr" -> "start2coin-fr-fr-tangem.html"
regionCode == "ch" -> "start2coin-en-ch-tangem.html"
regionCode == "at" -> "start2coin-de-at-tangem.html"
else -> "start2coin-fr-fr-tangem.html"
}
}

View file

@ -19,7 +19,6 @@ import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.features.home.compose.StoriesScreen
import com.tangem.tap.features.home.redux.HomeAction
import com.tangem.tap.features.home.redux.HomeState
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
import com.tangem.tap.features.tokens.redux.TokensAction
import com.tangem.tap.store
import org.rekotlin.StoreSubscriber
@ -39,8 +38,6 @@ class HomeFragment : Fragment(), StoreSubscriber<HomeState> {
container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
store.dispatch(BackupAction.CheckForUnfinishedBackup)
return ComposeView(inflater.context).apply {
setContent {
BackHandler {

View file

@ -117,5 +117,5 @@ private fun readCard() = scope.launch {
}
private fun changeButtonState(state: ButtonState) {
store.dispatch(HomeAction.ChangeScanCardButtonState(IndeterminateProgressButton(state)))
store.dispatchOnMain(HomeAction.ChangeScanCardButtonState(IndeterminateProgressButton(state)))
}

View file

@ -1,5 +1,6 @@
package com.tangem.tap.features.wallet.redux.middlewares
import com.tangem.blockchain.common.AmountType
import com.tangem.common.extensions.isZero
import com.tangem.tap.common.extensions.stripZeroPlainString
import com.tangem.tap.common.extensions.toFiatRateString
@ -21,6 +22,7 @@ import com.tangem.tap.features.wallet.ui.BalanceStatus
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
import com.tangem.tap.features.wallet.ui.TokenData
import com.tangem.tap.store
import java.math.BigDecimal
internal fun List<WalletStoreModel>.mapToReduxModels(
isMultiWalletAllowed: Boolean,
@ -69,6 +71,7 @@ private fun List<WalletDataModel>.mapToReduxModel(
?.takeIf { !status.isErrorStatus }
?.toFormattedFiatValue(appCurrency.symbol)
val fiatRateFormatted = fiatRate?.toFiatRateString(appCurrency.symbol)
val blockchainAmountValue = getBlockchainAmount()
WalletData(
currency = currency,
@ -83,7 +86,9 @@ private fun List<WalletDataModel>.mapToReduxModel(
fiatRateString = fiatRateFormatted,
pendingTransactions = status.pendingTransactions,
mainButton = WalletMainButton.SendButton(
enabled = !status.amount.isZero() && status.pendingTransactions.isEmpty(),
enabled = !blockchainAmountValue.isZero()
&& !status.amount.isZero()
&& status.pendingTransactions.isEmpty(),
),
walletRent = walletRent?.let {
WalletRent(
@ -103,7 +108,7 @@ private fun List<WalletDataModel>.mapToReduxModel(
},
currency = currency.currencyName,
currencySymbol = currency.currencySymbol,
blockchainAmount = status.amount,
blockchainAmount = blockchainAmountValue,
amount = amount,
amountFormatted = amountFormatted,
fiatAmount = fiatAmount,
@ -130,4 +135,9 @@ private fun List<WalletDataModel>.mapToReduxModel(
)
}
}
}
private fun WalletDataModel.getBlockchainAmount(): BigDecimal {
val walletStore = store.state.walletState.getWalletStore(currency) ?: return BigDecimal.ZERO
return walletStore.walletManager?.wallet?.amounts?.get(AmountType.Coin)?.value ?: BigDecimal.ZERO
}

View file

@ -8,10 +8,10 @@ import com.tangem.blockchain.common.WalletManager
import com.tangem.common.CompletionResult
import com.tangem.common.extensions.isZero
import com.tangem.common.services.Result
import com.tangem.core.analytics.Analytics
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.operations.attestation.Attestation
import com.tangem.operations.attestation.OnlineCardVerifier
import com.tangem.core.analytics.Analytics
import com.tangem.tap.common.analytics.events.MainScreen
import com.tangem.tap.common.analytics.events.Token
import com.tangem.tap.common.extensions.copyToClipboard
@ -287,7 +287,7 @@ class WalletMiddleware {
is WalletAction.UserWalletChanged -> Unit
is WalletAction.WalletStoresChanged -> {
updateWalletStores(action.walletStores, walletState)
fetchTotalFiatBalance(action.walletStores, walletState)
fetchTotalFiatBalance(action.walletStores)
findMissedDerivations(action.walletStores)
tryToShowAppRatingWarning(action.walletStores)
}
@ -312,12 +312,9 @@ class WalletMiddleware {
}
}
private fun fetchTotalFiatBalance(walletStores: List<WalletStoreModel>, state: WalletState) {
private fun fetchTotalFiatBalance(walletStores: List<WalletStoreModel>) {
scope.launch(Dispatchers.Default) {
val totalFiatBalance = totalFiatBalanceCalculator.calculateOrNull(
prevAmount = state.totalBalance?.fiatAmount,
walletStores = walletStores,
)?.mapToReduxModel()
val totalFiatBalance = totalFiatBalanceCalculator.calculateOrNull(walletStores)?.mapToReduxModel()
if (totalFiatBalance != null) {
store.dispatchOnMain(WalletAction.TotalFiatBalanceChanged(totalFiatBalance))

View file

@ -18,15 +18,16 @@ import androidx.transition.TransitionInflater
import by.kirich1409.viewbindingdelegate.viewBinding
import com.badoo.mvicore.modelWatcher
import com.tangem.common.doOnResult
import com.tangem.core.analytics.Analytics
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.tangem_sdk_new.extensions.dpToPx
import com.tangem.tap.common.SnackbarHandler
import com.tangem.tap.common.TestActions
import com.tangem.core.analytics.Analytics
import com.tangem.tap.common.analytics.events.DetailsScreen
import com.tangem.tap.common.analytics.events.Token
import com.tangem.tap.common.extensions.appendIfNotNull
import com.tangem.tap.common.extensions.beginDelayedTransition
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.fitChipsByGroupWidth
import com.tangem.tap.common.extensions.getColor
import com.tangem.tap.common.extensions.getString
@ -230,8 +231,8 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
tokens = emptyList(),
)
store.dispatch(WalletAction.LoadWallet(blockchainNetwork))
store.dispatch(WalletAction.LoadFiatRate(coinsList = listOf(currency)))
store.dispatchOnMain(WalletAction.LoadWallet(blockchainNetwork))
store.dispatchOnMain(WalletAction.LoadFiatRate(coinsList = listOf(currency)))
}
}
}

View file

@ -5,9 +5,9 @@ import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
import com.badoo.mvicore.DiffStrategy
import com.badoo.mvicore.modelWatcher
import com.tangem.core.analytics.Analytics
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.common.TapWorkarounds.isTestCard
import com.tangem.core.analytics.Analytics
import com.tangem.tap.common.analytics.events.MainScreen
import com.tangem.tap.common.analytics.events.Portfolio
import com.tangem.tap.common.extensions.animateVisibility
@ -69,7 +69,12 @@ class MultiWalletView : WalletView() {
}
watch({ it }, totalBalanceStrategy) { walletState ->
binding?.let {
handleTotalBalance(it, walletState.totalBalance, walletState.state, walletState.walletsDataFromStores.size)
handleTotalBalance(
binding = it,
totalBalance = walletState.totalBalance,
progressState = walletState.state,
walletsCount = walletState.walletsDataFromStores.size,
)
}
}
}
@ -175,9 +180,7 @@ class MultiWalletView : WalletView() {
walletsCount: Int,
) = with(binding.lCardTotalBalance) {
if (walletsCount == 0) {
if (progressState != ProgressState.Loading) {
root.isVisible = false
}
root.isVisible = false
} else {
if (totalBalance == null) {
if (progressState != ProgressState.Loading) {

View file

@ -6,9 +6,9 @@ import com.tangem.common.doOnFailure
import com.tangem.common.doOnSuccess
import com.tangem.common.flatMap
import com.tangem.common.map
import com.tangem.core.analytics.Analytics
import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.util.UserWalletId
import com.tangem.core.analytics.Analytics
import com.tangem.tap.common.analytics.events.MyWallets
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.onUserWalletSelected
@ -98,21 +98,25 @@ internal class WalletSelectorMiddleware {
}
}
private fun updateBalances(walletStores: Map<UserWalletId, List<WalletStoreModel>>, state: WalletSelectorState) {
walletStores.forEach { (walletId, walletStores) ->
scope.launch(Dispatchers.Default) {
val foundWallet = state.wallets
.find { it.id == walletId }
private fun updateBalances(
updatedWalletStores: Map<UserWalletId, List<WalletStoreModel>>,
state: WalletSelectorState,
) {
if (updatedWalletStores.isNotEmpty()) scope.launch(Dispatchers.Default) {
state.wallets
.associateWith { updatedWalletStores[it.id] }
.forEach { (wallet, walletStores) ->
val isWalletTokensEmpty = (wallet.type as? UserWalletModel.Type.MultiCurrency)?.tokensCount == 0
val updatedWallet = if (walletStores == null && isWalletTokensEmpty) {
wallet.copy(fiatBalance = TotalFiatBalance.Loaded(BigDecimal.ZERO))
} else {
wallet.updateWalletStoresAndCalculateFiatBalance(walletStores.orEmpty())
}
if (foundWallet != null) {
val updatedWallet = foundWallet
.updateWalletStoresAndCalculateFiatBalance(walletStores)
if (foundWallet != updatedWallet) {
if (wallet != updatedWallet) {
store.dispatchOnMain(WalletSelectorAction.BalanceLoaded(updatedWallet))
}
}
}
}
}
@ -287,7 +291,6 @@ internal class WalletSelectorMiddleware {
selectedUserWallet == null -> {
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Welcome))
}
currentSelectedWalletId != selectedUserWallet.walletId -> {
store.onUserWalletSelected(selectedUserWallet)
}
@ -320,9 +323,8 @@ internal class WalletSelectorMiddleware {
is UserWalletModel.Type.SingleCurrency -> type
},
fiatBalance = totalFiatBalanceCalculator.calculate(
prevAmount = fiatBalance.amount,
walletStores = walletStores,
initial = TotalFiatBalance.Loaded(BigDecimal.ZERO),
initial = TotalFiatBalance.Loading,
),
)
}