Updated on 2026-08-14

This commit is contained in:
Tangem 2023-06-21 13:25:40 +03:00
parent efe271e1a0
commit b87a0ae96b
18 changed files with 91 additions and 79 deletions

View file

@ -13,6 +13,9 @@
<package name="io.ktor" alias="false" withSubpackages="true" /> <package name="io.ktor" alias="false" withSubpackages="true" />
</value> </value>
</option> </option>
<option name="LINE_BREAK_AFTER_MULTILINE_WHEN_ENTRY" value="false" />
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="5" />
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="3" />
<option name="ALLOW_TRAILING_COMMA" value="true" /> <option name="ALLOW_TRAILING_COMMA" value="true" />
<option name="BLANK_LINES_BEFORE_DECLARATION_WITH_COMMENT_OR_ANNOTATION_ON_SEPARATE_LINE" value="0" /> <option name="BLANK_LINES_BEFORE_DECLARATION_WITH_COMMENT_OR_ANNOTATION_ON_SEPARATE_LINE" value="0" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />

View file

@ -200,6 +200,8 @@ class TapApplication : Application(), ImageLoaderFactory {
appStateHolder.userTokensRepository = userTokensRepository appStateHolder.userTokensRepository = userTokensRepository
appStateHolder.walletStoresManager = walletStoresManager appStateHolder.walletStoresManager = walletStoresManager
// TODO: Try to performance and user experience.
// [REDACTED_JIRA]
runBlocking { runBlocking {
featureTogglesManager.init() featureTogglesManager.init()
learn2ernInteractor.init() learn2ernInteractor.init()

View file

@ -3,5 +3,10 @@ package com.tangem.tap.network.auth
import com.tangem.lib.auth.BasicAuthProvider import com.tangem.lib.auth.BasicAuthProvider
class TangemBasicAuthProvider(private val credentials: String?) : BasicAuthProvider { class TangemBasicAuthProvider(private val credentials: String?) : BasicAuthProvider {
override fun getCredentials(): String? = credentials
override fun getCredentials(): Map<String, String> = if (credentials == null) {
mapOf()
} else {
mapOf("Authorization" to "Basic $credentials")
}
} }

View file

@ -2,6 +2,7 @@ package com.tangem.tap.network.auth.di
import com.tangem.datasource.config.ConfigManager import com.tangem.datasource.config.ConfigManager
import com.tangem.lib.auth.AuthProvider import com.tangem.lib.auth.AuthProvider
import com.tangem.lib.auth.BasicAuthProvider
import com.tangem.tap.network.auth.AuthProviderImpl import com.tangem.tap.network.auth.AuthProviderImpl
import com.tangem.tap.network.auth.TangemBasicAuthProvider import com.tangem.tap.network.auth.TangemBasicAuthProvider
import com.tangem.tap.proxy.AppStateHolder import com.tangem.tap.proxy.AppStateHolder
@ -23,7 +24,7 @@ class AuthModule {
@Provides @Provides
@Singleton @Singleton
fun provideTangemBasicAuthProvider(configManager: ConfigManager): com.tangem.lib.auth.BasicAuthProvider { fun provideTangemBasicAuthProvider(configManager: ConfigManager): BasicAuthProvider {
return TangemBasicAuthProvider(configManager.config.tangemComAuthorization) return TangemBasicAuthProvider(configManager.config.tangemComAuthorization)
} }
} }

View file

@ -4,35 +4,26 @@ import com.tangem.datasource.api.promotion.models.*
import retrofit2.http.* import retrofit2.http.*
/** /**
*
* Promotion API
* @see <a href = "https://www.notion.so/tangem/Promotion-Program-API-0907159c3fdb4975aac761be632f44da">Documentation<a/>
[REDACTED_AUTHOR] [REDACTED_AUTHOR]
* Api details
* https://www.notion.so/tangem/Promotion-Program-API-0907159c3fdb4975aac761be632f44da
*/ */
interface PromotionApi { interface PromotionApi {
@Headers("Cache-Control: max-age=3600")
@Headers("Cache-Control: max-age=$CACHE_CONTROL_SECONDS")
@GET("promotion") @GET("promotion")
suspend fun getPromotionInfo(@Query("programName") name: String): PromotionInfoResponse suspend fun getPromotionInfo(@Query("programName") name: String): PromotionInfoResponse
@POST("promotion/code/validate") @POST("promotion/code/validate")
suspend fun codeValidate(@Body request: CodeValidateRequestBody): CodeValidateResponse suspend fun validateCode(@Body request: CodeValidateRequestBody): CodeValidateResponse
@POST("promotion/code/award") @POST("promotion/code/award")
suspend fun codeAward(@Body request: CodeAwardRequestBody): CodeAwardResponse suspend fun requestAwardByCode(@Body request: CodeAwardRequestBody): CodeAwardResponse
@POST("promotion/validate") @POST("promotion/validate")
suspend fun validate(@Body request: ValidateRequestBody): ValidateResponse suspend fun validate(@Body request: ValidateRequestBody): ValidateResponse
@GET("promotion/award") @GET("promotion/award")
suspend fun award(@Body request: AwardRequestBody): AwardResponse suspend fun requestAward(@Body request: AwardRequestBody): AwardResponse
companion object {
const val ONE_INCH_TIMEOUT_MS = 5000L
const val CACHE_CONTROL_SECONDS = 3600
}
} }

View file

@ -14,7 +14,5 @@ abstract class AbstractPromotionResponse {
data class Error( data class Error(
@Json(name = "code") val code: Int, @Json(name = "code") val code: Int,
@Json(name = "description") val description: String, @Json(name = "description") val description: String,
) { )
companion object
}
} }

View file

@ -66,10 +66,10 @@ class NetworkModule {
AuthenticationHeader(authProvider), AuthenticationHeader(authProvider),
) )
.allowLogging() .allowLogging()
.callTimeout(PromotionApi.ONE_INCH_TIMEOUT_MS, TimeUnit.MILLISECONDS) .callTimeout(API_ONE_INCH_TIMEOUT_MS, TimeUnit.MILLISECONDS)
.connectTimeout(PromotionApi.ONE_INCH_TIMEOUT_MS, TimeUnit.MILLISECONDS) .connectTimeout(API_ONE_INCH_TIMEOUT_MS, TimeUnit.MILLISECONDS)
.readTimeout(PromotionApi.ONE_INCH_TIMEOUT_MS, TimeUnit.MILLISECONDS) .readTimeout(API_ONE_INCH_TIMEOUT_MS, TimeUnit.MILLISECONDS)
.writeTimeout(PromotionApi.ONE_INCH_TIMEOUT_MS, TimeUnit.MILLISECONDS) .writeTimeout(API_ONE_INCH_TIMEOUT_MS, TimeUnit.MILLISECONDS)
.build() .build()
return createBasePromotionRetrofit(okClient, moshi) return createBasePromotionRetrofit(okClient, moshi)
} }
@ -90,5 +90,6 @@ class NetworkModule {
const val PROMOTION_BASE_URL = "https://tangem.com/v1/" const val PROMOTION_BASE_URL = "https://tangem.com/v1/"
const val PAYMENTOLOGY_BASE_URL: String = "https://paymentologygate.oa.r.appspot.com/" const val PAYMENTOLOGY_BASE_URL: String = "https://paymentologygate.oa.r.appspot.com/"
const val API_ONE_INCH_TIMEOUT_MS = 5000L
} }
} }

View file

@ -6,6 +6,8 @@ import com.tangem.datasource.api.promotion.models.*
import com.tangem.feature.learn2earn.data.api.Learn2earnPreferenceStorage import com.tangem.feature.learn2earn.data.api.Learn2earnPreferenceStorage
import com.tangem.feature.learn2earn.data.api.Learn2earnRepository import com.tangem.feature.learn2earn.data.api.Learn2earnRepository
import com.tangem.feature.learn2earn.data.api.UsedCardsPreferenceStorage import com.tangem.feature.learn2earn.data.api.UsedCardsPreferenceStorage
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
/** /**
@ -16,6 +18,7 @@ internal class DefaultLearn2earnRepository(
private val usedCardsPreferenceStorage: UsedCardsPreferenceStorage, private val usedCardsPreferenceStorage: UsedCardsPreferenceStorage,
private val moshi: Moshi, private val moshi: Moshi,
private val api: PromotionApi, private val api: PromotionApi,
private val dispatchers: AppCoroutineDispatcherProvider,
) : Learn2earnRepository { ) : Learn2earnRepository {
private val promotionInfoAdapter = moshi.adapter(PromotionInfoResponse::class.java) private val promotionInfoAdapter = moshi.adapter(PromotionInfoResponse::class.java)
@ -40,50 +43,47 @@ internal class DefaultLearn2earnRepository(
return preferencesStorage.alreadyReceivedAward return preferencesStorage.alreadyReceivedAward
} }
override suspend fun requestAwardByCode(walletId: String, address: String): Result<Boolean> {
return runCatching {
api.codeAward(CodeAwardRequestBody(walletId, address, getPromoCode()))
}.fold(
onSuccess = { response ->
val alreadyReceived = response.status ?: false
preferencesStorage.alreadyReceivedAward = alreadyReceived
Result.success(alreadyReceived)
},
onFailure = { exception -> Result.failure(exception) },
)
}
override suspend fun getPromotionInfo(): Result<PromotionInfoResponse> { override suspend fun getPromotionInfo(): Result<PromotionInfoResponse> {
val info = restorePromotionInfo() return withContext(dispatchers.io) {
val info = restorePromotionInfo()
return if (info?.status == PromotionInfoResponse.Status.FINISHED) { if (info?.status == PromotionInfoResponse.Status.FINISHED) {
Result.success(info) Result.success(info)
} else { } else {
runCatching { api.getPromotionInfo(getProgramName()) } runCatching { api.getPromotionInfo(getProgramName()) }
.fold( .fold(
onSuccess = { infoResponse -> onSuccess = { infoResponse ->
savePromotionInfo(infoResponse) savePromotionInfo(infoResponse)
Result.success(infoResponse) Result.success(infoResponse)
}, },
onFailure = { exception -> Result.failure(exception) }, onFailure = { exception -> Result.failure(exception) },
) )
}
} }
} }
override suspend fun validate(walletId: String): ValidateResponse { override suspend fun validate(walletId: String): ValidateResponse {
return api.validate(ValidateRequestBody(walletId, getProgramName())) return withContext(dispatchers.io) {
api.validate(ValidateRequestBody(walletId, getProgramName()))
}
} }
override suspend fun award(walletId: String): AwardResponse { override suspend fun requestAward(walletId: String): AwardResponse {
return api.award(AwardRequestBody(walletId, getProgramName())) return withContext(dispatchers.io) {
api.requestAward(AwardRequestBody(walletId, getProgramName()))
}
} }
override suspend fun codeValidate(walletId: String, code: String?): CodeValidateResponse { override suspend fun validateCode(walletId: String, code: String?): CodeValidateResponse {
return api.codeValidate(CodeValidateRequestBody(walletId, code)) return withContext(dispatchers.io) {
api.validateCode(CodeValidateRequestBody(walletId, code))
}
} }
override suspend fun codeAward(walletId: String, address: String, code: String?): CodeAwardResponse { override suspend fun requestAwardByCode(walletId: String, address: String, code: String?): CodeAwardResponse {
return api.codeAward(CodeAwardRequestBody(walletId, address, code)) return withContext(dispatchers.io) {
api.requestAwardByCode(CodeAwardRequestBody(walletId, address, code))
}
} }
private fun restorePromotionInfo(): PromotionInfoResponse? { private fun restorePromotionInfo(): PromotionInfoResponse? {
@ -101,7 +101,6 @@ internal class DefaultLearn2earnRepository(
preferencesStorage.promotionInfo = promotionInfoAdapter.toJson(infoResponse) preferencesStorage.promotionInfo = promotionInfoAdapter.toJson(infoResponse)
} catch (ex: Exception) { } catch (ex: Exception) {
Timber.e(ex) Timber.e(ex)
preferencesStorage.promotionInfo = null
} }
} }

View file

@ -15,6 +15,8 @@ internal class DefaultPreferenceStorage(
) : SimpleKrate(context = context, name = "Lear2earnPromotion"), Learn2earnPreferenceStorage { ) : SimpleKrate(context = context, name = "Lear2earnPromotion"), Learn2earnPreferenceStorage {
override var promoCode: String? by stringPref().withDefault(null) override var promoCode: String? by stringPref().withDefault(null)
override var promotionInfo: String? by stringPref().withDefault(null) override var promotionInfo: String? by stringPref().withDefault(null)
override var alreadyReceivedAward: Boolean by booleanPref().withDefault(false) override var alreadyReceivedAward: Boolean by booleanPref().withDefault(false)
} }

View file

@ -4,7 +4,10 @@ package com.tangem.feature.learn2earn.data.api
[REDACTED_AUTHOR] [REDACTED_AUTHOR]
*/ */
interface Learn2earnPreferenceStorage { interface Learn2earnPreferenceStorage {
var promoCode: String? var promoCode: String?
var promotionInfo: String? var promotionInfo: String?
var alreadyReceivedAward: Boolean var alreadyReceivedAward: Boolean
} }

View file

@ -6,15 +6,24 @@ import com.tangem.datasource.api.promotion.models.*
[REDACTED_AUTHOR] [REDACTED_AUTHOR]
*/ */
interface Learn2earnRepository { interface Learn2earnRepository {
fun isHadActivatedCards(): Boolean fun isHadActivatedCards(): Boolean
fun getPromoCode(): String? fun getPromoCode(): String?
fun savePromoCode(code: String) fun savePromoCode(code: String)
fun getProgramName(): String fun getProgramName(): String
fun isAlreadyReceivedAward(): Boolean fun isAlreadyReceivedAward(): Boolean
suspend fun requestAwardByCode(walletId: String, address: String): Result<Boolean>
suspend fun getPromotionInfo(): Result<PromotionInfoResponse> suspend fun getPromotionInfo(): Result<PromotionInfoResponse>
suspend fun validate(walletId: String): ValidateResponse suspend fun validate(walletId: String): ValidateResponse
suspend fun award(walletId: String): AwardResponse
suspend fun codeValidate(walletId: String, code: String?): CodeValidateResponse suspend fun requestAward(walletId: String): AwardResponse
suspend fun codeAward(walletId: String, address: String, code: String?): CodeAwardResponse
suspend fun validateCode(walletId: String, code: String?): CodeValidateResponse
suspend fun requestAwardByCode(walletId: String, address: String, code: String?): CodeAwardResponse
} }

