Updated on 2026-08-14
This commit is contained in:
commit
bffd007a35
21 changed files with 396 additions and 100 deletions
|
|
@ -49,6 +49,6 @@ interface TangemExpressApi {
|
|||
@Query("toAddress") toAddress: String,
|
||||
): ApiResponse<ExchangeDataResponse>
|
||||
|
||||
@GET("exchange-result")
|
||||
suspend fun getExchangeResults(@Query("txId") txId: String): ApiResponse<ExchangeResultsResponse>
|
||||
@GET("exchange-status")
|
||||
suspend fun getExchangeStatus(@Query("txId") txId: String): ApiResponse<ExchangeStatusResponse>
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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<Preferences> */
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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<SavedSwapTransactionListModel>? = 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<List<SavedSwapTransactionListModel>?> {
|
||||
return appPreferencesStore.getObjectList<SavedSwapTransactionListModel>(
|
||||
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<SavedSwapTransactionListModel>? = 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<SavedSwapTransactionListModel>.updateList(
|
||||
userWalletId: UserWalletId,
|
||||
fromCryptoCurrencyId: CryptoCurrency.ID,
|
||||
toCryptoCurrencyId: CryptoCurrency.ID,
|
||||
transactions: List<SavedSwapTransactionModel>,
|
||||
): List<SavedSwapTransactionListModel> {
|
||||
return addOrReplace(
|
||||
item = SavedSwapTransactionListModel(
|
||||
userWalletId = userWalletId.stringValue,
|
||||
fromCryptoCurrencyId = fromCryptoCurrencyId.value,
|
||||
toCryptoCurrencyId = toCryptoCurrencyId.value,
|
||||
transactions = transactions,
|
||||
),
|
||||
predicate = {
|
||||
it.checkId(
|
||||
checkUserWalletId = userWalletId,
|
||||
fromCurrencyId = fromCryptoCurrencyId,
|
||||
toCurrencyId = toCryptoCurrencyId,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<UnknownError, ExchangeStatusModel> {
|
||||
return withContext(coroutineDispatcher.io) {
|
||||
either {
|
||||
catch(
|
||||
{
|
||||
exchangeStatusConverter.convert(
|
||||
tangemExpressApi
|
||||
.getExchangeStatus(txId)
|
||||
.getOrThrow(),
|
||||
)
|
||||
},
|
||||
{
|
||||
raise(UnknownError(it.message))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getRates(currencyId: String, tokenIds: List<String>): Map<String, Double> {
|
||||
// workaround cause backend do not return arbitrum and optimism rates
|
||||
val addedTokens = if (tokenIds.contains(OPTIMISM_ID) || tokenIds.contains(ARBITRUM_ID)) {
|
||||
|
|
|
|||
|
|
@ -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<ExchangeStatusResponse, ExchangeStatusModel> {
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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<Currency>
|
||||
|
||||
suspend fun getExchangeStatus(txId: String): Either<UnknownError, ExchangeStatusModel>
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
suspend fun findBestQuote(
|
||||
fromContractAddress: String,
|
||||
|
|
|
|||
|
|
@ -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<List<SavedSwapTransactionListModel>?>
|
||||
|
||||
suspend fun removeTransaction(
|
||||
userWalletId: UserWalletId,
|
||||
fromCryptoCurrencyId: CryptoCurrency.ID,
|
||||
toCryptoCurrencyId: CryptoCurrency.ID,
|
||||
txId: String,
|
||||
)
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<SavedSwapTransactionModel>,
|
||||
)
|
||||
|
||||
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,
|
||||
)
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 $"),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue