diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/express/TangemExpressApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/express/TangemExpressApi.kt index b56262221a..b5ef12459c 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/express/TangemExpressApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/express/TangemExpressApi.kt @@ -49,6 +49,6 @@ interface TangemExpressApi { @Query("toAddress") toAddress: String, ): ApiResponse - @GET("exchange-result") - suspend fun getExchangeResults(@Query("txId") txId: String): ApiResponse + @GET("exchange-status") + suspend fun getExchangeStatus(@Query("txId") txId: String): ApiResponse } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeResultsResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeResultsResponse.kt deleted file mode 100644 index c9b10f881e..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeResultsResponse.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.tangem.datasource.api.express.models.response - -import com.squareup.moshi.Json - -data class ExchangeResultsResponse( - @Json(name = "status") - val status: ExchangeResultsStatus, - - @Json(name = "externalStatus") - val externalStatus: String, - - @Json(name = "externalTxUrl") - val externalTxUrl: String, - - @Json(name = "error") - val error: ExchangeResultsError?, -) - -enum class ExchangeResultsStatus { - @Json(name = "processing") - PROCESSING, - - @Json(name = "done") - DONE, - - @Json(name = "failed") - FAILED, - - @Json(name = "refunded") - REFUNDED, - - @Json(name = "verificationRequired") - VERIFICATION_REQUIRED, -} - -data class ExchangeResultsError( - @Json(name = "code") - val code: Int, - - @Json(name = "description") - val description: String, -) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeStatusResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeStatusResponse.kt new file mode 100644 index 0000000000..94cd9cbf1f --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeStatusResponse.kt @@ -0,0 +1,59 @@ +package com.tangem.datasource.api.express.models.response + +import com.squareup.moshi.Json + +data class ExchangeStatusResponse( + + @Json(name = "providerId") + val providerId: String, + + @Json(name = "externalTxId") + val externalTxId: String, + + @Json(name = "externalTxStatus") + val externalStatus: ExchangeStatus, + + @Json(name = "externalTxUrl") + val externalTxUrl: String, + + @Json(name = "error") + val error: ExchangeStatusError?, +) + +enum class ExchangeStatus { + + @Json(name = "new") + NEW, + + @Json(name = "waiting") + WAITING, + + @Json(name = "confirming") + CONFIRMING, + + @Json(name = "exchanging") + EXCHANGING, + + @Json(name = "sending") + SENDING, + + @Json(name = "finished") + FINISHED, + + @Json(name = "failed") + FAILED, + + @Json(name = "refunded") + REFUNDED, + + @Json(name = "verifying") + VERIFYING, +} + +data class ExchangeStatusError( + @Json(name = "code") + val code: Int, + + @Json(name = "description") + val description: String, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt index d81065c147..7f43d0a7e8 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt @@ -32,6 +32,8 @@ object PreferencesKeys { val SELECTED_APP_CURRENCY_KEY by lazy { stringPreferencesKey(name = "selectedAppCurrency") } val BALANCE_HIDING_SETTINGS_KEY by lazy { stringPreferencesKey(name = "balanceHidingSettings") } + + val SWAP_TRANSACTIONS_KEY by lazy { stringPreferencesKey(name = "swapTransactions") } } /** Preferences keys set that should be migrated from "PreferencesDataSource" to a new DataStore */ diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt index da053a53f7..afb57565f8 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt @@ -3,14 +3,13 @@ package com.tangem.core.ui.components.appbar import androidx.annotation.DrawableRes import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.* import androidx.compose.material.Icon import androidx.compose.material.Text +import androidx.compose.material.ripple.rememberRipple import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource @@ -49,7 +48,11 @@ fun AppBarWithBackButton( contentDescription = null, modifier = Modifier .size(size = TangemTheme.dimens.size24) - .clickable { onBackClick() }, + .clickable( + indication = rememberRipple(bounded = false), + interactionSource = remember { MutableInteractionSource() }, + onClick = onBackClick, + ), tint = TangemTheme.colors.icon.primary1, ) if (!text.isNullOrBlank()) { diff --git a/features/swap/data/build.gradle.kts b/features/swap/data/build.gradle.kts index 2d753c6fa2..53dddfc5a9 100644 --- a/features/swap/data/build.gradle.kts +++ b/features/swap/data/build.gradle.kts @@ -8,15 +8,19 @@ plugins { dependencies { + /** AndroidX */ + implementation(deps.androidx.datastore) + /** Project*/ - implementation(project(":core:datasource")) - implementation(project(":core:utils")) - implementation(project(":features:swap:domain")) + implementation(projects.core.datasource) + implementation(projects.core.utils) + implementation(projects.features.swap.domain) /** Network */ implementation(deps.retrofit) implementation(deps.moshi) implementation(deps.moshi.kotlin) + implementation(deps.arrow.core) /** Domain */ implementation(projects.domain.tokens.models) diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapTransactionRepository.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapTransactionRepository.kt new file mode 100644 index 0000000000..63c8aecb72 --- /dev/null +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapTransactionRepository.kt @@ -0,0 +1,163 @@ +package com.tangem.feature.swap + +import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.datasource.local.preferences.PreferencesKeys +import com.tangem.datasource.local.preferences.utils.getObjectList +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.feature.swap.domain.SwapTransactionRepository +import com.tangem.feature.swap.domain.models.domain.SavedSwapTransactionListModel +import com.tangem.feature.swap.domain.models.domain.SavedSwapTransactionModel +import com.tangem.utils.extensions.addOrReplace +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +class DefaultSwapTransactionRepository( + private val appPreferencesStore: AppPreferencesStore, +) : SwapTransactionRepository { + + override suspend fun storeTransaction( + userWalletId: UserWalletId, + fromCryptoCurrencyId: CryptoCurrency.ID, + toCryptoCurrencyId: CryptoCurrency.ID, + transaction: SavedSwapTransactionModel, + ) { + appPreferencesStore.editData { mutablePreferences -> + val savedTransactions: List? = mutablePreferences.getObjectList( + key = PreferencesKeys.SWAP_TRANSACTIONS_KEY, + ) + + val tokenTransactions = savedTransactions + ?.firstOrNull { + it.checkId( + checkUserWalletId = userWalletId, + fromCurrencyId = fromCryptoCurrencyId, + toCurrencyId = toCryptoCurrencyId, + ) + } + ?.transactions + ?.addOrReplace( + item = transaction, + predicate = { it.txId == transaction.txId }, + ) ?: listOf(transaction) + + mutablePreferences.setObject( + key = PreferencesKeys.SWAP_TRANSACTIONS_KEY, + value = savedTransactions?.updateList( + userWalletId = userWalletId, + fromCryptoCurrencyId = fromCryptoCurrencyId, + toCryptoCurrencyId = toCryptoCurrencyId, + transactions = tokenTransactions, + ) ?: listOf( + SavedSwapTransactionListModel( + userWalletId = userWalletId.stringValue, + fromCryptoCurrencyId = fromCryptoCurrencyId.value, + toCryptoCurrencyId = toCryptoCurrencyId.value, + transactions = tokenTransactions, + ), + ), + ) + } + } + + override fun getTransactions( + userWalletId: UserWalletId, + cryptoCurrencyId: CryptoCurrency.ID, + ): Flow?> { + return appPreferencesStore.getObjectList( + key = PreferencesKeys.SWAP_TRANSACTIONS_KEY, + ).map { savedTransactions -> + savedTransactions + ?.filter { + it.userWalletId == userWalletId.stringValue && + ( + it.toCryptoCurrencyId == cryptoCurrencyId.value || + it.fromCryptoCurrencyId == cryptoCurrencyId.value + ) + } + } + } + + override suspend fun removeTransaction( + userWalletId: UserWalletId, + fromCryptoCurrencyId: CryptoCurrency.ID, + toCryptoCurrencyId: CryptoCurrency.ID, + txId: String, + ) { + appPreferencesStore.editData { mutablePreferences -> + val savedList: List? = mutablePreferences.getObjectList( + key = PreferencesKeys.SWAP_TRANSACTIONS_KEY, + ) + val tokenTransactions = savedList + ?.first { + it.checkId( + checkUserWalletId = userWalletId, + fromCurrencyId = fromCryptoCurrencyId, + toCurrencyId = toCryptoCurrencyId, + ) + } + ?.transactions + ?.dropWhile { it.txId == txId } + + val editedList = + if (tokenTransactions.isNullOrEmpty()) { + savedList?.dropWhile { + it.checkId( + checkUserWalletId = userWalletId, + fromCurrencyId = fromCryptoCurrencyId, + toCurrencyId = toCryptoCurrencyId, + ) + } + } else { + savedList.updateList( + userWalletId = userWalletId, + fromCryptoCurrencyId = fromCryptoCurrencyId, + toCryptoCurrencyId = toCryptoCurrencyId, + transactions = tokenTransactions, + ) + } + + if (editedList.isNullOrEmpty()) { + mutablePreferences.remove(key = PreferencesKeys.SWAP_TRANSACTIONS_KEY) + } else { + mutablePreferences.setObject( + key = PreferencesKeys.SWAP_TRANSACTIONS_KEY, + value = editedList, + ) + } + } + } + + private fun SavedSwapTransactionListModel.checkId( + checkUserWalletId: UserWalletId, + fromCurrencyId: CryptoCurrency.ID, + toCurrencyId: CryptoCurrency.ID, + ): Boolean { + return userWalletId == checkUserWalletId.stringValue && + toCryptoCurrencyId == toCurrencyId.value && + fromCryptoCurrencyId == fromCurrencyId.value + } + + private fun List.updateList( + userWalletId: UserWalletId, + fromCryptoCurrencyId: CryptoCurrency.ID, + toCryptoCurrencyId: CryptoCurrency.ID, + transactions: List, + ): List { + return addOrReplace( + item = SavedSwapTransactionListModel( + userWalletId = userWalletId.stringValue, + fromCryptoCurrencyId = fromCryptoCurrencyId.value, + toCryptoCurrencyId = toCryptoCurrencyId.value, + transactions = transactions, + ), + predicate = { + it.checkId( + checkUserWalletId = userWalletId, + fromCurrencyId = fromCryptoCurrencyId, + toCurrencyId = toCryptoCurrencyId, + ) + }, + ) + } +} \ No newline at end of file diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt index 9ea783fc6b..338fafe196 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt @@ -1,5 +1,8 @@ package com.tangem.feature.swap +import arrow.core.Either +import arrow.core.raise.catch +import arrow.core.raise.either import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.Approver import com.tangem.blockchain.common.Blockchain @@ -52,6 +55,7 @@ internal class SwapRepositoryImpl @Inject constructor( private val leastTokenInfoConverter = LeastTokenInfoConverter() private val swapPairInfoConverter = SwapPairInfoConverter() private val cryptoCurrencyFactory = CryptoCurrencyFactory() + private val exchangeStatusConverter = ExchangeStatusConverter() override suspend fun getPairs( initialCurrency: LeastTokenInfo, @@ -103,6 +107,25 @@ internal class SwapRepositoryImpl @Inject constructor( ).getOrThrow() } + override suspend fun getExchangeStatus(txId: String): Either { + return withContext(coroutineDispatcher.io) { + either { + catch( + { + exchangeStatusConverter.convert( + tangemExpressApi + .getExchangeStatus(txId) + .getOrThrow(), + ) + }, + { + raise(UnknownError(it.message)) + }, + ) + } + } + } + override suspend fun getRates(currencyId: String, tokenIds: List): Map { // workaround cause backend do not return arbitrum and optimism rates val addedTokens = if (tokenIds.contains(OPTIMISM_ID) || tokenIds.contains(ARBITRUM_ID)) { diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ExchangeStatusConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ExchangeStatusConverter.kt new file mode 100644 index 0000000000..f5901e8c69 --- /dev/null +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ExchangeStatusConverter.kt @@ -0,0 +1,19 @@ +package com.tangem.feature.swap.converters + +import com.tangem.datasource.api.express.models.response.ExchangeStatusResponse +import com.tangem.feature.swap.domain.models.domain.ExchangeStatus +import com.tangem.feature.swap.domain.models.domain.ExchangeStatusModel +import com.tangem.utils.converter.Converter + +internal class ExchangeStatusConverter : Converter { + override fun convert(value: ExchangeStatusResponse): ExchangeStatusModel { + return ExchangeStatusModel( + providerId = value.providerId, + status = ExchangeStatus.values().firstOrNull { + it.name.lowercase() == value.externalStatus.name.lowercase() + }, + txId = value.externalTxId, + txUrl = value.externalTxUrl, + ) + } +} \ No newline at end of file diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt index 4c34db7b17..b30b20f817 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt @@ -7,11 +7,14 @@ import com.tangem.datasource.api.oneinch.OneInchApiFactory import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.config.ConfigManager import com.tangem.datasource.di.NetworkMoshi +import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.legacy.WalletsStateHolder import com.tangem.feature.swap.SwapRepositoryImpl import com.tangem.feature.swap.converters.ErrorsDataConverter +import com.tangem.feature.swap.DefaultSwapTransactionRepository import com.tangem.feature.swap.domain.SwapRepository +import com.tangem.feature.swap.domain.SwapTransactionRepository import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides @@ -47,6 +50,14 @@ internal class SwapDataModule { ) } + @Provides + @Singleton + fun provideSwapTransactionRepository(appPreferencesStore: AppPreferencesStore): SwapTransactionRepository { + return DefaultSwapTransactionRepository( + appPreferencesStore = appPreferencesStore, + ) + } + @Provides @Singleton internal fun provideErrorsConverter(@NetworkMoshi moshi: Moshi): ErrorsDataConverter { diff --git a/features/swap/domain/build.gradle.kts b/features/swap/domain/build.gradle.kts index 4f4d42fe13..f3841b400d 100644 --- a/features/swap/domain/build.gradle.kts +++ b/features/swap/domain/build.gradle.kts @@ -27,9 +27,11 @@ dependencies { implementation(projects.domain.legacy) implementation(projects.domain.demo) implementation(projects.domain.card) + implementation(projects.domain.appCurrency.models) /** Core modules */ implementation(projects.core.utils) + implementation(projects.core.ui) /** Feature Apis */ implementation(projects.features.wallet.api) @@ -40,4 +42,5 @@ dependencies { implementation(deps.arrow.core) implementation(deps.timber) implementation(deps.tangem.blockchain) + implementation(deps.moshi) } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index afc2b3787b..8669d31ece 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -213,7 +213,10 @@ internal class SwapInteractorImpl @Inject constructor( return when (result) { is SendTxResult.Success -> { allowPermissionsHandler.addAddressToInProgress(permissionOptions.forTokenContractAddress) - TxState.TxSent(txAddress = userWalletManager.getLastTransactionHash(networkId, derivationPath) ?: "") + TxState.TxSent( + txAddress = userWalletManager.getLastTransactionHash(networkId, derivationPath).orEmpty(), + timestamp = System.currentTimeMillis(), + ) } SendTxResult.UserCancelledError -> TxState.UserCancelled is SendTxResult.BlockchainSdkError -> TxState.BlockchainError @@ -370,7 +373,7 @@ internal class SwapInteractorImpl @Inject constructor( currencyToGet = currencyToGet, amount = amount, fee = fee, - providerId = swapProvider.providerId, + swapProvider = swapProvider, userWalletId = requireNotNull(getSelectedWallet()).walletId, ) } @@ -455,7 +458,8 @@ internal class SwapInteractorImpl @Inject constructor( swapData.toTokenAmount, currencyToGet.symbol, ), - txAddress = userWalletManager.getLastTransactionHash(networkId, derivationPath) ?: "", + txAddress = userWalletManager.getLastTransactionHash(networkId, derivationPath).orEmpty(), + timestamp = System.currentTimeMillis(), ) } SendTxResult.UserCancelledError -> TxState.UserCancelled @@ -471,7 +475,7 @@ internal class SwapInteractorImpl @Inject constructor( currencyToGet: CryptoCurrencyStatus, amount: SwapAmount, fee: TxFee, - providerId: String, + swapProvider: SwapProvider, userWalletId: UserWalletId, ): TxState { val exchangeData = repository.getExchangeData( @@ -481,7 +485,7 @@ internal class SwapInteractorImpl @Inject constructor( toNetwork = currencyToGet.currency.network.backendId, fromAmount = amount.toStringWithRightOffset(), fromDecimals = amount.decimals, - providerId = providerId, + providerId = swapProvider.providerId, rateType = RateType.FLOAT, toAddress = currencyToGet.value.networkAddress?.defaultAddress ?: "", ) @@ -511,6 +515,7 @@ internal class SwapInteractorImpl @Inject constructor( } }, ifRight = { + val timestamp = System.currentTimeMillis() TxState.TxSent( fromAmount = amountFormatter.formatSwapAmountToUI( amount, @@ -523,7 +528,8 @@ internal class SwapInteractorImpl @Inject constructor( txAddress = userWalletManager.getLastTransactionHash( currencyToSend.currency.network.backendId, derivationPath, - ) ?: "", + ).orEmpty(), + timestamp = timestamp, ) }, ) diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt index 13650fe4fe..c96bb13b19 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt @@ -1,5 +1,6 @@ package com.tangem.feature.swap.domain +import arrow.core.Either import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.swap.domain.models.data.AggregatedSwapDataModel @@ -14,6 +15,8 @@ interface SwapRepository { suspend fun getExchangeableTokens(networkId: String): List + suspend fun getExchangeStatus(txId: String): Either + @Suppress("LongParameterList") suspend fun findBestQuote( fromContractAddress: String, diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapTransactionRepository.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapTransactionRepository.kt new file mode 100644 index 0000000000..d89914d537 --- /dev/null +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapTransactionRepository.kt @@ -0,0 +1,29 @@ +package com.tangem.feature.swap.domain + +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.feature.swap.domain.models.domain.SavedSwapTransactionListModel +import com.tangem.feature.swap.domain.models.domain.SavedSwapTransactionModel +import kotlinx.coroutines.flow.Flow + +interface SwapTransactionRepository { + + suspend fun storeTransaction( + userWalletId: UserWalletId, + fromCryptoCurrencyId: CryptoCurrency.ID, + toCryptoCurrencyId: CryptoCurrency.ID, + transaction: SavedSwapTransactionModel, + ) + + fun getTransactions( + userWalletId: UserWalletId, + cryptoCurrencyId: CryptoCurrency.ID, + ): Flow?> + + suspend fun removeTransaction( + userWalletId: UserWalletId, + fromCryptoCurrencyId: CryptoCurrency.ID, + toCryptoCurrencyId: CryptoCurrency.ID, + txId: String, + ) +} \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/ExchangeStatus.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/ExchangeStatus.kt index a74e8fbb6a..dc5c5738be 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/ExchangeStatus.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/ExchangeStatus.kt @@ -1,7 +1,7 @@ package com.tangem.feature.swap.domain.models.domain data class ExchangeStatusModel( - val providerId: Int, + val providerId: String, val status: ExchangeStatus? = null, val txId: String? = null, val txUrl: String? = null, diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/SavedSwapTransactionListModel.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/SavedSwapTransactionListModel.kt new file mode 100644 index 0000000000..6b59b50197 --- /dev/null +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/SavedSwapTransactionListModel.kt @@ -0,0 +1,19 @@ +package com.tangem.feature.swap.domain.models.domain + +data class SavedSwapTransactionListModel( + val userWalletId: String, + val fromCryptoCurrencyId: String, + val toCryptoCurrencyId: String, + val transactions: List, +) + +data class SavedSwapTransactionModel( + val txId: String, + val timestamp: Long, + val fromCryptoAmount: String, + val toCryptoAmount: String, + val provider: SwapProvider, + val toFiatAmount: String? = null, + val fromFiatAmount: String? = null, + val status: ExchangeStatusModel? = null, +) \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/TxState.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/TxState.kt index ca95f626e3..0a0f5b5b2a 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/TxState.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/TxState.kt @@ -6,6 +6,7 @@ sealed class TxState { val fromAmount: String? = null, val toAmount: String? = null, val txAddress: String, + val timestamp: Long, ) : TxState() object UserCancelled : TxState() diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapSuccessStateHolder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapSuccessStateHolder.kt index 640c4809d3..814723462c 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapSuccessStateHolder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapSuccessStateHolder.kt @@ -2,14 +2,15 @@ package com.tangem.feature.swap.models import com.tangem.core.ui.components.currency.tokenicon.TokenIconState import com.tangem.core.ui.extensions.TextReference -import com.tangem.feature.swap.domain.models.domain.SwapProvider data class SwapSuccessStateHolder( val timestamp: Long, val txUrl: String, val fee: TextReference, val rate: TextReference, - val selectedProvider: SwapProvider, + val providerName: TextReference, + val providerType: TextReference, + val providerIcon: String, val fromTokenAmount: TextReference, val toTokenAmount: TextReference, val fromTokenFiatAmount: TextReference, 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 4a90e77fab..a13a37110c 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 @@ -21,7 +21,6 @@ import com.tangem.feature.swap.domain.models.ui.* import com.tangem.feature.swap.models.* import com.tangem.feature.swap.models.states.* import com.tangem.feature.swap.presentation.R -import com.tangem.feature.swap.viewmodels.SwapProcessDataState import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import java.math.BigDecimal @@ -565,38 +564,37 @@ internal class StateBuilder( ) } + @Suppress("LongParameterList") fun createSuccessState( uiState: SwapStateHolder, txState: TxState.TxSent, - dataState: SwapProcessDataState, + fromAmount: BigDecimal, + toAmount: BigDecimal, txUrl: String, onSecondaryBtnClick: () -> Unit, ): SwapStateHolder { - val fromToken = requireNotNull(uiState.sendCardData as? SwapCardState.SwapCardData) - val toToken = requireNotNull(uiState.receiveCardData as? SwapCardState.SwapCardData) - val fromTokenIconState = fromToken.token?.let(iconStateConverter::convert) - val toTokenIconState = toToken.token?.let(iconStateConverter::convert) + val providerState = uiState.providerState as ProviderState.Content + val fromToken = requireNotNull((uiState.sendCardData as? SwapCardState.SwapCardData)?.token) + val toToken = requireNotNull((uiState.receiveCardData as? SwapCardState.SwapCardData)?.token) + val fromTokenIconState = iconStateConverter.convert(fromToken) + val toTokenIconState = iconStateConverter.convert(toToken) val fee = uiState.fee as? FeeItemState.Content ?: return uiState - val fromCryptoCurrencyStatus = requireNotNull(fromToken.token) - val toCryptoCurrencyStatus = requireNotNull(toToken.token) - val rate = txState.toAmount?.toBigDecimal()?.calculateRate( - txState.fromAmount?.toBigDecimal() ?: BigDecimal.ZERO, - toCryptoCurrencyStatus.currency.decimals, - ) - val fromCurrencySymbol = fromCryptoCurrencyStatus.currency.symbol - val toCurrencySymbol = toCryptoCurrencyStatus.currency.symbol + val fromFiatAmount = getFormattedFiatAmount(fromToken.value.fiatRate?.multiply(fromAmount)) + val toFiatAmount = getFormattedFiatAmount(toToken.value.fiatRate?.multiply(toAmount)) return uiState.copy( successState = SwapSuccessStateHolder( - timestamp = System.currentTimeMillis(), + timestamp = txState.timestamp, txUrl = txUrl, - selectedProvider = requireNotNull(dataState.selectedProvider), + providerName = TextReference.Str(providerState.name), + providerType = TextReference.Str(providerState.type), + providerIcon = providerState.iconUrl, fee = TextReference.Str("${fee.amountCrypto} ${fee.symbolCrypto} (${fee.amountFiatFormatted})"), - rate = TextReference.Str("1 $fromCurrencySymbol ≈ $rate $toCurrencySymbol"), - fromTokenAmount = TextReference.Str("${txState.fromAmount.orEmpty()} ${fromToken.tokenCurrency}}"), - toTokenAmount = TextReference.Str("${txState.toAmount.orEmpty()} ${toToken.tokenCurrency}}"), - fromTokenFiatAmount = TextReference.Str(fromToken.amountEquivalent.orEmpty()), - toTokenFiatAmount = TextReference.Str(toToken.amountEquivalent.orEmpty()), + rate = TextReference.Str(providerState.rate), + fromTokenAmount = TextReference.Str(txState.fromAmount.orEmpty()), + toTokenAmount = TextReference.Str(txState.toAmount.orEmpty()), + fromTokenFiatAmount = TextReference.Str(fromFiatAmount), + toTokenFiatAmount = TextReference.Str(toFiatAmount), fromTokenIconState = fromTokenIconState, toTokenIconState = toTokenIconState, onSecondaryButtonClick = onSecondaryBtnClick, @@ -985,7 +983,7 @@ internal class StateBuilder( return BigDecimalFormatter.formatFiatAmount(fiatAmount, appCurrency.code, appCurrency.symbol) } - private fun getFormattedFiatAmount(amount: BigDecimal): String { + private fun getFormattedFiatAmount(amount: BigDecimal?): String { val appCurrency = appCurrencyProvider() return BigDecimalFormatter.formatFiatAmount(amount, appCurrency.code, appCurrency.symbol) diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt index 909239af13..ffb9334830 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt @@ -23,7 +23,6 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.shareText import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.swap.domain.models.domain.ExchangeProviderType -import com.tangem.feature.swap.domain.models.domain.SwapProvider import com.tangem.feature.swap.models.SwapSuccessStateHolder import com.tangem.feature.swap.presentation.R @@ -60,7 +59,7 @@ private fun SwapSuccessScreenContent(state: SwapSuccessStateHolder, padding: Pad .background(TangemTheme.colors.background.secondary) .padding(horizontal = TangemTheme.dimens.spacing16), ) { - TransactionDoneTitle(titleRes = R.string.swapping_success_view_title, date = 0L) + TransactionDoneTitle(titleRes = R.string.swapping_success_view_title, date = state.timestamp) SpacerH16() InputRowImage( title = TextReference.Res(R.string.swapping_success_from_title), @@ -83,9 +82,9 @@ private fun SwapSuccessScreenContent(state: SwapSuccessStateHolder, padding: Pad ) SpacerH16() InputRowBestRate( - imageUrl = state.selectedProvider.imageLarge, - title = TextReference.Str(state.selectedProvider.name), - titleExtra = TextReference.Str(state.selectedProvider.type.name), + imageUrl = state.providerIcon, + title = state.providerName, + titleExtra = state.providerType, subtitle = state.rate, modifier = Modifier .clip(TangemTheme.shapes.roundedCornersXMedium) @@ -153,13 +152,9 @@ private val state = SwapSuccessStateHolder( timestamp = 0L, txUrl = "https://www.google.com/#q=nam", fee = TextReference.Str("1 000 DAI ~ 1 000 MATIC"), - selectedProvider = SwapProvider( - providerId = "1inch", - rateTypes = listOf(), - name = "1inch", - type = ExchangeProviderType.DEX, - imageLarge = "", - ), + providerName = TextReference.Str("1inch"), + providerType = TextReference.Str(ExchangeProviderType.DEX.name), + providerIcon = "", fromTokenAmount = TextReference.Str("1 000 DAI"), toTokenAmount = TextReference.Str("1 000 MATIC"), fromTokenFiatAmount = TextReference.Str("1 000 $"), 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 4fb07606b9..d1dcdef1c0 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 @@ -27,9 +27,7 @@ import com.tangem.feature.swap.presentation.SwapFragment import com.tangem.feature.swap.router.SwapNavScreen import com.tangem.feature.swap.router.SwapRouter import com.tangem.feature.swap.ui.StateBuilder -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import com.tangem.utils.coroutines.Debouncer -import com.tangem.utils.coroutines.runCatching +import com.tangem.utils.coroutines.* import com.tangem.utils.isNullOrZero import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.* @@ -405,7 +403,8 @@ internal class SwapViewModel @Inject constructor( uiState = stateBuilder.createSuccessState( uiState = uiState, txState = it, - dataState = dataState, + fromAmount = dataState.amount?.toBigDecimal() ?: BigDecimal.ZERO, + toAmount = dataState.swapDataModel?.toTokenAmount?.value ?: BigDecimal.ZERO, txUrl = url, onSecondaryBtnClick = { val txHash = it.txAddress