Updated on 2026-08-14
This commit is contained in:
parent
6031a97ffe
commit
8d0f11d10b
67 changed files with 1527 additions and 41 deletions
|
|
@ -5,4 +5,5 @@ interface SwapFeatureToggles {
|
|||
val isSwapIntegratedApproveEnabled: Boolean
|
||||
val isSwapAbEnabled: Boolean
|
||||
val isSwapProviderFilterEnabled: Boolean
|
||||
val isSwapRateExperienceEnabled: Boolean
|
||||
}
|
||||
|
|
@ -27,6 +27,7 @@ dependencies {
|
|||
|
||||
/** Network */
|
||||
implementation(deps.retrofit)
|
||||
implementation(deps.retrofit.moshi)
|
||||
implementation(deps.moshi)
|
||||
implementation(deps.moshi.kotlin)
|
||||
implementation(deps.arrow.core)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
package com.tangem.feature.swap
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.datasource.api.surveysparrow.SurveySparrowApi
|
||||
import com.tangem.datasource.api.surveysparrow.models.CreateSurveySparrowResponseBody
|
||||
import com.tangem.datasource.api.surveysparrow.models.SurveySparrowAnswerDto
|
||||
import com.tangem.feature.swap.domain.api.SwapFeedbackRepository
|
||||
import com.tangem.feature.swap.domain.models.domain.ExistingRating
|
||||
import com.tangem.feature.swap.domain.models.domain.SwapFeedbackParams
|
||||
import org.json.JSONObject
|
||||
|
||||
internal class DefaultSwapFeedbackRepository(
|
||||
private val api: SurveySparrowApi,
|
||||
private val surveyId: Long,
|
||||
private val ratingQuestionId: Long,
|
||||
private val feedbackQuestionId: Long,
|
||||
) : SwapFeedbackRepository {
|
||||
|
||||
override suspend fun getRating(txExternalId: String): Either<Throwable, ExistingRating?> {
|
||||
return Either.catch {
|
||||
val responses = api.getResponses(
|
||||
surveyId = surveyId,
|
||||
variables = JSONObject().put("tx_external_id", txExternalId).toString(),
|
||||
limit = 1,
|
||||
)
|
||||
val ratingAnswer = responses.data
|
||||
.firstOrNull()
|
||||
?.answers
|
||||
?.firstOrNull { answer ->
|
||||
when (val id = answer.questionId) {
|
||||
is Number -> id.toLong() == ratingQuestionId
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
?.answer
|
||||
?.let { v ->
|
||||
when (v) {
|
||||
is Number -> v.toInt()
|
||||
is String -> v.toIntOrNull()
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
if (ratingAnswer != null) ExistingRating(ratingAnswer) else null
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun submitFeedback(params: SwapFeedbackParams): Either<Throwable, Unit> {
|
||||
return Either.catch {
|
||||
api.createResponse(
|
||||
CreateSurveySparrowResponseBody(
|
||||
surveyId = surveyId,
|
||||
answers = buildList {
|
||||
add(SurveySparrowAnswerDto(ratingQuestionId, params.rating.toString()))
|
||||
if (params.feedback.isNotEmpty()) {
|
||||
add(SurveySparrowAnswerDto(feedbackQuestionId, params.feedback))
|
||||
}
|
||||
},
|
||||
variables = mapOf(
|
||||
"tx_external_id" to params.txExternalId,
|
||||
"provider_name" to params.providerName,
|
||||
"tx_url" to params.txUrl,
|
||||
"user_wallet_id" to params.userWalletIdHash,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.feature.swap
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.right
|
||||
import com.tangem.feature.swap.domain.api.SwapFeedbackRepository
|
||||
import com.tangem.feature.swap.domain.models.domain.ExistingRating
|
||||
import com.tangem.feature.swap.domain.models.domain.SwapFeedbackParams
|
||||
|
||||
internal class NoOpSwapFeedbackRepository : SwapFeedbackRepository {
|
||||
|
||||
override suspend fun getRating(txExternalId: String): Either<Throwable, ExistingRating?> = null.right()
|
||||
|
||||
override suspend fun submitFeedback(params: SwapFeedbackParams): Either<Throwable, Unit> = Unit.right()
|
||||
}
|
||||
|
|
@ -5,16 +5,21 @@ import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory
|
|||
import com.tangem.data.common.network.NetworkFactory
|
||||
import com.tangem.datasource.api.express.TangemExpressApi
|
||||
import com.tangem.datasource.api.express.models.response.ExpressErrorResponse
|
||||
import com.tangem.datasource.api.surveysparrow.SurveySparrowApi
|
||||
import com.tangem.datasource.crypto.DataSignatureVerifier
|
||||
import com.tangem.datasource.di.NetworkMoshi
|
||||
import com.tangem.datasource.local.config.environment.EnvironmentConfig
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
||||
import com.tangem.domain.exchange.RampStateManager
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.feature.swap.DefaultSwapFeedbackRepository
|
||||
import com.tangem.feature.swap.DefaultSwapRepository
|
||||
import com.tangem.feature.swap.NoOpSwapFeedbackRepository
|
||||
import com.tangem.feature.swap.DefaultSwapTransactionRepository
|
||||
import com.tangem.feature.swap.converters.ErrorsDataConverter
|
||||
import com.tangem.feature.swap.domain.SwapTransactionRepository
|
||||
import com.tangem.feature.swap.domain.api.SwapFeedbackRepository
|
||||
import com.tangem.feature.swap.domain.api.SwapRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
|
|
@ -75,4 +80,20 @@ internal class SwapDataModule {
|
|||
val jsonAdapter = moshi.adapter(ExpressErrorResponse::class.java)
|
||||
return ErrorsDataConverter(jsonAdapter)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
internal fun provideSwapFeedbackRepository(
|
||||
api: SurveySparrowApi,
|
||||
environmentConfig: EnvironmentConfig,
|
||||
): SwapFeedbackRepository {
|
||||
val rating = environmentConfig.surveySparrowSwapRating
|
||||
?: return NoOpSwapFeedbackRepository()
|
||||
return DefaultSwapFeedbackRepository(
|
||||
api = api,
|
||||
surveyId = rating.surveyId,
|
||||
ratingQuestionId = rating.ratingQuestionId,
|
||||
feedbackQuestionId = rating.feedbackQuestionId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.feature.swap.domain
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.feature.swap.domain.api.SwapFeedbackRepository
|
||||
import com.tangem.feature.swap.domain.models.domain.ExistingRating
|
||||
import com.tangem.feature.swap.domain.models.domain.SwapFeedbackParams
|
||||
import javax.inject.Inject
|
||||
|
||||
class SwapFeedbackUseCase @Inject constructor(
|
||||
private val repository: SwapFeedbackRepository,
|
||||
) {
|
||||
suspend fun getExistingRating(txExternalId: String): Either<Throwable, ExistingRating?> =
|
||||
repository.getRating(txExternalId)
|
||||
|
||||
suspend fun submit(params: SwapFeedbackParams): Either<Throwable, Unit> = repository.submitFeedback(params)
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.feature.swap.domain.api
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.feature.swap.domain.models.domain.ExistingRating
|
||||
import com.tangem.feature.swap.domain.models.domain.SwapFeedbackParams
|
||||
|
||||
interface SwapFeedbackRepository {
|
||||
suspend fun getRating(txExternalId: String): Either<Throwable, ExistingRating?>
|
||||
suspend fun submitFeedback(params: SwapFeedbackParams): Either<Throwable, Unit>
|
||||
}
|
||||
|
|
@ -5,8 +5,10 @@ import com.tangem.feature.swap.domain.AllowPermissionsHandler
|
|||
import com.tangem.feature.swap.domain.AllowPermissionsHandlerImpl
|
||||
import com.tangem.feature.swap.domain.GetSwapUiModeUseCase
|
||||
import com.tangem.feature.swap.domain.SetSwapUiModeUseCase
|
||||
import com.tangem.feature.swap.domain.SwapFeedbackUseCase
|
||||
import com.tangem.feature.swap.domain.SwapInteractor
|
||||
import com.tangem.feature.swap.domain.SwapInteractorImpl
|
||||
import com.tangem.feature.swap.domain.api.SwapFeedbackRepository
|
||||
import com.tangem.feature.swap.domain.api.SwapRepository
|
||||
import com.tangem.features.swap.SwapFeatureToggles
|
||||
import com.tangem.feature.swap.domain.transfer.SwapTransferInteractor
|
||||
|
|
@ -44,6 +46,12 @@ internal class SwapDomainModule {
|
|||
@Singleton
|
||||
fun provideSetSwapUiModeUseCase(swapRepository: SwapRepository): SetSwapUiModeUseCase =
|
||||
SetSwapUiModeUseCase(swapRepository = swapRepository)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSwapFeedbackUseCase(repository: SwapFeedbackRepository): SwapFeedbackUseCase {
|
||||
return SwapFeedbackUseCase(repository)
|
||||
}
|
||||
}
|
||||
|
||||
@Module
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
package com.tangem.feature.swap.domain.models.domain
|
||||
|
||||
data class ExistingRating(val rating: Int)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.feature.swap.domain.models.domain
|
||||
|
||||
data class SwapFeedbackParams(
|
||||
val userWalletIdHash: String,
|
||||
val providerName: String,
|
||||
val txUrl: String,
|
||||
val txExternalId: String,
|
||||
val rating: Int,
|
||||
val feedback: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
package com.tangem.feature.swap.domain
|
||||
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.feature.swap.domain.api.SwapFeedbackRepository
|
||||
import com.tangem.feature.swap.domain.models.domain.ExistingRating
|
||||
import com.tangem.feature.swap.domain.models.domain.SwapFeedbackParams
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
internal class SwapFeedbackUseCaseTest {
|
||||
|
||||
private val repository: SwapFeedbackRepository = mockk()
|
||||
private val useCase = SwapFeedbackUseCase(repository)
|
||||
|
||||
@Test
|
||||
fun `getExistingRating returns ExistingRating when rated`() = runTest {
|
||||
coEvery { repository.getRating("tx123") } returns ExistingRating(rating = 4).right()
|
||||
|
||||
val result = useCase.getExistingRating("tx123")
|
||||
|
||||
assertThat(result.getOrNull()).isEqualTo(ExistingRating(rating = 4))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getExistingRating returns null when not rated`() = runTest {
|
||||
coEvery { repository.getRating("tx123") } returns null.right()
|
||||
|
||||
val result = useCase.getExistingRating("tx123")
|
||||
|
||||
assertThat(result.getOrNull()).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getExistingRating returns Left on error`() = runTest {
|
||||
coEvery { repository.getRating("tx123") } returns RuntimeException("Network error").left()
|
||||
|
||||
val result = useCase.getExistingRating("tx123")
|
||||
|
||||
assertThat(result.isLeft()).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `submit delegates to repository`() = runTest {
|
||||
val params = SwapFeedbackParams(
|
||||
userWalletIdHash = "hash",
|
||||
providerName = "ChangeNOW",
|
||||
txUrl = "https://example.com/tx/abc",
|
||||
txExternalId = "tx123",
|
||||
rating = 5,
|
||||
feedback = "Great!",
|
||||
)
|
||||
coEvery { repository.submitFeedback(params) } returns Unit.right()
|
||||
|
||||
useCase.submit(params)
|
||||
|
||||
coVerify(exactly = 1) { repository.submitFeedback(params) }
|
||||
}
|
||||
}
|
||||
|
|
@ -24,4 +24,8 @@ internal class DefaultSwapFeatureToggles @Inject constructor(
|
|||
override val isSwapProviderFilterEnabled: Boolean = featureTogglesManager.isFeatureEnabled(
|
||||
toggle = FeatureToggles.AND_15009_SWAP_PROVIDER_FILTER_ENABLED,
|
||||
)
|
||||
|
||||
override val isSwapRateExperienceEnabled: Boolean = featureTogglesManager.isFeatureEnabled(
|
||||
toggle = FeatureToggles.AND_15103_SWAP_RATE_EXPERIENCE_ENABLED,
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue