Updated on 2026-08-14

This commit is contained in:
Tangem 2023-07-07 16:55:06 +03:00
parent baf5a717e6
commit bfd2daa0b8
13 changed files with 236 additions and 76 deletions

View file

@ -10,6 +10,7 @@ plugins {
dependencies {
/** Core modules */
implementation(project(":common"))
implementation(project(":core:analytics"))
implementation(project(":core:featuretoggles"))
implementation(project(":core:datasource"))
implementation(project(":core:utils"))

View file

@ -0,0 +1,70 @@
package com.tangem.feature.learn2earn.analytics
import com.tangem.core.analytics.AnalyticsEvent
sealed class Learn2earnEvents(
category: String,
event: String,
params: Map<String, String> = mapOf(),
) : AnalyticsEvent(
category = category,
event = event,
params = params + mapOf(
"Program Name" to "1inch",
),
) {
sealed class IntroductionProcess(
event: String,
params: Map<String, String> = mapOf(),
) : Learn2earnEvents(
category = "Introduction Process",
event = event,
params = params,
) {
class ButtonLearn : IntroductionProcess("Button - Learn")
}
sealed class MainScreen(
event: String,
params: Map<String, String> = mapOf(),
) : Learn2earnEvents(
category = "Main Screen",
event = event,
params = params,
) {
class NoticeLear2earn(clientType: AnalyticsParam.ClientType) : MainScreen(
event = "Notice - Learn&Earn",
params = mapOf(AnalyticsParam.CLIENT_TYPE to clientType.value),
)
class NoticeClaimSuccess : MainScreen("Notice - Claim Successed")
}
sealed class PromoScreen(
event: String,
params: Map<String, String> = mapOf(),
) : Learn2earnEvents(
category = "Promo Screen",
event = event,
params = params,
) {
class ButtonBuy : PromoScreen("Button - Buy")
class SuccessScreenOpened(clientType: AnalyticsParam.ClientType) : PromoScreen(
event = "Success Screen Opened",
params = mapOf(AnalyticsParam.CLIENT_TYPE to clientType.value),
)
}
}
sealed class AnalyticsParam {
sealed class ClientType(val value: String) {
object New : ClientType("New")
object Old : ClientType("Old")
}
companion object Key {
const val CLIENT_TYPE = "Client type"
}
}

View file

@ -1,6 +1,7 @@
package com.tangem.feature.learn2earn.domain
import android.net.Uri
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.datasource.api.promotion.models.AbstractPromotionResponse
import com.tangem.datasource.api.promotion.models.PromotionInfoResponse
import com.tangem.feature.learn2earn.data.api.Learn2earnRepository
@ -17,11 +18,12 @@ import com.tangem.lib.crypto.models.Currency
/**
[REDACTED_AUTHOR]
*/
class DefaultLearn2earnInteractor(
internal class DefaultLearn2earnInteractor(
private val featureToggleManager: Learn2earnFeatureToggleManager,
private val repository: Learn2earnRepository,
private val userWalletManager: UserWalletManager,
private val derivationManager: DerivationManager,
private val analyticsEventHandler: AnalyticsEventHandler,
dependencyProvider: Learn2earnDependencyProvider,
) : Learn2earnInteractor {
@ -29,11 +31,17 @@ class DefaultLearn2earnInteractor(
private lateinit var promotion: Promotion
private val webViewUriBuilder: WebViewUriBuilder = WebViewUriBuilder(
authCredentialsProvider = dependencyProvider.getWebViewAuthCredentialsProvider(),
userCountryCodeProvider = dependencyProvider.getUserCountryCodeProvider(),
promoCodeProvider = { repository.getUserData().promoCode },
)
private val webViewUriBuilder: WebViewUriBuilder by lazy {
WebViewUriBuilder(
authCredentialsProvider = dependencyProvider.getWebViewAuthCredentialsProvider(),
userCountryCodeProvider = dependencyProvider.getUserCountryCodeProvider(),
promoCodeProvider = { repository.getUserData().promoCode },
)
}
private val webViewUriParser: WebViewUriParser by lazy {
WebViewUriParser { repository.getProgramName() }
}
override suspend fun init() {
initPromotionInfo()
@ -183,27 +191,28 @@ class DefaultLearn2earnInteractor(
return HeadersConverter().convert(webViewUriBuilder.getBasicAuthHeaders())
}
override fun handleRedirect(uri: Uri): RedirectConsequences {
if (webViewUriBuilder.isReadyForExistedCardAwardRedirect(uri)) {
updateUserData { it.copy(isRegisteredInPromotion = true) }
webViewResultHandler?.handleResult(WebViewResult.ReadyForAward)
return RedirectConsequences.FINISH_SESSION
}
return if (webViewUriBuilder.isPromoCodeRedirect(uri)) {
webViewUriBuilder.extractPromoCode(uri)?.let { code ->
override fun handleRedirect(uri: Uri): WebViewAction {
val result = webViewUriParser.parse(uri)
when (result) {
is WebViewResult.PromoCode -> {
updateUserData {
it.copy(
promoCode = code,
promoCode = result.promoCode,
isRegisteredInPromotion = true,
)
}
webViewResultHandler?.handleResult(WebViewResult.PromoCodeReceived)
}
RedirectConsequences.NOTHING
} else {
RedirectConsequences.PROCEED
WebViewResult.ReadyForAward -> {
updateUserData { it.copy(isRegisteredInPromotion = true) }
}
is WebViewResult.AnalyticsEvent -> {
analyticsEventHandler.send(result.event)
}
else -> Unit
}
webViewResultHandler?.handleResult(result)
return result.toWebViewAction()
}
private fun promotionIsActive(): Boolean {
@ -285,4 +294,13 @@ private fun Promotion.PromotionInfo.getData(promoCode: String?): PromotionInfoRe
} else {
oldCard
}
}
private fun WebViewResult.toWebViewAction(): WebViewAction {
return when (this) {
WebViewResult.Empty -> WebViewAction.PROCEED
WebViewResult.ReadyForAward -> WebViewAction.FINISH_SESSION
is WebViewResult.AnalyticsEvent -> WebViewAction.NOTHING
is WebViewResult.PromoCode -> WebViewAction.NOTHING
}
}

View file

@ -39,41 +39,15 @@ internal class WebViewUriBuilder(
}
}
fun isPromoCodeRedirect(uri: Uri): Boolean {
return uri.toString().contains(SUFFIX_CODE_CREATED)
}
fun isReadyForExistedCardAwardRedirect(uri: Uri): Boolean {
return uri.toString().endsWith(SUFFIX_READY_FOR_AWARD)
}
fun extractPromoCode(uri: Uri): String? {
val url = uri.toString()
return if (isPromoCodeRedirect(uri)) {
val replaceString = makeWebViewUriBuilder().build().toString() + SUFFIX_CODE_CREATED
val code = url.replace(replaceString, "")
code
} else {
null
}
}
private fun makeWebViewUriBuilder(): Uri.Builder {
val builder = Uri.Builder()
builder.scheme(SCHEME)
private fun makeWebViewUriBuilder(): Uri.Builder = Uri.Builder().apply {
scheme(SCHEME)
if (BuildConfig.DEBUG) {
builder.authority(DEV_BASE_URL)
builder.appendPath(userCountryCodeProvider.invoke())
builder.appendPath(DEV_PATH_PROMOTION)
authority(DEV_BASE_URL)
} else {
builder.authority(BASE_URL)
builder.appendPath(userCountryCodeProvider.invoke())
builder.appendPath(PATH_PROMOTION)
authority(BASE_URL)
}
return builder
appendPath(userCountryCodeProvider.invoke())
appendPath(PATH_PROMOTION)
}
private companion object {
@ -86,9 +60,5 @@ internal class WebViewUriBuilder(
const val QUERY_EXISTED_CARD = "existed-card"
const val DEV_BASE_URL = "devweb.tangem.com"
const val DEV_PATH_PROMOTION = "promotion-test"
const val SUFFIX_CODE_CREATED = "/code-created?code="
const val SUFFIX_READY_FOR_AWARD = "/ready-for-existed-card-award"
}
}

View file

@ -0,0 +1,91 @@
package com.tangem.feature.learn2earn.domain
import android.net.Uri
import com.tangem.feature.learn2earn.analytics.AnalyticsParam
import com.tangem.feature.learn2earn.analytics.Learn2earnEvents
import com.tangem.feature.learn2earn.analytics.Learn2earnEvents.PromoScreen
import com.tangem.feature.learn2earn.domain.api.WebViewResult
/**
[REDACTED_AUTHOR]
*/
internal class WebViewUriParser(
private val programNameProvider: () -> String,
) {
fun parse(uri: Uri): WebViewResult = when {
isAnalyticsRedirect(uri) -> {
val event = extractAnalyticsEvent(uri)
if (event == null) {
WebViewResult.Empty
} else {
WebViewResult.AnalyticsEvent(event)
}
}
isPromoCodeRedirect(uri) -> {
val promoCode = extractPromoCode(uri)
if (promoCode == null) {
WebViewResult.Empty
} else {
WebViewResult.PromoCode(promoCode)
}
}
isReadyForAwardRedirect(uri) -> {
WebViewResult.ReadyForAward
}
else -> WebViewResult.Empty
}
private fun isReadyForAwardRedirect(uri: Uri): Boolean {
return uri.lastPathSegment == PATH_READY_FOR_AWARD
}
private fun isPromoCodeRedirect(uri: Uri): Boolean {
return uri.lastPathSegment == PATH_PROMO_CODE_CREATED &&
uri.queryParameterNames.contains(QUERY_PROMO_CODE)
}
private fun extractPromoCode(uri: Uri): String? {
return if (isPromoCodeRedirect(uri)) {
uri.getQueryParameter(QUERY_PROMO_CODE)
} else {
null
}
}
private fun isAnalyticsRedirect(uri: Uri): Boolean {
return uri.lastPathSegment == PATH_ANALYTICS &&
uri.queryParameterNames.contains(QUERY_ANALYTICS_EVENT)
}
private fun extractAnalyticsEvent(uri: Uri): Learn2earnEvents? {
if (!isAnalyticsRedirect(uri)) return null
val programName = uri.getQueryParameter(QUERY_PROGRAM_NAME) ?: return null
if (programName != programNameProvider.invoke()) return null
val event = uri.getQueryParameter(QUERY_ANALYTICS_EVENT) ?: return null
val analyticsEvent = when (event) {
EVENT_PROMO_BUY -> PromoScreen.ButtonBuy()
EVENT_PROMO_SUCCESS_NEW_USER -> PromoScreen.SuccessScreenOpened(AnalyticsParam.ClientType.New)
EVENT_PROMO_SUCCESS_OLD_USER -> PromoScreen.SuccessScreenOpened(AnalyticsParam.ClientType.Old)
else -> null
}
return analyticsEvent
}
private companion object {
const val PATH_PROMO_CODE_CREATED = "code-created"
const val PATH_READY_FOR_AWARD = "ready-for-existed-card-award"
const val PATH_ANALYTICS = "analytics"
const val QUERY_PROMO_CODE = "code"
const val QUERY_ANALYTICS_EVENT = "event"
const val QUERY_PROGRAM_NAME = "programName"
const val EVENT_PROMO_BUY = "promotion-buy"
const val EVENT_PROMO_SUCCESS_NEW_USER = "promotion-success-new-user"
const val EVENT_PROMO_SUCCESS_OLD_USER = "promotion-success-old-user"
}
}

View file

@ -9,10 +9,10 @@ import android.net.Uri
[REDACTED_AUTHOR]
*/
interface WebViewRedirectHandler {
fun handleRedirect(uri: Uri): RedirectConsequences
fun handleRedirect(uri: Uri): WebViewAction
}
enum class RedirectConsequences {
enum class WebViewAction {
NOTHING,
PROCEED,
FINISH_SESSION,

View file

@ -1,5 +1,7 @@
package com.tangem.feature.learn2earn.domain.api
import com.tangem.feature.learn2earn.analytics.Learn2earnEvents
/**
* Handler that helps determine a result of the Learn2earnWebViewActivity webView actions.
*
@ -10,6 +12,8 @@ interface WebViewResultHandler {
}
sealed class WebViewResult {
object PromoCodeReceived : WebViewResult()
object Empty : WebViewResult()
data class PromoCode(val promoCode: String) : WebViewResult()
object ReadyForAward : WebViewResult()
data class AnalyticsEvent(val event: Learn2earnEvents) : WebViewResult()
}

View file

@ -1,6 +1,7 @@
package com.tangem.feature.learn2earn.domain.di
import android.content.Context
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
import com.tangem.feature.learn2earn.data.api.Learn2earnRepository
import com.tangem.feature.learn2earn.data.toggles.DefaultLearn2earnFeatureToggleManager
@ -21,7 +22,7 @@ import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
class Learn2earnDomainModule {
internal class Learn2earnDomainModule {
@Provides
@Singleton
@ -42,6 +43,7 @@ class Learn2earnDomainModule {
repository: Learn2earnRepository,
dependencyProvider: Learn2earnDependencyProvider,
userWalletManager: UserWalletManager,
analyticsEventHandler: AnalyticsEventHandler,
derivationManager: DerivationManager,
): Learn2earnInteractor {
return DefaultLearn2earnInteractor(
@ -49,6 +51,7 @@ class Learn2earnDomainModule {
repository = repository,
userWalletManager = userWalletManager,
derivationManager = derivationManager,
analyticsEventHandler = analyticsEventHandler,
dependencyProvider = dependencyProvider,
)
}

View file

@ -5,7 +5,7 @@ import com.tangem.datasource.api.promotion.models.PromotionInfoResponse
/**
[REDACTED_AUTHOR]
*/
data class Promotion(
internal data class Promotion(
val info: PromotionInfo?,
val error: PromotionError?,
) {

View file

@ -5,6 +5,9 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.feature.learn2earn.analytics.AnalyticsParam
import com.tangem.feature.learn2earn.analytics.Learn2earnEvents
import com.tangem.feature.learn2earn.domain.api.Learn2earnInteractor
import com.tangem.feature.learn2earn.domain.api.WebViewResult
import com.tangem.feature.learn2earn.domain.api.WebViewResultHandler
@ -25,6 +28,7 @@ class Learn2earnViewModel @Inject constructor(
private val interactor: Learn2earnInteractor,
private val router: Learn2earnRouter,
private val dispatchers: AppCoroutineDispatcherProvider,
private val analytics: AnalyticsEventHandler,
) : ViewModel() {
var uiState: Learn2earnState by mutableStateOf(
@ -63,16 +67,19 @@ class Learn2earnViewModel @Inject constructor(
}
private fun onButtonStoryClick() {
analytics.send(Learn2earnEvents.IntroductionProcess.ButtonLearn())
router.openWebView(interactor.buildUriForNewUser(), interactor.getBasicAuthHeaders())
}
private fun onButtonMainClick() {
if (!interactor.isUserHadPromoCode() && !interactor.isUserRegisteredInPromotion()) {
analytics.send(Learn2earnEvents.MainScreen.NoticeLear2earn(AnalyticsParam.ClientType.New))
subscribeToWebViewResultEvents()
router.openWebView(interactor.buildUriForOldUser(), interactor.getBasicAuthHeaders())
return
}
analytics.send(Learn2earnEvents.MainScreen.NoticeLear2earn(AnalyticsParam.ClientType.Old))
viewModelScope.launch(dispatchers.io) {
updateUi { uiState.updateProgress(showProgress = true) }
@ -80,6 +87,7 @@ class Learn2earnViewModel @Inject constructor(
.onSuccess { requestAwardResult ->
requestAwardResult.fold(
onSuccess = {
analytics.send(Learn2earnEvents.MainScreen.NoticeClaimSuccess())
val onHideDialog = {
uiState = uiState.updateGetBonusVisibility(isVisible = false)
.hideDialog()
@ -166,7 +174,7 @@ class Learn2earnViewModel @Inject constructor(
interactor.webViewResultHandler = null
when (result) {
WebViewResult.ReadyForAward -> updateMainScreenViews()
WebViewResult.PromoCodeReceived -> Unit
else -> Unit
}
}
}

View file

@ -38,7 +38,7 @@ class Learn2earnWebViewActivity : AppCompatActivity() {
webView.settings.loadsImagesAutomatically = true
webView.webViewClient = Learn2earnWebViewClient(
helper = viewModel,
redirectHandler = viewModel,
headers = webViewData.headers,
finishSessionHandler = { finish() },
)

View file

@ -3,28 +3,28 @@ package com.tangem.feature.learn2earn.presentation.webView
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import com.tangem.feature.learn2earn.domain.api.RedirectConsequences
import com.tangem.feature.learn2earn.domain.api.WebViewAction
import com.tangem.feature.learn2earn.domain.api.WebViewRedirectHandler
import timber.log.Timber
internal class Learn2earnWebViewClient(
private val helper: WebViewRedirectHandler,
private val redirectHandler: WebViewRedirectHandler,
private val headers: Map<String, String>,
private val finishSessionHandler: () -> Unit,
) : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
Timber.d("url: ${request.url}")
when (helper.handleRedirect(request.url)) {
RedirectConsequences.PROCEED -> {
when (redirectHandler.handleRedirect(request.url)) {
WebViewAction.NOTHING -> Unit
WebViewAction.PROCEED -> {
val mergedHeaders = headers.toMutableMap()
request.requestHeaders?.let { mergedHeaders.putAll(it) }
view.loadUrl(request.url.toString(), mergedHeaders)
}
RedirectConsequences.FINISH_SESSION -> {
WebViewAction.FINISH_SESSION -> {
finishSessionHandler.invoke()
}
else -> Unit
}
return true
}

View file

@ -4,7 +4,6 @@ import android.content.Intent
import android.net.Uri
import androidx.lifecycle.ViewModel
import com.tangem.feature.learn2earn.domain.HeadersConverter
import com.tangem.feature.learn2earn.domain.api.RedirectConsequences
import com.tangem.feature.learn2earn.domain.api.WebViewRedirectHandler
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@ -14,12 +13,8 @@ import javax.inject.Inject
*/
@HiltViewModel
internal class WebViewViewModel @Inject constructor(
private val handler: WebViewRedirectHandler,
) : ViewModel(), WebViewRedirectHandler {
override fun handleRedirect(uri: Uri): RedirectConsequences {
return handler.handleRedirect(uri)
}
handler: WebViewRedirectHandler,
) : ViewModel(), WebViewRedirectHandler by handler {
fun extractData(intent: Intent): WebViewData {
val uriString = requireNotNull(intent.getStringExtra(Learn2earnWebViewActivity.EXTRA_WEB_URI)) {