View file

@ -12,6 +12,7 @@ import com.tangem.feature.learn2earn.data.DefaultUsedCardsPreferenceStorage
import com.tangem.feature.learn2earn.data.api.Learn2earnPreferenceStorage import com.tangem.feature.learn2earn.data.api.Learn2earnPreferenceStorage
import com.tangem.feature.learn2earn.data.api.Learn2earnRepository import com.tangem.feature.learn2earn.data.api.Learn2earnRepository
import com.tangem.feature.learn2earn.data.api.UsedCardsPreferenceStorage import com.tangem.feature.learn2earn.data.api.UsedCardsPreferenceStorage
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
@ -44,12 +45,14 @@ class Learn2earnDataModule {
usedCardsPreferenceStorage: UsedCardsPreferenceStorage, usedCardsPreferenceStorage: UsedCardsPreferenceStorage,
@NetworkMoshi moshi: Moshi, @NetworkMoshi moshi: Moshi,
@PromotionOneInch promotionApi: PromotionApi, @PromotionOneInch promotionApi: PromotionApi,
dispatchers: AppCoroutineDispatcherProvider,
): Learn2earnRepository { ): Learn2earnRepository {
return DefaultLearn2earnRepository( return DefaultLearn2earnRepository(
preferencesStorage = preferenceStorage, preferencesStorage = preferenceStorage,
usedCardsPreferenceStorage = usedCardsPreferenceStorage, usedCardsPreferenceStorage = usedCardsPreferenceStorage,
moshi = moshi, moshi = moshi,
api = promotionApi, api = promotionApi,
dispatchers = dispatchers,
) )
} }
} }

View file

