Updated on 2026-08-14

This commit is contained in:
Tangem 2026-05-22 11:17:28 +02:00
parent 6031a97ffe
commit 8d0f11d10b
67 changed files with 1527 additions and 41 deletions

View file

@ -32,6 +32,7 @@ sealed class ApiConfig {
MoonPay,
News,
GaslessTxService,
SurveySparrow,
}
private fun initializeId(): ID {
@ -47,6 +48,7 @@ sealed class ApiConfig {
is MoonPay -> ID.MoonPay
is News -> ID.News
is GaslessTxService -> ID.GaslessTxService
is SurveySparrow -> ID.SurveySparrow
}
}

View file

@ -0,0 +1,26 @@
package com.tangem.datasource.api.common.config
import com.tangem.datasource.local.config.environment.EnvironmentConfig
import com.tangem.utils.ProviderSuspend
internal class SurveySparrow(
private val environmentConfig: EnvironmentConfig,
) : ApiConfig() {
override val defaultEnvironment: ApiEnvironment = ApiEnvironment.PROD
override val environmentConfigs = listOf(
createProdEnvironment(),
)
private fun createProdEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
environment = ApiEnvironment.PROD,
baseUrl = "https://eu-api.surveysparrow.com/",
headers = buildMap {
put(
key = "Authorization",
value = ProviderSuspend { "Bearer ${environmentConfig.surveySparrowToken.orEmpty()}" },
)
},
)
}

View file

@ -0,0 +1,21 @@
package com.tangem.datasource.api.surveysparrow
import com.tangem.datasource.api.surveysparrow.models.CreateSurveySparrowResponseBody
import com.tangem.datasource.api.surveysparrow.models.SurveySparrowResponsesDto
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Query
interface SurveySparrowApi {
@GET("v3/responses")
suspend fun getResponses(
@Query("survey_id") surveyId: Long,
@Query("variables") variables: String,
@Query("limit") limit: Int = 1,
): SurveySparrowResponsesDto
@POST("v3/responses")
suspend fun createResponse(@Body body: CreateSurveySparrowResponseBody)
}

View file

@ -0,0 +1,11 @@
package com.tangem.datasource.api.surveysparrow.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class CreateSurveySparrowResponseBody(
@Json(name = "survey_id") val surveyId: Long,
@Json(name = "answers") val answers: List<SurveySparrowAnswerDto>,
@Json(name = "variables") val variables: Map<String, String>,
)

View file

@ -0,0 +1,10 @@
package com.tangem.datasource.api.surveysparrow.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class SurveySparrowAnswerDto(
@Json(name = "question_id") val questionId: Long,
@Json(name = "answer") val answer: String? = null,
)

View file

@ -0,0 +1,12 @@
package com.tangem.datasource.api.surveysparrow.models
import com.squareup.moshi.Json
// No @JsonClass: KotlinJsonAdapterFactory (registered in MoshiModule) handles this via reflection.
// Any? is required because the API returns question_id as Long for survey questions but as String
// ("startTime", "submittedTime", etc.) for metadata answers, and answer as Int for ratings but
// as String for other answer types.
data class SurveySparrowGetAnswerDto(
@Json(name = "question_id") val questionId: Any?,
@Json(name = "answer") val answer: Any?,
)

View file

@ -0,0 +1,9 @@
package com.tangem.datasource.api.surveysparrow.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class SurveySparrowResponseDto(
@Json(name = "answers") val answers: List<SurveySparrowGetAnswerDto>,
)

View file

@ -0,0 +1,9 @@
package com.tangem.datasource.api.surveysparrow.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class SurveySparrowResponsesDto(
@Json(name = "data") val data: List<SurveySparrowResponseDto>,
)

View file

@ -107,4 +107,10 @@ internal object ApiConfigsModule {
appInfoProvider = appInfoProvider,
)
}
@Provides
@IntoSet
fun provideSurveySparrowConfig(environmentConfig: EnvironmentConfig): ApiConfig {
return SurveySparrow(environmentConfig)
}
}

View file

@ -2,6 +2,7 @@ package com.tangem.datasource.di
import com.tangem.datasource.BuildConfig
import com.tangem.datasource.api.common.blockaid.BlockAidApi
import com.tangem.datasource.api.surveysparrow.SurveySparrowApi
import com.tangem.datasource.api.common.config.ApiConfig
import com.tangem.datasource.api.common.config.ApiConfig.Companion.MOCKED_BUILD_TYPE
import com.tangem.datasource.api.common.config.ApiConfigs
@ -180,6 +181,15 @@ internal object NetworkModule {
)
}
@Provides
@Singleton
fun provideSurveySparrowApi(retrofitApiBuilder: RetrofitApiBuilder): SurveySparrowApi {
return retrofitApiBuilder.build(
apiConfigId = ApiConfig.ID.SurveySparrow,
applyTimeoutAnnotations = false,
)
}
@Provides
@Singleton
fun provideMoonPayApi(retrofitApiBuilder: RetrofitApiBuilder): MoonPayApi {

View file

@ -3,6 +3,7 @@ package com.tangem.datasource.local.config.environment
import com.tangem.blockchain.common.BlockchainSdkConfig
import com.tangem.datasource.local.config.environment.models.ExpressModel
import com.tangem.datasource.local.config.environment.models.P2PKeys
import com.tangem.datasource.local.config.environment.models.SurveySparrowSwapRatingConfig
data class EnvironmentConfig(
val moonPayApiKey: String = "",
@ -31,4 +32,5 @@ data class EnvironmentConfig(
val gaslessTxApiKey: String? = null,
val customerIoCdpApiKey: String? = null,
val surveySparrowToken: String? = null,
val surveySparrowSwapRating: SurveySparrowSwapRatingConfig? = null,
)

View file

@ -11,6 +11,7 @@ import com.tangem.datasource.local.config.environment.generated.GeneratedEnviron
import com.tangem.datasource.local.config.environment.generated.GeneratedEnvironmentConfig.TonCenterApiKey
import com.tangem.datasource.local.config.environment.models.ExpressModel
import com.tangem.datasource.local.config.environment.models.P2PKeys
import com.tangem.datasource.local.config.environment.models.SurveySparrowSwapRatingConfig
/**
* Converts [GeneratedEnvironmentConfig] to [EnvironmentConfig]
@ -54,6 +55,7 @@ internal object GeneratedEnvironmentConfigConverter {
gaslessTxApiKey = GeneratedEnvironmentConfig.gaslessTxApiKey,
customerIoCdpApiKey = GeneratedEnvironmentConfig.CustomerIO.androidApiKey,
surveySparrowToken = GeneratedEnvironmentConfig.SurveySparrow.apiKey,
surveySparrowSwapRating = createSurveySparrowSwapRating(),
)
}
@ -181,4 +183,15 @@ internal object GeneratedEnvironmentConfigConverter {
stellar = GetBlockAccessToken(rest = GetBlockAccessTokens.Stellar.rest),
)
}
private fun createSurveySparrowSwapRating(): SurveySparrowSwapRatingConfig? {
val surveyId = GeneratedEnvironmentConfig.SurveySparrow.SwapRating.surveyId.toLongOrNull()
val ratingQuestionId = GeneratedEnvironmentConfig.SurveySparrow.SwapRating.ratingQuestionId.toLongOrNull()
val feedbackQuestionId = GeneratedEnvironmentConfig.SurveySparrow.SwapRating.feedbackQuestionId.toLongOrNull()
return if (surveyId != null && ratingQuestionId != null && feedbackQuestionId != null) {
SurveySparrowSwapRatingConfig(surveyId, ratingQuestionId, feedbackQuestionId)
} else {
null
}
}
}

View file

@ -2,4 +2,10 @@ package com.tangem.datasource.local.config.environment.models
data class ExpressModel(val apiKey: String, val signVerifierPublicKey: String)
data class P2PKeys(val mainnet: String, val hoodi: String)
data class P2PKeys(val mainnet: String, val hoodi: String)
data class SurveySparrowSwapRatingConfig(
val surveyId: Long,
val ratingQuestionId: Long,
val feedbackQuestionId: Long,
)

View file

@ -87,6 +87,7 @@ class ApiConfigTest {
authProvider = appAuthProvider,
appInfoProvider = mockk(),
)
ApiConfig.ID.SurveySparrow -> SurveySparrow(environmentConfig = environmentConfig)
}
}
}

View file

@ -129,6 +129,7 @@ internal class ProdApiConfigsManagerTest {
authProvider = appAuthProvider,
appInfoProvider = appInfoProvider,
)
ApiConfig.ID.SurveySparrow -> SurveySparrow(environmentConfig = environmentConfig)
}
}
}
@ -146,6 +147,7 @@ internal class ProdApiConfigsManagerTest {
ApiConfig.ID.P2PEthPool -> createP2PModel()
ApiConfig.ID.News -> createNewsModel()
ApiConfig.ID.GaslessTxService -> createGaslessTxServiceModel()
ApiConfig.ID.SurveySparrow -> createSurveySparrowModel()
}
}
@ -320,6 +322,19 @@ internal class ProdApiConfigsManagerTest {
)
}
private fun createSurveySparrowModel(): TestModel {
return TestModel(
id = ApiConfig.ID.SurveySparrow,
expected = ApiEnvironmentConfig(
environment = ApiEnvironment.PROD,
baseUrl = "https://eu-api.surveysparrow.com/",
headers = mapOf(
"Authorization" to ProviderSuspend { "Bearer $SURVEY_SPARROW_API_KEY" },
),
),
)
}
private fun createBlockAidSdkModel(): TestModel {
return TestModel(
id = ApiConfig.ID.BlockAid,
@ -425,6 +440,7 @@ internal class ProdApiConfigsManagerTest {
const val TANGEM_GASLESS_API_KEY = "tangem_gasless_api_key"
const val TANGEM_PAY_BFF_KEY_DEV = "tangem_pay_bff_key_dev"
const val BLOCK_AID_API_KEY = "block_aid_api_key"
const val SURVEY_SPARROW_API_KEY = "survey_sparrow_api_key"
const val EXPRESS_API_KEY = "express_api_key"
const val EXPRESS_DEV_API_KEY = "express_dev_api_key"
const val YIELD_MODULE_KEY = "yield_module_key"
@ -460,6 +476,7 @@ internal class ProdApiConfigsManagerTest {
bffStaticTokenDev = TANGEM_PAY_BFF_KEY_DEV,
gaslessTxApiKeyDev = TANGEM_GASLESS_API_KEY,
gaslessTxApiKey = TANGEM_GASLESS_API_KEY,
surveySparrowToken = SURVEY_SPARROW_API_KEY,
)
}
}