diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index 69289418b4..49d98a51dd 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -693,6 +693,32 @@ internal class StateBuilder( ) } + fun updateSendCurrencyBalance( + uiState: SwapStateHolder, + cryptoCurrencyStatus: CryptoCurrencyStatus, + ): SwapStateHolder { + if (uiState.sendCardData !is SwapCardState.SwapCardData) return uiState + + return uiState.copy( + sendCardData = uiState.sendCardData.copy( + balance = cryptoCurrencyStatus.getFormattedAmount(isNeedSymbol = false), + ), + ) + } + + fun updateReceiveCurrencyBalance( + uiState: SwapStateHolder, + cryptoCurrencyStatus: CryptoCurrencyStatus, + ): SwapStateHolder { + if (uiState.receiveCardData !is SwapCardState.SwapCardData) return uiState + + return uiState.copy( + receiveCardData = uiState.receiveCardData.copy( + balance = cryptoCurrencyStatus.getFormattedAmount(isNeedSymbol = false), + ), + ) + } + fun updateBalanceHiddenState(uiState: SwapStateHolder, isBalanceHidden: Boolean): SwapStateHolder { if (uiState.sendCardData !is SwapCardState.SwapCardData) return uiState if (uiState.receiveCardData !is SwapCardState.SwapCardData) return uiState diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt index d194494046..be2e616805 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.* +import arrow.core.Either import arrow.core.getOrElse import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsParam @@ -13,11 +14,13 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.tokens.GetCryptoCurrencyStatusSyncUseCase +import com.tangem.domain.tokens.GetCurrencyStatusUpdatesUseCase import com.tangem.domain.tokens.UpdateDelayedNetworkStatusUseCase import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.Network import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.swap.analytics.SwapEvents import com.tangem.feature.swap.domain.BlockchainInteractor import com.tangem.feature.swap.domain.SwapInteractor @@ -62,6 +65,7 @@ internal class SwapViewModel @Inject constructor( private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val getCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusSyncUseCase, private val updateDelayedCurrencyStatusUseCase: UpdateDelayedNetworkStatusUseCase, + private val getCurrencyStatusUpdatesUseCase: GetCurrencyStatusUpdatesUseCase, savedStateHandle: SavedStateHandle, ) : ViewModel(), DefaultLifecycleObserver { @@ -104,6 +108,9 @@ internal class SwapViewModel @Inject constructor( (it.error is DataError.ExchangeTooSmallAmountError || it.error is DataError.ExchangeTooBigAmountError) } + private val fromTokenBalanceJobHolder = JobHolder() + private val toTokenBalanceJobHolder = JobHolder() + val currentScreen: SwapNavScreen get() = swapRouter.currentScreen @@ -173,6 +180,23 @@ internal class SwapViewModel @Inject constructor( state, ), ) + + val userWalletId = swapInteractor.getSelectedWallet()?.walletId ?: return@launch + (dataState.fromCryptoCurrency?.currency as? CryptoCurrency.Coin)?.let { + subscribeToCoinBalanceUpdates( + userWalletId = userWalletId, + coin = it, + isFromCurrency = true, + ) + } + + (dataState.toCryptoCurrency?.currency as? CryptoCurrency.Coin)?.let { + subscribeToCoinBalanceUpdates( + userWalletId = userWalletId, + coin = it, + isFromCurrency = false, + ) + } }.onFailure { Timber.tag(loggingTag).e(it) @@ -617,7 +641,6 @@ internal class SwapViewModel @Inject constructor( unavailable = unavailable, afterSearch = true, ), - ) } else { tokenDataState.copy( @@ -647,15 +670,35 @@ internal class SwapViewModel @Inject constructor( analyticsEventHandler.send(SwapEvents.ChooseTokenScreenResult(tokenChosen = true, token = it)) } + val userWalletId = swapInteractor.getSelectedWallet()?.walletId + if (foundToken != null) { val fromToken: CryptoCurrencyStatus val toToken: CryptoCurrencyStatus if (isOrderReversed) { fromToken = foundToken.currencyStatus toToken = initialCryptoCurrencyStatus + + val newToken = fromToken.currency as? CryptoCurrency.Coin + if (userWalletId != null && newToken != null) { + subscribeToCoinBalanceUpdates( + userWalletId = userWalletId, + coin = newToken, + isFromCurrency = true, + ) + } } else { fromToken = initialCryptoCurrencyStatus toToken = foundToken.currencyStatus + + val newToken = toToken.currency as? CryptoCurrency.Coin + if (userWalletId != null && newToken != null) { + subscribeToCoinBalanceUpdates( + userWalletId = userWalletId, + coin = newToken, + isFromCurrency = false, + ) + } } dataState = dataState.copy( fromCryptoCurrency = fromToken, @@ -673,6 +716,36 @@ internal class SwapViewModel @Inject constructor( } } + private fun subscribeToCoinBalanceUpdates( + userWalletId: UserWalletId, + coin: CryptoCurrency.Coin, + isFromCurrency: Boolean, + ) { + Timber.d("Subscribe to ${coin.id} balance updates") + + getCurrencyStatusUpdatesUseCase( + userWalletId = userWalletId, + currencyId = coin.id, + isSingleWalletWithTokens = false, + ) + .mapNotNull { (it as? Either.Right)?.value } + .distinctUntilChanged { old, new -> old.value.amount == new.value.amount } // Check only balance changes + .onEach { + Timber.d("${coin.id} balance is ${it.value.amount}") + + uiState = if (isFromCurrency) { + dataState = dataState.copy(fromCryptoCurrency = it) + stateBuilder.updateSendCurrencyBalance(uiState, it) + } else { + dataState = dataState.copy(toCryptoCurrency = it) + stateBuilder.updateReceiveCurrencyBalance(uiState, it) + } + } + .flowOn(dispatchers.main) + .launchIn(viewModelScope) + .saveIn(if (isFromCurrency) fromTokenBalanceJobHolder else toTokenBalanceJobHolder) + } + private fun onChangeCardsClicked() { val newFromToken = dataState.toCryptoCurrency val newToToken = dataState.fromCryptoCurrency