@ -89,8 +89,6 @@ class DefaultLearn2earnInteractor(
} }
private suspend fun initPromotionInfo() { private suspend fun initPromotionInfo() {
// return Promotion.dummyActive()
promotion = repository.getPromotionInfo() promotion = repository.getPromotionInfo()
.fold( .fold(
onSuccess = { response -> onSuccess = { response ->

View file

@ -42,9 +42,7 @@ class WebViewUriBuilder(
} }
fun getBasicAuthHeaders(): Map<String, String> { fun getBasicAuthHeaders(): Map<String, String> {
return basicAuthProvider.getCredentials()?.let { return basicAuthProvider.getCredentials()
mapOf("Authorization" to "Basic $it")
} ?: mapOf()
} }
fun isPromoCodeRedirect(uri: Uri): Boolean { fun isPromoCodeRedirect(uri: Uri): Boolean {

View file

@ -9,10 +9,16 @@ import com.tangem.feature.learn2earn.domain.models.RedirectConsequences
interface Learn2earnInteractor { interface Learn2earnInteractor {
suspend fun init() suspend fun init()
fun isNeedToShowViewOnStoriesScreen(): Boolean fun isNeedToShowViewOnStoriesScreen(): Boolean
suspend fun isNeedToShowViewOnWalletScreen(walletId: String): Boolean suspend fun isNeedToShowViewOnWalletScreen(walletId: String): Boolean
fun getBasicAuthHeaders(): Map<String, String> fun getBasicAuthHeaders(): Map<String, String>
fun buildUriForStories(): Uri fun buildUriForStories(): Uri
fun buildUriForMainPage(walletId: String, cardId: String, cardPubKey: String): Uri fun buildUriForMainPage(walletId: String, cardId: String, cardPubKey: String): Uri
fun handleWebViewRedirect(uri: Uri): RedirectConsequences fun handleWebViewRedirect(uri: Uri): RedirectConsequences
} }

View file

@ -6,7 +6,6 @@ import com.tangem.feature.learn2earn.domain.DefaultLearn2earnInteractor
import com.tangem.feature.learn2earn.domain.api.Learn2earnInteractor import com.tangem.feature.learn2earn.domain.api.Learn2earnInteractor
import com.tangem.feature.learn2earn.presentation.Learn2earnRouter import com.tangem.feature.learn2earn.presentation.Learn2earnRouter
import com.tangem.lib.auth.BasicAuthProvider import com.tangem.lib.auth.BasicAuthProvider
import com.tangem.utils.extensions.toWeakReference
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
@ -21,7 +20,7 @@ class Learn2earnDomainModule {
@Provides @Provides
@Singleton @Singleton
fun provideRouter(@ApplicationContext context: Context): Learn2earnRouter { fun provideRouter(@ApplicationContext context: Context): Learn2earnRouter {
return Learn2earnRouter(context.toWeakReference()) return Learn2earnRouter(context)
} }
@Provides @Provides
@ -33,6 +32,7 @@ class Learn2earnDomainModule {
return DefaultLearn2earnInteractor( return DefaultLearn2earnInteractor(
repository = repository, repository = repository,
basicAuthProvider = basicAuthProvider, basicAuthProvider = basicAuthProvider,
// TODO: 1inch: paste locale provider instead of hardcode
userCountryCodeProvider = { "ru" }, userCountryCodeProvider = { "ru" },
) )
} }

View file

@ -2,22 +2,15 @@ package com.tangem.feature.learn2earn.presentation
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import timber.log.Timber
import java.lang.ref.WeakReference
/** /**
[REDACTED_AUTHOR] [REDACTED_AUTHOR]
*/ */
class Learn2earnRouter( class Learn2earnRouter(
private val wContext: WeakReference<Context>, private val context: Context,
) { ) {
fun openWebView() { fun openWebView() {
val context = wContext.get() context.startActivity(Intent(context, Learn2earnWebViewActivity::class.java))
if (context == null) {
Timber.e("Can't open the Learn2earnWebViewActivity")
} else {
context.startActivity(Intent(context, Learn2earnWebViewActivity::class.java))
}
} }
} }

View file

@ -5,5 +5,5 @@ package com.tangem.lib.auth
*/ */
interface BasicAuthProvider { interface BasicAuthProvider {
fun getCredentials(): String? fun getCredentials(): Map<String, String>
} }