Updated on 2026-08-14

This commit is contained in:
Tangem 2023-11-21 13:15:39 +04:00
parent 975e404db7
commit 2657cf5b4c
50 changed files with 6 additions and 2590 deletions

View file

@ -79,8 +79,6 @@ dependencies {
/** Features */
implementation(projects.features.onboarding)
implementation(projects.features.learn2earn.api)
implementation(projects.features.learn2earn.impl)
implementation(projects.features.referral.presentation)
implementation(projects.features.referral.domain)
implementation(projects.features.referral.data)

View file

@ -141,10 +141,6 @@
android:name="com.tangem.tap.common.qrCodeScan.ScanQrCodeActivity"
android:theme="@style/AppTheme" />
<activity
android:name="com.tangem.feature.learn2earn.presentation.webView.Learn2earnWebViewActivity"
android:theme="@style/AppTheme" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"

View file

@ -110,9 +110,6 @@ internal class TapApplication : Application(), ImageLoaderFactory {
@Inject
lateinit var walletConnectSessionsRepository: WalletConnectSessionsRepository
// @Inject
// lateinit var learn2earnInteractor: Learn2earnInteractor
@Inject
lateinit var manageTokensFeatureToggles: ManageTokensFeatureToggles
@ -184,7 +181,6 @@ internal class TapApplication : Application(), ImageLoaderFactory {
runBlocking {
initUserWalletsListManager()
featureTogglesManager.init()
// learn2earnInteractor.init()
}
val configLoader = FeaturesLocalLoader(assetReader, MoshiConverter.sdkMoshi, BuildConfig.ENVIRONMENT)

View file

@ -34,8 +34,6 @@ class HomeFragment : ComposeFragment(), StoreSubscriber<HomeState> {
private var homeState: MutableState<HomeState> = mutableStateOf(store.state.homeState)
// private val learn2earnViewModel by activityViewModels<Learn2earnViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
store.dispatch(HomeAction.OnCreate)
@ -44,11 +42,6 @@ class HomeFragment : ComposeFragment(), StoreSubscriber<HomeState> {
@Composable
override fun ScreenContent(modifier: Modifier) {
// sync adding story before screen creation
// if (learn2earnViewModel.uiState.storyScreenState.isVisible) {
// store.dispatch(HomeAction.InsertStory(position = 0, Stories.OneInchPromo))
// homeState.value = store.state.homeState
// }
BackHandler(onBack = requireActivity()::finish)
SystemBarsEffect {
setSystemBarsColor(color = Color.Transparent, darkIcons = false)
@ -83,7 +76,6 @@ class HomeFragment : ComposeFragment(), StoreSubscriber<HomeState> {
private fun ScreenContent() {
StoriesScreen(
homeState = homeState,
onLearn2earnClick = {}, // learn2earnViewModel.uiState.storyScreenState.onClick,
onScanButtonClick = {
Analytics.send(IntroductionProcess.ButtonScanCard())
store.dispatch(action = HomeAction.ReadCard(scope = requireActivity().lifecycleScope))

View file

@ -20,7 +20,6 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.learn2earn.presentation.ui.Learn2earnStoriesScreen
import com.tangem.tap.features.home.compose.content.*
import com.tangem.tap.features.home.compose.views.HomeButtons
import com.tangem.tap.features.home.compose.views.SearchCurrenciesButton
@ -33,7 +32,6 @@ import kotlin.math.max
@Composable
fun StoriesScreen(
homeState: MutableState<HomeState>,
onLearn2earnClick: () -> Unit,
onScanButtonClick: () -> Unit,
onShopButtonClick: () -> Unit,
onSearchTokensClick: () -> Unit,
@ -65,7 +63,6 @@ fun StoriesScreen(
isScanInProgress = homeState.value.scanInProgress,
onGoToPreviousStory = goToPreviousStory,
onGoToNextStory = goToNextStory,
onLearn2earnClick = onLearn2earnClick,
onSearchTokensClick = onSearchTokensClick,
onScanButtonClick = onScanButtonClick,
onShopButtonClick = onShopButtonClick,
@ -152,7 +149,6 @@ private fun StoriesScreenContent(config: StoriesScreenContentConfig, modifier: M
.align(Alignment.Start),
)
when (config.currentStory) {
Stories.OneInchPromo -> Learn2earnStoriesScreen(config.onLearn2earnClick)
Stories.TangemIntro -> FirstStoriesContent(
isPaused = isPaused,
duration = currentStoryDuration,
@ -187,18 +183,12 @@ private fun StoriesScreenContent(config: StoriesScreenContentConfig, modifier: M
)
}
AnimatedVisibility(
visible = config.currentStory != Stories.OneInchPromo,
enter = fadeIn(),
exit = fadeOut(),
) {
HomeButtons(
modifier = Modifier.fillMaxWidth(),
btnScanStateInProgress = config.isScanInProgress,
onScanButtonClick = config.onScanButtonClick,
onShopButtonClick = config.onShopButtonClick,
)
}
HomeButtons(
modifier = Modifier.fillMaxWidth(),
btnScanStateInProgress = config.isScanInProgress,
onScanButtonClick = config.onScanButtonClick,
onShopButtonClick = config.onShopButtonClick,
)
}
}
}
@ -210,7 +200,6 @@ private data class StoriesScreenContentConfig(
val isScanInProgress: Boolean,
val onGoToPreviousStory: () -> Unit = {},
val onGoToNextStory: () -> Unit = {},
val onLearn2earnClick: () -> Unit = {},
val onSearchTokensClick: () -> Unit = {},
val onScanButtonClick: () -> Unit = {},
val onShopButtonClick: () -> Unit = {},
@ -265,12 +254,6 @@ private class StoriesScreenContentConfigProvider : CollectionPreviewParameterPro
currentStory = Stories.WalletForEveryone,
isScanInProgress = false,
),
StoriesScreenContentConfig(
storiesSize = 6,
currentStoryIndex = 6,
currentStory = Stories.OneInchPromo,
isScanInProgress = false,
),
),
)
// endregion Preview

View file

@ -52,7 +52,6 @@ sealed class Stories(
val duration: Int,
val isNewWalletAvailable: MutableState<Boolean> = mutableStateOf(HomeState.isNewWalletAvailableInit()),
) {
object OneInchPromo : Stories(duration = 8000)
object TangemIntro : Stories(duration = 6000)
object RevolutionaryWallet : Stories(duration = 6000)
object UltraSecureBackup : Stories(duration = 6000)

View file

@ -102,10 +102,6 @@ class UserWalletManagerImpl(
}
}
override suspend fun addToken(currency: Currency, derivationPath: String?) {
// FIXME: Can be removed, used only in learn2earn
}
override suspend fun hideAllTokens() {
// FIXME: Used only in Tester Actions
Timber.w("Not implemented")

View file

@ -1,15 +1,11 @@
package com.tangem.tap.proxy.di
import com.tangem.common.Provider
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.feature.learn2earn.domain.api.Learn2earnDependencyProvider
import com.tangem.lib.crypto.DerivationManager
import com.tangem.lib.crypto.TransactionManager
import com.tangem.lib.crypto.UserWalletManager
@ -18,7 +14,6 @@ import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.*
import javax.inject.Singleton
@ -77,27 +72,4 @@ internal object ProxyModule {
networksRepository = networksRepository,
)
}
// regions FeatureConsumers
@Provides
@Singleton
fun provideLear2earnDependencies(appStateHolder: AppStateHolder): Learn2earnDependencyProvider {
return object : Learn2earnDependencyProvider {
@OptIn(ExperimentalCoroutinesApi::class)
override fun getCardTypeResolverFlow(): Flow<CardTypesResolver?> {
return appStateHolder.userWalletListManagerFlow
.flatMapLatest { manager ->
manager?.selectedUserWallet
?.map { it.scanResponse.cardTypesResolver }
?: flowOf(null)
}
}
override fun getWebViewAuthCredentialsProvider(): Provider<String?> = Provider {
appStateHolder.mainStore?.state?.globalState?.configManager?.config?.tangemComAuthorization
}
}
}
// endregion FeatureConsumers
}

View file

@ -1 +0,0 @@
/build

View file

@ -1,4 +0,0 @@
plugins {
alias(deps.plugins.kotlin.jvm)
id("configuration")
}

View file

@ -1 +0,0 @@
/build

View file

@ -1,60 +0,0 @@
plugins {
alias(deps.plugins.android.library)
alias(deps.plugins.kotlin.android)
alias(deps.plugins.kotlin.kapt)
alias(deps.plugins.kotlin.serialization)
alias(deps.plugins.hilt.android)
id("configuration")
}
dependencies {
/** Core modules */
implementation(project(":common"))
implementation(project(":domain:legacy"))
implementation(project(":core:analytics"))
implementation(projects.core.analytics.models)
implementation(project(":core:featuretoggles"))
implementation(project(":core:datasource"))
implementation(project(":core:utils"))
implementation(project(":core:ui"))
implementation(project(":core:res"))
implementation(project(":data:source:preferences"))
implementation(project(":data:common"))
implementation(project(":libs:auth"))
implementation(project(":libs:crypto"))
implementation(deps.material)
/** AndroidX */
implementation(deps.androidx.core.ktx)
implementation(deps.androidx.appCompat)
implementation(deps.androidx.fragment.ktx)
implementation(deps.androidx.activity.compose)
implementation(deps.androidx.browser)
/** Compose */
implementation(deps.compose.material)
implementation(deps.compose.material3)
implementation(deps.compose.foundation)
implementation(deps.compose.ui)
implementation(deps.compose.ui.tooling)
implementation(deps.compose.accompanist.webView)
/** Preferences */
implementation(deps.krateSharedPref)
/** Network */
implementation(deps.moshi)
implementation(deps.moshi.kotlin)
implementation(deps.retrofit)
implementation(deps.retrofit.moshi)
/** Other libraries */
implementation(deps.kotlin.immutable.collections)
implementation(deps.kotlin.serialization)
implementation(deps.timber)
/** DI */
implementation(deps.hilt.android)
kapt(deps.hilt.kapt)
}

View file

@ -1,3 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.tangem.feature.learn2earn.impl">
</manifest>

View file

@ -1,73 +0,0 @@
package com.tangem.feature.learn2earn.analytics
import com.tangem.core.analytics.models.AnalyticsEvent
internal 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(clientType: AnalyticsParam.ClientType) : MainScreen(
event = "Notice - Successful Claim",
params = mapOf(AnalyticsParam.CLIENT_TYPE to clientType.value),
)
}
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),
)
}
}
internal sealed class AnalyticsParam {
sealed class ClientType(val value: String) {
class New : ClientType("New")
class Old : ClientType("Old")
}
companion object Key {
const val CLIENT_TYPE = "Client Type"
}
}

View file

@ -1,138 +0,0 @@
package com.tangem.feature.learn2earn.data
import com.squareup.moshi.Moshi
import com.tangem.datasource.api.promotion.PromotionApi
import com.tangem.datasource.api.promotion.models.*
import com.tangem.feature.learn2earn.data.api.Learn2earnPreferenceStorage
import com.tangem.feature.learn2earn.data.api.Learn2earnRepository
import com.tangem.feature.learn2earn.data.models.PromoUserData
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
import kotlinx.coroutines.withContext
import timber.log.Timber
/**
[REDACTED_AUTHOR]
*/
internal class DefaultLearn2earnRepository(
private val preferencesStorage: Learn2earnPreferenceStorage,
private val api: PromotionApi,
private val dispatchers: AppCoroutineDispatcherProvider,
moshi: Moshi,
) : Learn2earnRepository {
private val promotionInfoAdapter = moshi.adapter(PromotionInfoResponse::class.java)
private val userDataAdapter = moshi.adapter(PromoUserData::class.java)
private var userData: PromoUserData = restoreUserData()
override fun getUserData(): PromoUserData {
return userData
}
override fun updateUserData(userData: PromoUserData) {
this.userData = userData
saveUserData(userData)
}
override fun getProgramName(): String {
return PROGRAM_NAME
}
override suspend fun getPromotionInfo(): Result<PromotionInfoResponse> {
return withContext(dispatchers.io) {
val promotionInfo = restorePromotionInfo()
val data = promotionInfo?.getData(userData.promoCode)
if (data?.status == PromotionInfoResponse.Status.FINISHED) {
Result.success(promotionInfo)
} else {
runCatching { api.getPromotionInfo(getProgramName()) }
.fold(
onSuccess = { infoResponse ->
savePromotionInfo(infoResponse)
Result.success(infoResponse)
},
onFailure = { exception -> Result.failure(exception) },
)
}
}
}
override suspend fun validate(walletId: String): ValidateResponse {
return withContext(dispatchers.io) {
api.validate(ValidateRequestBody(walletId, getProgramName()))
}
}
override suspend fun requestAward(walletId: String, address: String): AwardResponse {
return withContext(dispatchers.io) {
api.requestAward(AwardRequestBody(walletId, address, getProgramName()))
}
}
override suspend fun validateCode(walletId: String, code: String?): CodeValidateResponse {
return withContext(dispatchers.io) {
api.validateCode(CodeValidateRequestBody(walletId, code))
}
}
override suspend fun requestAwardByCode(walletId: String, address: String, code: String?): CodeAwardResponse {
return withContext(dispatchers.io) {
api.requestAwardByCode(CodeAwardRequestBody(walletId, address, code))
}
}
private fun restorePromotionInfo(): PromotionInfoResponse? {
return try {
preferencesStorage.promotionInfo?.let { promotionInfoAdapter.fromJson(it) }
} catch (ex: Exception) {
Timber.e(ex)
preferencesStorage.promotionInfo = null
null
}
}
private fun savePromotionInfo(infoResponse: PromotionInfoResponse) {
try {
preferencesStorage.promotionInfo = promotionInfoAdapter.toJson(infoResponse)
} catch (ex: Exception) {
Timber.e(ex)
}
}
private fun restoreUserData(): PromoUserData {
return try {
preferencesStorage.userData?.let { userDataAdapter.fromJson(it) }
?: createEmptyUserData().apply { saveUserData(this) }
} catch (ex: Exception) {
Timber.e(ex)
createEmptyUserData().apply { saveUserData(this) }
}
}
private fun saveUserData(userData: PromoUserData) {
try {
preferencesStorage.userData = userDataAdapter.toJson(userData)
} catch (ex: Exception) {
Timber.e(ex)
}
}
private fun createEmptyUserData(): PromoUserData = PromoUserData(
promoCode = null,
isRegisteredInPromotion = false,
isAlreadyReceivedAward = false,
isLearningStageFinished = false,
)
private companion object {
const val PROGRAM_NAME: String = "1inch"
}
}
private fun PromotionInfoResponse.getData(promoCode: String?): PromotionInfoResponse.Data? {
return if (promoCode == null) {
newCard
} else {
oldCard
}
}

View file

@ -1,19 +0,0 @@
package com.tangem.feature.learn2earn.data
import android.content.Context
import com.tangem.feature.learn2earn.data.api.Learn2earnPreferenceStorage
import hu.autsoft.krate.SimpleKrate
import hu.autsoft.krate.default.withDefault
import hu.autsoft.krate.stringPref
/**
[REDACTED_AUTHOR]
*/
internal class DefaultPreferenceStorage(
context: Context,
) : SimpleKrate(context = context, name = "Lear2earnPromotion"), Learn2earnPreferenceStorage {
override var promotionInfo: String? by stringPref().withDefault(null)
override var userData: String? by stringPref().withDefault(null)
}

View file

@ -1,11 +0,0 @@
package com.tangem.feature.learn2earn.data.api
/**
[REDACTED_AUTHOR]
*/
interface Learn2earnPreferenceStorage {
var promotionInfo: String?
var userData: String?
}

View file

@ -1,26 +0,0 @@
package com.tangem.feature.learn2earn.data.api
import com.tangem.datasource.api.promotion.models.*
import com.tangem.feature.learn2earn.data.models.PromoUserData
/**
[REDACTED_AUTHOR]
*/
interface Learn2earnRepository {
fun getUserData(): PromoUserData
fun updateUserData(userData: PromoUserData)
fun getProgramName(): String
suspend fun getPromotionInfo(): Result<PromotionInfoResponse>
suspend fun validate(walletId: String): ValidateResponse
suspend fun requestAward(walletId: String, address: String): AwardResponse
suspend fun validateCode(walletId: String, code: String?): CodeValidateResponse
suspend fun requestAwardByCode(walletId: String, address: String, code: String?): CodeAwardResponse
}

View file

@ -1,45 +0,0 @@
package com.tangem.feature.learn2earn.data.di
import android.content.Context
import com.squareup.moshi.Moshi
import com.tangem.datasource.api.promotion.PromotionApi
import com.tangem.datasource.di.NetworkMoshi
import com.tangem.datasource.di.PromotionOneInch
import com.tangem.feature.learn2earn.data.DefaultLearn2earnRepository
import com.tangem.feature.learn2earn.data.DefaultPreferenceStorage
import com.tangem.feature.learn2earn.data.api.Learn2earnPreferenceStorage
import com.tangem.feature.learn2earn.data.api.Learn2earnRepository
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal class Learn2earnDataModule {
@Provides
@Singleton
fun providePreferenceStorage(@ApplicationContext context: Context): Learn2earnPreferenceStorage {
return DefaultPreferenceStorage(context)
}
@Provides
@Singleton
fun provideRepository(
preferenceStorage: Learn2earnPreferenceStorage,
@NetworkMoshi moshi: Moshi,
@PromotionOneInch promotionApi: PromotionApi,
dispatchers: AppCoroutineDispatcherProvider,
): Learn2earnRepository {
return DefaultLearn2earnRepository(
preferencesStorage = preferenceStorage,
api = promotionApi,
dispatchers = dispatchers,
moshi = moshi,
)
}
}

View file

@ -1,11 +0,0 @@
package com.tangem.feature.learn2earn.data.models
/**
[REDACTED_AUTHOR]
*/
data class PromoUserData(
val promoCode: String?,
val isLearningStageFinished: Boolean,
val isRegisteredInPromotion: Boolean,
val isAlreadyReceivedAward: Boolean,
)

View file

@ -1,14 +0,0 @@
package com.tangem.feature.learn2earn.data.toggles
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
/**
[REDACTED_AUTHOR]
*/
internal class DefaultLearn2earnFeatureToggleManager(
private val featureTogglesManager: FeatureTogglesManager,
) : Learn2earnFeatureToggleManager {
override val isLearn2earnEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(name = "1INCH_LEARN_2_EARN_ENABLED")
}

View file

@ -1,11 +0,0 @@
package com.tangem.feature.learn2earn.data.toggles
/**
* Feature toggles manager implementation of "learn2earn" feature
*
[REDACTED_AUTHOR]
*/
interface Learn2earnFeatureToggleManager {
val isLearn2earnEnabled: Boolean
}

View file

@ -1,388 +0,0 @@
package com.tangem.feature.learn2earn.domain
import android.net.Uri
import com.tangem.common.Provider
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.data.common.locale.LocaleProvider
import com.tangem.datasource.api.promotion.models.PromotionInfoResponse
import com.tangem.datasource.demo.DemoModeDatasource
import com.tangem.feature.learn2earn.analytics.AnalyticsParam
import com.tangem.feature.learn2earn.analytics.Learn2earnEvents.*
import com.tangem.feature.learn2earn.data.api.Learn2earnRepository
import com.tangem.feature.learn2earn.data.models.PromoUserData
import com.tangem.feature.learn2earn.data.toggles.Learn2earnFeatureToggleManager
import com.tangem.feature.learn2earn.domain.api.*
import com.tangem.feature.learn2earn.domain.models.Promotion
import com.tangem.feature.learn2earn.domain.models.PromotionError
import com.tangem.feature.learn2earn.domain.models.toDomainError
import com.tangem.lib.crypto.DerivationManager
import com.tangem.lib.crypto.UserWalletManager
import com.tangem.lib.crypto.models.Currency
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import timber.log.Timber
/**
[REDACTED_AUTHOR]
*/
@Suppress("LongParameterList", "LargeClass")
internal class DefaultLearn2earnInteractor(
private val featureToggleManager: Learn2earnFeatureToggleManager,
private val repository: Learn2earnRepository,
private val userWalletManager: UserWalletManager,
private val derivationManager: DerivationManager,
private val localeProvider: LocaleProvider,
private val analytics: AnalyticsEventHandler,
private val demoModeDatasource: DemoModeDatasource,
private val dependencyProvider: Learn2earnDependencyProvider,
dispatchers: AppCoroutineDispatcherProvider,
) : Learn2earnInteractor {
override var webViewResultHandler: WebViewResultHandler? = null
private val promoUserData: PromoUserData
get() = repository.getUserData()
private lateinit var promotion: Promotion
private val scope = CoroutineScope(Job() + dispatchers.io + FeatureCoroutineExceptionHandler.create("mainScope"))
private val isTangemWalletFlow = MutableStateFlow(false)
private var isTangemWalletFlowJob: Job? = null
private var isTangemWalletSync = false
private val webViewUriBuilder: WebViewUriBuilder by lazy {
WebViewUriBuilder(
authCredentialsProvider = dependencyProvider.getWebViewAuthCredentialsProvider(),
localeProvider = localeProvider,
promoCodeProvider = Provider { promoUserData.promoCode },
)
}
private val webViewUriParser: WebViewUriParser by lazy {
WebViewUriParser { repository.getProgramName() }
}
override suspend fun init() {
initPromotionInfo()
}
override fun getIsTangemWalletFlow(): Flow<Boolean> = isTangemWalletFlow
override fun isUserHadPromoCode(): Boolean {
return promoUserData.promoCode != null
}
override suspend fun validateUserWallet(): Result<Unit> {
val promoCode = promoUserData.promoCode
val userWalletId = userWalletManager.getWalletId()
if (userWalletId.isEmpty()) return Result.failure(PromotionError.EmptyUserWalletId)
val domainError = if (promoCode == null) {
repository.validate(userWalletId).error
} else {
repository.validateCode(userWalletId, promoCode).error
}?.toDomainError()
return if (domainError == null) {
Result.success(Unit)
} else {
handlePromotionError(domainError)
Result.failure(domainError)
}
}
override fun isUserRegisteredInPromotion(): Boolean {
return promoUserData.isRegisteredInPromotion
}
override fun getAwardAmount(): Int = try {
promotion.getPromotionInfo().getCardData().award.toInt()
} catch (ex: NullPointerException) {
Timber.w(ex)
0
}
override fun getAwardNetworkName(): String {
val networkId = promotion.getPromotionInfo().awardPaymentToken.networkId
return userWalletManager.getNativeTokenForNetwork(networkId).name
}
@Throws(IllegalArgumentException::class)
override suspend fun requestAward(): Result<Unit> {
val awardCurrency = getCurrencyForAward()
?: return Result.failure(PromotionError.UnknownError("Currency for award is null"))
val walletId = userWalletManager.getWalletId()
val promoCode = promoUserData.promoCode
val domainError = if (promoCode == null) {
requestAward(walletId, awardCurrency)
} else {
requestAwardWithPromoCode(walletId, awardCurrency, promoCode)
}
return if (domainError == null) {
Result.success(Unit)
} else {
handlePromotionError(domainError)
Result.failure(domainError)
}
}
private suspend fun requestAward(walletId: String, awardCurrency: Currency): PromotionError? {
val validateResponse = repository.validate(walletId)
return if (validateResponse.valid == true) {
val address = getWalletAddressForAward(awardCurrency)
val awardResponse = repository.requestAward(walletId, address)
if (awardResponse.status == true) {
updateUserData { it.copy(isAlreadyReceivedAward = true) }
null
} else {
awardResponse.error
}
} else {
validateResponse.error
}?.toDomainError()
}
private suspend fun requestAwardWithPromoCode(
walletId: String,
awardCurrency: Currency,
promoCode: String,
): PromotionError? {
val codeValidateResponse = repository.validateCode(walletId, promoCode)
return if (codeValidateResponse.valid == true) {
val address = getWalletAddressForAward(awardCurrency)
val awardWithCodeResponse = repository.requestAwardByCode(walletId, address, promoCode)
if (awardWithCodeResponse.status == true) {
updateUserData { it.copy(isAlreadyReceivedAward = true) }
null
} else {
awardWithCodeResponse.error
}
} else {
codeValidateResponse.error
}?.toDomainError()
}
private suspend fun getWalletAddressForAward(currency: Currency): String {
val derivationPath = deriveOrAddTokens(currency)
return userWalletManager.getWalletAddress(currency.networkId, derivationPath)
}
private suspend fun deriveOrAddTokens(currency: Currency): String {
val derivationPath = derivationManager.getDerivationPathForBlockchain(currency.networkId)
if (derivationPath.isNullOrEmpty()) error("derivationPath shouldn't be empty")
if (!derivationManager.hasDerivation(currency.networkId, derivationPath)) {
derivationManager.deriveMissingBlockchains(currency)
}
if (!userWalletManager.isTokenAdded(currency, derivationPath)) {
userWalletManager.addToken(currency, derivationPath)
}
return derivationPath
}
private fun handlePromotionError(error: PromotionError) {
when (error) {
is PromotionError.CodeNotFound -> {
updateUserData { it.copy(promoCode = null) }
}
is PromotionError.CardAlreadyHasAward, is PromotionError.WalletAlreadyHasAward,
is PromotionError.CodeWasAlreadyUsed,
-> {
updateUserData { it.copy(isAlreadyReceivedAward = true) }
}
else -> Unit
}
}
override fun buildUriForNewUser(): Uri {
return webViewUriBuilder.buildUriForNewUser(promoUserData.isLearningStageFinished)
}
override fun buildUriForOldUser(): Uri {
return webViewUriBuilder.buildUriForOldUser(promoUserData.isLearningStageFinished)
}
override fun getBasicAuthHeaders(): ArrayList<String> {
return HeadersConverter().convert(webViewUriBuilder.getBasicAuthHeaders())
}
override fun handleRedirect(uri: Uri): WebViewAction {
val result = webViewUriParser.parse(uri)
when (result) {
is WebViewResult.NewUserLearningFinished -> {
analytics.send(PromoScreen.SuccessScreenOpened(AnalyticsParam.ClientType.New()))
updateUserData {
it.copy(
promoCode = result.promoCode,
isRegisteredInPromotion = true,
isLearningStageFinished = true,
)
}
}
is WebViewResult.OldUserLearningFinished -> {
analytics.send(PromoScreen.SuccessScreenOpened(AnalyticsParam.ClientType.Old()))
updateUserData {
it.copy(
promoCode = null,
isLearningStageFinished = true,
)
}
}
WebViewResult.ReadyForAward -> {
updateUserData { it.copy(isRegisteredInPromotion = true) }
}
is WebViewResult.Learn2earnAnalyticsEvent -> {
analytics.send(result.event)
}
WebViewResult.Empty -> Unit
}
webViewResultHandler?.handleResult(result)
return result.toWebViewAction()
}
override fun isPromotionActive(): Boolean {
subscribeToCardTypeResolverFlow()
val isActive = when {
demoModeDatasource.isDemoModeActive -> false
!featureToggleManager.isLearn2earnEnabled -> false
promoUserData.isAlreadyReceivedAward -> false
else -> promotion.isReadyToUse()
}
return isActive
}
override fun isPromotionActiveOnStories(): Boolean {
return if (isPromotionActive()) {
promotion.getPromotionInfo().newCard.status == PromotionInfoResponse.Status.ACTIVE
} else {
false
}
}
override fun isPromotionActiveOnMain(): Boolean {
if (!isPromotionActive() || !isTangemWalletSync) return false
val promotionInfo = promotion.getPromotionInfo()
return if (isUseLogicForOldCards()) {
promotionInfo.oldCard.status == PromotionInfoResponse.Status.ACTIVE
} else {
when (promotionInfo.newCard.status) {
PromotionInfoResponse.Status.PENDING -> !promoUserData.isAlreadyReceivedAward
PromotionInfoResponse.Status.ACTIVE -> true
// disable promotion for a new user only if they have received an award
PromotionInfoResponse.Status.FINISHED -> !promoUserData.isAlreadyReceivedAward
}
}
}
private suspend fun initPromotionInfo() {
promotion = if (featureToggleManager.isLearn2earnEnabled) {
repository.getPromotionInfo()
.fold(
onSuccess = { response ->
val responseError = response.error
if (responseError == null) {
val npeMessage = { "Shouldn't be null" }
Promotion(
info = Promotion.PromotionInfo(
newCard = requireNotNull(response.newCard, npeMessage),
oldCard = requireNotNull(response.oldCard, npeMessage),
awardPaymentToken = requireNotNull(response.awardPaymentToken, npeMessage),
),
error = null,
)
} else {
Promotion(
info = null,
error = responseError.toDomainError(),
)
}
},
onFailure = {
Promotion(
info = null,
error = PromotionError.NetworkUnreachable,
)
},
)
} else {
Promotion(
info = null,
error = null,
)
}
}
private fun getCurrencyForAward(): Currency? {
val token = promotion.info?.awardPaymentToken ?: return null
return Currency.NonNativeToken(
id = token.id,
name = token.name,
symbol = token.symbol,
networkId = token.networkId,
contractAddress = token.contractAddress,
decimalCount = token.decimalCount,
)
}
private fun subscribeToCardTypeResolverFlow() {
if (isTangemWalletFlowJob == null || isTangemWalletFlowJob?.isCancelled == true) {
isTangemWalletFlowJob = dependencyProvider.getCardTypeResolverFlow()
.onEach {
val isTangemWallet = it?.isTangemWallet() ?: false
isTangemWalletSync = isTangemWallet
isTangemWalletFlow.emit(isTangemWallet)
}
.launchIn(scope)
}
}
// region Utils
private fun updateUserData(updateBlock: (PromoUserData) -> PromoUserData): PromoUserData {
return updateBlock(promoUserData).apply {
repository.updateUserData(this)
}
}
private fun isUseLogicForOldCards(): Boolean {
return promoUserData.promoCode == null
}
private fun Promotion.isReadyToUse(): Boolean {
return info != null
}
private fun Promotion.PromotionInfo.getCardData(): PromotionInfoResponse.Data {
return if (isUseLogicForOldCards()) {
oldCard
} else {
newCard
}
}
private fun WebViewResult.toWebViewAction(): WebViewAction {
return when (this) {
is WebViewResult.NewUserLearningFinished,
WebViewResult.OldUserLearningFinished,
is WebViewResult.Learn2earnAnalyticsEvent,
-> WebViewAction.NOTHING
WebViewResult.ReadyForAward -> WebViewAction.FINISH_SESSION
WebViewResult.Empty -> WebViewAction.PROCEED
}
}
// endregion Utils
}

View file

@ -1,28 +0,0 @@
package com.tangem.feature.learn2earn.domain
import com.tangem.utils.converter.TwoWayConverter
/**
* A converter that helps prepare a list of headers to be added to the Bundle.
* ArrayList is used to prevent cast exceptions when the convert result is placed in a Bundle.
* @see Learn2earnRouter#openWebView() putStringArrayListExtra
*
[REDACTED_AUTHOR]
*/
internal class HeadersConverter : TwoWayConverter<Map<String, String>, ArrayList<String>> {
override fun convert(value: Map<String, String>): ArrayList<String> {
val list = value.toList().map {
"${it.first}$SPLIT_SIGN${it.second}"
}
return ArrayList(list)
}
override fun convertBack(value: ArrayList<String>): Map<String, String> {
return value.map { it.split(SPLIT_SIGN) }.associate { it[0] to it[1] }
}
private companion object {
const val SPLIT_SIGN = ":"
}
}

View file

@ -1,68 +0,0 @@
package com.tangem.feature.learn2earn.domain
import android.net.Uri
import com.tangem.common.Provider
import com.tangem.data.common.locale.LocaleProvider
import com.tangem.feature.learn2earn.impl.BuildConfig
/**
[REDACTED_AUTHOR]
*/
internal class WebViewUriBuilder(
private val authCredentialsProvider: Provider<String?>,
private val localeProvider: LocaleProvider,
private val promoCodeProvider: Provider<String?>,
) {
fun buildUriForNewUser(learningIsFinished: Boolean): Uri {
val builder = makeWebViewUriBuilder(learningIsFinished)
.appendQueryParameter("type", QUERY_NEW_CARD)
promoCodeProvider.invoke()?.let {
builder.appendQueryParameter("code", it)
}
return builder.build()
}
fun buildUriForOldUser(learningIsFinished: Boolean): Uri {
val builder = makeWebViewUriBuilder(learningIsFinished)
.appendQueryParameter("type", QUERY_EXISTING_CARD)
return builder.build()
}
fun getBasicAuthHeaders(): Map<String, String> {
val credentials = authCredentialsProvider.invoke()
return if (credentials == null) {
mapOf()
} else {
mapOf("Authorization" to "Basic $credentials")
}
}
private fun makeWebViewUriBuilder(learningIsFinished: Boolean): Uri.Builder = Uri.Builder().apply {
scheme(SCHEME)
if (BuildConfig.DEBUG) {
authority(DEV_BASE_URL)
} else {
authority(BASE_URL)
}
appendPath(localeProvider.getWebUriLocaleLanguage())
appendPath(PATH_PROMOTION)
appendQueryParameter(QUERY_FINISHED, learningIsFinished.toString())
}
private companion object {
const val SCHEME = "https"
const val BASE_URL = "tangem.com"
const val PATH_PROMOTION = "promotion"
const val QUERY_NEW_CARD = "new-card"
const val QUERY_EXISTING_CARD = "existing-card"
const val QUERY_FINISHED = "finished"
const val DEV_BASE_URL = "devweb.tangem.com"
}
}

View file

@ -1,94 +0,0 @@
package com.tangem.feature.learn2earn.domain
import android.net.Uri
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.Learn2earnAnalyticsEvent(event)
}
}
isSuccessNewUserRedirect(uri) -> {
val promoCode = extractPromoCode(uri)
if (promoCode == null) {
WebViewResult.Empty
} else {
WebViewResult.NewUserLearningFinished(promoCode)
}
}
isSuccessOldUserRedirect(uri) -> {
WebViewResult.OldUserLearningFinished
}
isReadyForAwardRedirect(uri) -> {
WebViewResult.ReadyForAward
}
else -> WebViewResult.Empty
}
private fun isReadyForAwardRedirect(uri: Uri): Boolean {
return uri.lastPathSegment == PATH_READY_FOR_AWARD
}
private fun isSuccessNewUserRedirect(uri: Uri): Boolean {
return uri.lastPathSegment == PATH_LEARNING_SUCCESS &&
uri.queryParameterNames.contains(QUERY_PROMO_CODE)
}
private fun isSuccessOldUserRedirect(uri: Uri): Boolean {
return uri.lastPathSegment == PATH_LEARNING_SUCCESS &&
!uri.queryParameterNames.contains(QUERY_PROMO_CODE)
}
private fun extractPromoCode(uri: Uri): String? {
return if (isSuccessNewUserRedirect(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()
else -> null
}
return analyticsEvent
}
private companion object {
const val PATH_LEARNING_SUCCESS = "success"
const val PATH_READY_FOR_AWARD = "ready-for-existing-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"
}
}

View file

@ -1,17 +0,0 @@
package com.tangem.feature.learn2earn.domain.api
import com.tangem.common.Provider
import com.tangem.domain.common.CardTypesResolver
import kotlinx.coroutines.flow.Flow
/**
* Interface, a wrapper that allows us to get values from the AppStateHolder located in the app module
*
[REDACTED_AUTHOR]
*/
interface Learn2earnDependencyProvider {
fun getCardTypeResolverFlow(): Flow<CardTypesResolver?>
fun getWebViewAuthCredentialsProvider(): Provider<String?>
}

View file

@ -1,41 +0,0 @@
package com.tangem.feature.learn2earn.domain.api
import android.net.Uri
import kotlinx.coroutines.flow.Flow
/**
[REDACTED_AUTHOR]
*/
interface Learn2earnInteractor : WebViewRedirectHandler {
var webViewResultHandler: WebViewResultHandler?
suspend fun init()
fun getIsTangemWalletFlow(): Flow<Boolean>
fun isUserHadPromoCode(): Boolean
fun isPromotionActive(): Boolean
fun isPromotionActiveOnStories(): Boolean
fun isPromotionActiveOnMain(): Boolean
suspend fun validateUserWallet(): Result<Unit>
fun isUserRegisteredInPromotion(): Boolean
fun getAwardAmount(): Int
fun getAwardNetworkName(): String
@Throws(IllegalArgumentException::class)
suspend fun requestAward(): Result<Unit>
fun buildUriForNewUser(): Uri
fun buildUriForOldUser(): Uri
fun getBasicAuthHeaders(): ArrayList<String>
}

View file

@ -1,19 +0,0 @@
package com.tangem.feature.learn2earn.domain.api
import android.net.Uri
/**
* Handler that helps determine what actions to take inside the WebView of the Learn2earnWebViewActivity after
* a redirect has been processed by WebViewRedirectHandler
*
[REDACTED_AUTHOR]
*/
interface WebViewRedirectHandler {
fun handleRedirect(uri: Uri): WebViewAction
}
enum class WebViewAction {
NOTHING,
PROCEED,
FINISH_SESSION,
}

View file

@ -1,25 +0,0 @@
package com.tangem.feature.learn2earn.domain.api
import com.tangem.core.analytics.models.AnalyticsEvent
/**
* Handler that helps determine a result of the Learn2earnWebViewActivity webView actions.
*
[REDACTED_AUTHOR]
*/
interface WebViewResultHandler {
fun handleResult(result: WebViewResult)
}
sealed class WebViewResult {
object Empty : WebViewResult()
data class NewUserLearningFinished(val promoCode: String?) : WebViewResult()
object OldUserLearningFinished : WebViewResult()
object ReadyForAward : WebViewResult()
data class Learn2earnAnalyticsEvent(val event: AnalyticsEvent) : WebViewResult()
}

View file

@ -1,73 +0,0 @@
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.data.common.locale.LocaleProvider
import com.tangem.datasource.demo.DemoModeDatasource
import com.tangem.feature.learn2earn.data.api.Learn2earnRepository
import com.tangem.feature.learn2earn.data.toggles.DefaultLearn2earnFeatureToggleManager
import com.tangem.feature.learn2earn.data.toggles.Learn2earnFeatureToggleManager
import com.tangem.feature.learn2earn.domain.DefaultLearn2earnInteractor
import com.tangem.feature.learn2earn.domain.api.Learn2earnDependencyProvider
import com.tangem.feature.learn2earn.domain.api.Learn2earnInteractor
import com.tangem.feature.learn2earn.domain.api.WebViewRedirectHandler
import com.tangem.feature.learn2earn.presentation.Learn2earnRouter
import com.tangem.lib.crypto.DerivationManager
import com.tangem.lib.crypto.UserWalletManager
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal class Learn2earnDomainModule {
@Provides
@Singleton
fun provideRouter(@ApplicationContext context: Context): Learn2earnRouter {
return Learn2earnRouter(context)
}
@Provides
@Singleton
fun provideFeatureToggle(featureToggleManager: FeatureTogglesManager): Learn2earnFeatureToggleManager {
return DefaultLearn2earnFeatureToggleManager(featureToggleManager)
}
@Provides
@Singleton
fun provideInteractor(
featureToggleManager: Learn2earnFeatureToggleManager,
repository: Learn2earnRepository,
dependencyProvider: Learn2earnDependencyProvider,
userWalletManager: UserWalletManager,
derivationManager: DerivationManager,
localeProvider: LocaleProvider,
analyticsEventHandler: AnalyticsEventHandler,
demoModeDatasource: DemoModeDatasource,
dispatchers: AppCoroutineDispatcherProvider,
): Learn2earnInteractor {
return DefaultLearn2earnInteractor(
featureToggleManager = featureToggleManager,
repository = repository,
userWalletManager = userWalletManager,
derivationManager = derivationManager,
localeProvider = localeProvider,
analytics = analyticsEventHandler,
demoModeDatasource = demoModeDatasource,
dependencyProvider = dependencyProvider,
dispatchers = dispatchers,
)
}
@Provides
@Singleton
fun provideWebViewHandler(interactor: Learn2earnInteractor): WebViewRedirectHandler {
return interactor
}
}

View file

@ -1,21 +0,0 @@
package com.tangem.feature.learn2earn.domain.models
import com.tangem.datasource.api.promotion.models.PromotionInfoResponse
/**
[REDACTED_AUTHOR]
*/
internal data class Promotion(
val info: PromotionInfo?,
val error: PromotionError?,
) {
@Throws(NullPointerException::class)
fun getPromotionInfo(): PromotionInfo = info!!
data class PromotionInfo(
val newCard: PromotionInfoResponse.Data,
val oldCard: PromotionInfoResponse.Data,
val awardPaymentToken: PromotionInfoResponse.TokenData,
)
}

View file

@ -1,36 +0,0 @@
package com.tangem.feature.learn2earn.domain.models
import com.tangem.datasource.api.promotion.models.AbstractPromotionResponse
/**
[REDACTED_AUTHOR]
*/
sealed class PromotionError(val code: Int, val description: String) : Throwable(
message = "code: $code, description: $description",
) {
object NetworkUnreachable : PromotionError(code = 0, "Network or service not available")
class CodeNotFound(code: Int, description: String) : PromotionError(code, description)
class CodeWasNotAppliedInShop(code: Int, description: String) : PromotionError(code, description)
class CodeWasAlreadyUsed(code: Int, description: String) : PromotionError(code, description)
class WalletAlreadyHasAward(code: Int, description: String) : PromotionError(code, description)
class CardAlreadyHasAward(code: Int, description: String) : PromotionError(code, description)
class ProgramNotFound(code: Int, description: String) : PromotionError(code, description)
class ProgramWasEnd(code: Int, description: String) : PromotionError(code, description)
class UnknownError(description: String) : PromotionError(code = -1, description)
object EmptyUserWalletId : PromotionError(code = -1, "UserWalletId is empty")
}
@Suppress("MagicNumber")
internal fun AbstractPromotionResponse.Error.toDomainError(): PromotionError = when (code) {
101 -> PromotionError.CodeNotFound(code, message)
102 -> PromotionError.CodeWasNotAppliedInShop(code, message)
103 -> PromotionError.CodeWasAlreadyUsed(code, message)
104 -> PromotionError.WalletAlreadyHasAward(code, message)
105 -> PromotionError.CardAlreadyHasAward(code, message)
106 -> PromotionError.ProgramNotFound(code, message)
107 -> PromotionError.ProgramWasEnd(code, message)
else -> PromotionError.UnknownError("$code:$message")
}

View file

@ -1,23 +0,0 @@
package com.tangem.feature.learn2earn.presentation
import android.content.Context
import android.content.Intent
import android.net.Uri
import com.tangem.feature.learn2earn.presentation.webView.Learn2earnWebViewActivity
/**
[REDACTED_AUTHOR]
*/
class Learn2earnRouter(
private val context: Context,
) {
fun openWebView(uri: Uri, rawHeaders: ArrayList<String>) {
val intent = Intent(context, Learn2earnWebViewActivity::class.java)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(Learn2earnWebViewActivity.EXTRA_WEB_URI, uri.toString())
.putStringArrayListExtra(Learn2earnWebViewActivity.EXTRA_WEB_HEADERS, rawHeaders)
context.startActivity(intent)
}
}

View file

@ -1,294 +0,0 @@
package com.tangem.feature.learn2earn.presentation
import androidx.compose.runtime.getValue
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.core.ui.extensions.TextReference
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
import com.tangem.feature.learn2earn.domain.models.PromotionError
import com.tangem.feature.learn2earn.impl.R
import com.tangem.feature.learn2earn.presentation.ui.state.*
import com.tangem.lib.crypto.models.errors.UserCancelledException
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import timber.log.Timber
import javax.inject.Inject
/**
[REDACTED_AUTHOR]
*/
@HiltViewModel
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(
Learn2earnState.init(
uiActions = Learn2earnUiActions(
onButtonStoryClick = ::onButtonStoryClick,
onButtonMainClick = ::onButtonMainClick,
),
),
)
private set
init {
subscribeToWalletChanges()
uiState = uiState
.updateStoriesVisibility(isVisible = interactor.isPromotionActiveOnStories())
.updateGetBonusVisibility(isVisible = interactor.isPromotionActiveOnMain())
}
private fun subscribeToWalletChanges() {
viewModelScope.launch(dispatchers.io) {
interactor.getIsTangemWalletFlow()
.collect { isTangemWallet ->
if (isTangemWallet) {
updateMainScreenViews()
} else {
updateUi { uiState.updateGetBonusVisibility(isVisible = false) }
}
}
}
}
fun onMainScreenCreated() {
updateMainScreenViews()
}
fun onMainScreenRefreshed() {
updateMainScreenViews()
}
private fun updateMainScreenViews() {
if (!interactor.isPromotionActiveOnMain()) {
uiState = uiState.updateGetBonusVisibility(isVisible = false)
return
}
viewModelScope.launch(dispatchers.io) {
runCatching { interactor.validateUserWallet() }
.onSuccess { result ->
result.fold(
onSuccess = {
updateUi {
uiState
.updateStoriesVisibility(isVisible = interactor.isPromotionActiveOnStories())
.updateGetBonusVisibility(isVisible = interactor.isPromotionActiveOnMain())
.changeGetBonusDescription(getBonusDescription())
}
},
onFailure = {
if (interactor.isPromotionActive()) {
val error = it as? PromotionError ?: return@launch
updateViewsVisibilityOnError(error)
} else {
updateUi { uiState.updateViewsVisibility(isVisible = false) }
}
},
)
}
.onFailure {
Timber.w(it)
updateUi { uiState.updateGetBonusVisibility(isVisible = false) }
}
}
}
private suspend fun updateViewsVisibilityOnError(error: PromotionError) {
when (error) {
is PromotionError.ProgramNotFound,
is PromotionError.ProgramWasEnd,
is PromotionError.CodeWasAlreadyUsed,
is PromotionError.WalletAlreadyHasAward,
is PromotionError.CardAlreadyHasAward,
-> {
updateUi { uiState.updateViewsVisibility(isVisible = false) }
}
is PromotionError.CodeWasNotAppliedInShop,
is PromotionError.EmptyUserWalletId,
-> {
updateUi { uiState.updateGetBonusVisibility(isVisible = false) }
}
is PromotionError.CodeNotFound,
-> {
updateUi {
uiState.updateViewsVisibility(isVisible = true)
.changeGetBonusDescription(getBonusDescription())
}
}
is PromotionError.UnknownError,
PromotionError.NetworkUnreachable,
-> Unit
}
}
private fun onButtonStoryClick() {
analytics.send(Learn2earnEvents.IntroductionProcess.ButtonLearn())
router.openWebView(interactor.buildUriForNewUser(), interactor.getBasicAuthHeaders())
}
private fun onButtonMainClick() {
val isUserHasPromo = interactor.isUserHadPromoCode()
analytics.send(Learn2earnEvents.MainScreen.NoticeLear2earn(getAnalyticsClientType(isUserHasPromo)))
if (isUserHasPromo || interactor.isUserRegisteredInPromotion()) {
requestAward()
} else {
subscribeToWebViewResultEvents()
router.openWebView(interactor.buildUriForOldUser(), interactor.getBasicAuthHeaders())
}
}
private fun requestAward() {
viewModelScope.launch(dispatchers.io) {
updateUi { uiState.updateProgress(showProgress = true) }
runCatching { interactor.requestAward() }
.onSuccess { requestAwardResult ->
requestAwardResult.fold(
onSuccess = {
analytics.send(
Learn2earnEvents.MainScreen.NoticeClaimSuccess(
clientType = getAnalyticsClientType(
isUserHasPromo = interactor.isUserHadPromoCode(),
),
),
)
val onHideDialog = {
uiState = uiState.updateViewsVisibility(isVisible = false)
.hideDialog()
}
val successDialog = MainScreenState.Dialog.Claimed(
networkFullName = interactor.getAwardNetworkName(),
onOk = onHideDialog,
onDismissRequest = onHideDialog,
)
updateUi { uiState.showDialog(successDialog) }
},
onFailure = {
val errorDialog = createErrorDialog(it.toDomainError())
if (errorDialog == null) {
updateUi { uiState.updateProgress(showProgress = false) }
} else {
updateUi {
uiState.updateProgress(showProgress = false)
.showDialog(errorDialog)
}
}
},
)
}
.onFailure { exception ->
updateUi { uiState.updateProgress(showProgress = false) }
if (exception is UserCancelledException) return@launch
createErrorDialog(exception.toDomainError())?.let {
updateUi { uiState.showDialog(it) }
}
}
}
}
private fun getBonusDescription(): MainScreenState.Description {
return if (interactor.isUserRegisteredInPromotion()) {
MainScreenState.Description.GetBonus
} else {
MainScreenState.Description.Learn(interactor.getAwardAmount())
}
}
private fun getAnalyticsClientType(isUserHasPromo: Boolean): AnalyticsParam.ClientType {
return if (isUserHasPromo) {
AnalyticsParam.ClientType.New()
} else {
AnalyticsParam.ClientType.Old()
}
}
private fun createErrorDialog(error: PromotionError): MainScreenState.Dialog? {
return when (error) {
is PromotionError.CodeWasNotAppliedInShop -> {
val onHideDialog = { uiState = uiState.hideDialog() }
MainScreenState.Dialog.PromoCodeNotRegistered(
onOk = {
uiState = uiState.hideDialog()
subscribeToWebViewResultEvents()
router.openWebView(interactor.buildUriForNewUser(), interactor.getBasicAuthHeaders())
},
onCancel = onHideDialog,
onDismissRequest = onHideDialog,
)
}
is PromotionError.CodeNotFound,
is PromotionError.CodeWasAlreadyUsed,
is PromotionError.CardAlreadyHasAward,
is PromotionError.WalletAlreadyHasAward,
is PromotionError.ProgramNotFound,
is PromotionError.ProgramWasEnd,
-> {
val onHideDialog = {
uiState = uiState.hideDialog()
.updateGetBonusVisibility(isVisible = false)
}
MainScreenState.Dialog.Error(
textReference = TextReference.Str(error.description),
onOk = onHideDialog,
onDismissRequest = onHideDialog,
)
}
is PromotionError.UnknownError, PromotionError.NetworkUnreachable -> {
val onHideDialog = { uiState = uiState.hideDialog() }
MainScreenState.Dialog.Error(
textReference = TextReference.Res(R.string.common_server_unavailable),
onOk = onHideDialog,
onDismissRequest = onHideDialog,
)
}
is PromotionError.EmptyUserWalletId -> null
}
}
private fun subscribeToWebViewResultEvents() {
interactor.webViewResultHandler = object : WebViewResultHandler {
override fun handleResult(result: WebViewResult) {
when (result) {
is WebViewResult.NewUserLearningFinished -> {
interactor.webViewResultHandler = null
}
WebViewResult.ReadyForAward -> {
interactor.webViewResultHandler = null
updateMainScreenViews()
requestAward()
}
is WebViewResult.OldUserLearningFinished,
is WebViewResult.Learn2earnAnalyticsEvent,
WebViewResult.Empty,
-> Unit
}
}
}
}
private suspend fun updateUi(updateBlock: suspend () -> Learn2earnState) {
withContext(dispatchers.io) {
val newState = updateBlock.invoke()
withContext(dispatchers.main) { uiState = newState }
}
}
}
private fun Throwable.toDomainError(): PromotionError {
return this as? PromotionError ?: PromotionError.UnknownError(message ?: "Unknown error")
}

View file

@ -1,172 +0,0 @@
package com.tangem.feature.learn2earn.presentation.ui
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.SpacerH4
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.learn2earn.impl.R
import com.tangem.feature.learn2earn.presentation.ui.component.GradientCircle
import com.tangem.feature.learn2earn.presentation.ui.state.MainScreenState
/**
[REDACTED_AUTHOR]
*/
@Composable
internal fun GetBonusView(state: MainScreenState, modifier: Modifier = Modifier) {
if (!state.isVisible) return
val shape = TangemTheme.shapes.roundedCornersMedium
Box(
modifier = modifier
.clickable(onClick = state.onClick)
.height(TangemTheme.dimens.size96)
.background(
color = TangemTheme.colors.background.action,
shape = shape,
),
) {
ContentBackground(shape)
Row(
modifier = Modifier.fillMaxHeight(),
verticalAlignment = Alignment.CenterVertically,
) {
Box(modifier = Modifier.padding(horizontal = TangemTheme.dimens.size16)) {
Image(
painter = painterResource(id = R.drawable.img_1inch_logo_42_40),
alpha = state.logoState.alpha,
contentDescription = null,
)
if (state.showProgress) {
CircularProgressIndicator(
modifier = Modifier
.align(Alignment.Center)
.size(size = TangemTheme.dimens.size28),
color = TangemColorPalette.White,
strokeWidth = TangemTheme.dimens.size2,
)
}
}
Column(
modifier = Modifier.padding(end = TangemTheme.dimens.size40),
) {
Text(
text = state.description.title.resolveReference(),
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.primary2,
)
SpacerH4()
Text(
text = state.description.subtitle.resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
}
}
Image(
modifier = Modifier
.size(TangemTheme.dimens.size40)
.padding(end = TangemTheme.dimens.size16)
.align(Alignment.CenterEnd),
painter = painterResource(id = R.drawable.ic_chevron_right_24),
contentDescription = null,
colorFilter = ColorFilter.tint(Color.Gray),
)
}
}
@Suppress("MagicNumber")
@Composable
private fun ContentBackground(shape: Shape) {
Box(
modifier = Modifier
.clip(shape)
.fillMaxSize(),
) {
GradientCircle(
size = 340.dp,
offsetX = (-103).dp,
offsetY = 16.dp,
startColor = Color(0xFF1D5DC7),
endColor = Color(0x000B173D),
)
GradientCircle(
size = 268.dp,
offsetX = 90.dp,
offsetY = 0.dp,
startColor = Color(0xD8FF0CCA),
endColor = Color(0x000B0E17),
)
}
Box(
modifier = Modifier
.background(
color = TangemTheme.colors.background.action.copy(alpha = 0.7f),
shape = shape,
)
.fillMaxSize(),
)
}
@Preview
@Composable
private fun GetBonusViewPreview_Light(@PreviewParameter(GetBonusViewPreviewProvider::class) state: MainScreenState) {
TangemTheme(
isDark = false,
) {
GetBonusView(state)
}
}
@Preview
@Composable
private fun GetBonusViewPreview_Dark(@PreviewParameter(GetBonusViewPreviewProvider::class) state: MainScreenState) {
TangemTheme(
isDark = true,
) {
GetBonusView(state)
}
}
private class GetBonusViewPreviewProvider : CollectionPreviewParameterProvider<MainScreenState>(
collection = listOf(
MainScreenState(
isVisible = true,
onClick = {},
description = MainScreenState.Description.Learn(1),
logoState = MainScreenState.LogoState.Idle,
showProgress = false,
),
MainScreenState(
isVisible = true,
onClick = {},
description = MainScreenState.Description.Learn(2),
logoState = MainScreenState.LogoState.InProgress,
showProgress = true,
),
MainScreenState(
isVisible = true,
onClick = {},
description = MainScreenState.Description.GetBonus,
logoState = MainScreenState.LogoState.Idle,
showProgress = false,
),
),
)

View file

@ -1,87 +0,0 @@
package com.tangem.feature.learn2earn.presentation.ui
import androidx.compose.material.AlertDialog
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import com.tangem.core.ui.components.TextButton
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.feature.learn2earn.impl.R
import com.tangem.feature.learn2earn.presentation.ui.state.MainScreenState
/**
[REDACTED_AUTHOR]
*/
@Composable
internal fun Learn2earnDialogs(dialog: MainScreenState.Dialog?) {
when (dialog) {
is MainScreenState.Dialog.Claimed -> ClaimedDialog(dialog)
// TODO: https://tangem.slack.com/archives/C01HARKDLQ0/p1691421861756069
// is MainScreenState.Dialog.PromoCodeNotRegistered -> PromoCodeNotRegisteredDialog(dialog)
is MainScreenState.Dialog.Error -> ErrorDialog(dialog)
else -> Unit
}
}
@Composable
private fun ClaimedDialog(dialog: MainScreenState.Dialog.Claimed) {
AlertDialog(
title = {
Text(text = stringResource(id = R.string.common_success))
},
text = {
Text(text = stringResource(id = R.string.main_promotion_credited, dialog.networkFullName))
},
confirmButton = {
TextButton(
text = stringResource(id = R.string.common_ok),
onClick = dialog.onOk,
)
},
onDismissRequest = dialog.onDismissRequest,
)
}
// @Composable
// private fun PromoCodeNotRegisteredDialog(dialog: MainScreenState.Dialog.PromoCodeNotRegistered) {
// AlertDialog(
// title = {
// Text(text = stringResource(id = R.string.common_error))
// },
// text = {
// Text(text = stringResource(id = R.string.main_promotion_no_purchase))
// },
// dismissButton = {
// TextButton(
// text = stringResource(id = R.string.common_cancel),
// onClick = dialog.onCancel,
// )
// },
// confirmButton = {
// TextButton(
// text = stringResource(id = R.string.common_buy),
// onClick = dialog.onOk,
// )
// },
// onDismissRequest = dialog.onDismissRequest,
// )
// }
@Composable
private fun ErrorDialog(dialog: MainScreenState.Dialog.Error) {
AlertDialog(
title = {
Text(text = stringResource(id = R.string.common_error))
},
text = {
Text(text = dialog.textReference.resolveReference())
},
confirmButton = {
TextButton(
text = stringResource(id = R.string.common_ok),
onClick = dialog.onOk,
)
},
onDismissRequest = dialog.onDismissRequest,
)
}

View file

@ -1,21 +0,0 @@
package com.tangem.feature.learn2earn.presentation.ui
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.learn2earn.presentation.ui.state.Learn2earnState
/**
[REDACTED_AUTHOR]
*/
@Composable
fun Learn2earnMainPageScreen(state: Learn2earnState, modifier: Modifier = Modifier) {
GetBonusView(
state = state.mainScreenState,
modifier = modifier
.padding(horizontal = TangemTheme.dimens.size16)
.padding(vertical = TangemTheme.dimens.size8),
)
Learn2earnDialogs(state.mainScreenState.dialog)
}

View file

@ -1,136 +0,0 @@
package com.tangem.feature.learn2earn.presentation.ui
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.SecondaryButton
import com.tangem.core.ui.components.SpacerH16
import com.tangem.core.ui.components.SpacerH32
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.learn2earn.impl.R
import com.tangem.feature.learn2earn.presentation.ui.component.GradientCircle
/**
[REDACTED_AUTHOR]
*/
@Composable
fun Learn2earnStoriesScreen(onLearnClick: () -> Unit, modifier: Modifier = Modifier) {
Box(modifier = modifier.fillMaxSize()) {
ContentBackground(
modifier = Modifier
.fillMaxSize()
.align(Alignment.Center),
)
Column(
modifier = Modifier.fillMaxSize(),
) {
StoryDescription(
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.size40)
.weight(weight = 1f),
headerText = stringResource(id = R.string.common_learn_and_earn),
bodyText = stringResource(id = R.string.story_learn_description),
)
Image(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = TangemTheme.dimens.size16)
.navigationBarsPadding()
.weight(weight = 1.2f),
painter = painterResource(id = R.drawable.img_1inch_logo_401_378),
contentDescription = null,
)
}
SecondaryButton(
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.size16)
.padding(bottom = TangemTheme.dimens.size36)
.fillMaxWidth()
.align(Alignment.BottomCenter)
.navigationBarsPadding(),
text = stringResource(id = R.string.story_learn_learn),
onClick = onLearnClick,
)
}
}
@Suppress("MagicNumber")
@Composable
private fun ContentBackground(modifier: Modifier = Modifier) {
Box(modifier = modifier) {
GradientCircle(
size = 800.dp,
offsetX = (-220).dp,
offsetY = (-200).dp,
startColor = Color(0xCE164594),
endColor = Color(0x000B173D),
)
GradientCircle(
size = 800.dp,
offsetX = 200.dp,
offsetY = 290.dp,
startColor = Color(0xFF3D0230),
endColor = Color(0x000B0E17),
)
}
}
@Composable
private fun StoryDescription(headerText: String, bodyText: String, modifier: Modifier = Modifier) {
Column(
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally,
) {
SpacerH32()
Text(
text = headerText,
textAlign = TextAlign.Center,
style = TangemTheme.typography.h1.copy(
fontWeight = FontWeight.Bold,
),
color = TangemTheme.colors.text.primary2,
)
SpacerH16()
Text(
text = bodyText,
textAlign = TextAlign.Center,
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.tertiary,
)
}
}
@Preview
@Composable
private fun Learn2earnStoriesScreenPreview_Light() {
TangemTheme(
isDark = false,
) {
Learn2earnStoriesScreen(
onLearnClick = {},
)
}
}
@Preview
@Composable
private fun Learn2earnStoriesScreenPreview_Dark() {
TangemTheme(
isDark = true,
) {
Learn2earnStoriesScreen(
onLearnClick = {},
)
}
}

View file

@ -1,38 +0,0 @@
package com.tangem.feature.learn2earn.presentation.ui.component
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
/**
[REDACTED_AUTHOR]
*/
@Composable
internal fun GradientCircle(
size: Dp,
offsetX: Dp,
offsetY: Dp,
startColor: Color,
endColor: Color,
radius: Dp = size / 2,
) {
Canvas(
modifier = Modifier
.size(size)
.offset(x = offsetX, y = offsetY),
onDraw = {
drawCircle(
radius = radius.toPx(),
brush = Brush.radialGradient(
radius = radius.toPx(),
colors = listOf(startColor, endColor),
),
)
},
)
}

View file

@ -1,74 +0,0 @@
package com.tangem.feature.learn2earn.presentation.ui.state
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.WrappedList
import com.tangem.feature.learn2earn.impl.R
/**
[REDACTED_AUTHOR]
*/
data class Learn2earnState(
val storyScreenState: StoryScreenState,
val mainScreenState: MainScreenState,
) {
companion object
}
data class StoryScreenState(
val isVisible: Boolean,
val onClick: () -> Unit,
)
data class MainScreenState(
val isVisible: Boolean,
val onClick: () -> Unit,
val description: Description,
val showProgress: Boolean,
val logoState: LogoState,
val dialog: Dialog? = null,
) {
sealed class LogoState(val alpha: Float) {
object Idle : LogoState(alpha = 1.0f)
object InProgress : LogoState(alpha = 0.2f)
}
sealed class Description(val title: TextReference, val subtitle: TextReference) {
class Learn(award: Int) : Description(
TextReference.Res(R.string.common_learn_and_earn),
TextReference.PluralRes(R.plurals.main_learn_subtitle, award, WrappedList(listOf(award))),
)
object GetBonus : Description(
TextReference.Res(R.string.common_learn_and_earn),
TextReference.Res(R.string.main_get_bonus_subtitle),
)
}
sealed class Dialog {
data class Claimed(
val networkFullName: String,
val onOk: () -> Unit,
val onDismissRequest: () -> Unit,
) : Dialog()
data class PromoCodeNotRegistered(
val onOk: () -> Unit,
val onCancel: () -> Unit,
val onDismissRequest: () -> Unit,
) : Dialog()
data class Error(
val textReference: TextReference,
val onOk: () -> Unit,
val onDismissRequest: () -> Unit,
) : Dialog()
}
}
class Learn2earnUiActions(
val onButtonStoryClick: () -> Unit,
val onButtonMainClick: () -> Unit,
)

View file

@ -1,102 +0,0 @@
package com.tangem.feature.learn2earn.presentation.ui.state
/**
[REDACTED_AUTHOR]
*/
internal fun Learn2earnState.Companion.init(uiActions: Learn2earnUiActions): Learn2earnState {
return Learn2earnState(
storyScreenState = StoryScreenState(
isVisible = false,
onClick = uiActions.onButtonStoryClick,
),
mainScreenState = MainScreenState(
isVisible = false,
onClick = uiActions.onButtonMainClick,
description = MainScreenState.Description.Learn(0),
logoState = MainScreenState.LogoState.Idle,
showProgress = false,
),
)
}
internal fun Learn2earnState.updateStoriesVisibility(isVisible: Boolean): Learn2earnState {
return if (storyScreenState.isVisible == isVisible) {
this
} else {
copy(
storyScreenState = storyScreenState.copy(
isVisible = isVisible,
),
)
}
}
internal fun Learn2earnState.updateGetBonusVisibility(isVisible: Boolean): Learn2earnState {
return if (mainScreenState.isVisible == isVisible) {
this
} else {
copy(
mainScreenState = mainScreenState.copy(
isVisible = isVisible,
),
)
}
}
internal fun Learn2earnState.updateViewsVisibility(isVisible: Boolean): Learn2earnState {
return updateStoriesVisibility(isVisible)
.updateGetBonusVisibility(isVisible)
}
internal fun Learn2earnState.changeGetBonusDescription(description: MainScreenState.Description): Learn2earnState {
return if (mainScreenState.description == description) {
this
} else {
copy(
mainScreenState = mainScreenState.copy(
description = description,
),
)
}
}
internal fun Learn2earnState.showDialog(dialog: MainScreenState.Dialog): Learn2earnState {
return if (mainScreenState.dialog == dialog) {
this
} else {
copy(
mainScreenState = mainScreenState.copy(
dialog = dialog,
),
)
}
}
internal fun Learn2earnState.hideDialog(): Learn2earnState {
return if (mainScreenState.dialog == null) {
this
} else {
copy(
mainScreenState = mainScreenState.copy(
dialog = null,
),
)
}
}
internal fun Learn2earnState.updateProgress(showProgress: Boolean): Learn2earnState {
return if (mainScreenState.showProgress == showProgress) {
this
} else {
copy(
mainScreenState = mainScreenState.copy(
showProgress = showProgress,
logoState = if (showProgress) {
MainScreenState.LogoState.InProgress
} else {
MainScreenState.LogoState.Idle
},
),
)
}
}

View file

@ -1,74 +0,0 @@
package com.tangem.feature.learn2earn.presentation.webView
import android.os.Bundle
import android.view.MenuItem
import android.webkit.WebView
import androidx.activity.viewModels
import androidx.appcompat.app.ActionBar
import androidx.appcompat.app.AppCompatActivity
import com.tangem.feature.learn2earn.impl.R
import dagger.hilt.android.AndroidEntryPoint
/**
[REDACTED_AUTHOR]
*/
@AndroidEntryPoint
class Learn2earnWebViewActivity : AppCompatActivity() {
private val viewModel by viewModels<WebViewViewModel>()
private lateinit var actionBar: ActionBar
private lateinit var webView: WebView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_web_view)
val webViewData = viewModel.extractData(intent)
setSupportActionBar(findViewById(R.id.toolbar))
actionBar = supportActionBar!!
actionBar.setDisplayHomeAsUpEnabled(true)
actionBar.setDisplayShowHomeEnabled(true)
actionBar.title = webViewData.uri.authority
webView = findViewById(R.id.web_view)
webView.settings.javaScriptEnabled = true
webView.settings.domStorageEnabled = true
webView.settings.loadsImagesAutomatically = true
webView.webViewClient = Learn2earnWebViewClient(
redirectHandler = viewModel,
headers = webViewData.headers,
finishSessionHandler = { finish() },
)
webView.loadUrl(
webViewData.uri.toString(),
webViewData.headers,
)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
android.R.id.home -> {
finish()
true
}
else -> super.onOptionsItemSelected(item)
}
}
override fun onDestroy() {
webView.clearHistory()
webView.clearCache(true)
webView.destroy()
super.onDestroy()
}
companion object {
const val EXTRA_WEB_URI = "webViewUri"
const val EXTRA_WEB_HEADERS = "webViewHeaders"
}
}

View file

@ -1,31 +0,0 @@
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.WebViewAction
import com.tangem.feature.learn2earn.domain.api.WebViewRedirectHandler
import timber.log.Timber
internal class Learn2earnWebViewClient(
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 (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)
}
WebViewAction.FINISH_SESSION -> {
finishSessionHandler.invoke()
}
}
return true
}
}

View file

@ -1,35 +0,0 @@
package com.tangem.feature.learn2earn.presentation.webView
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.WebViewRedirectHandler
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
/**
[REDACTED_AUTHOR]
*/
@HiltViewModel
internal class WebViewViewModel @Inject constructor(
handler: WebViewRedirectHandler,
) : ViewModel(), WebViewRedirectHandler by handler {
fun extractData(intent: Intent): WebViewData {
val uriString = requireNotNull(intent.getStringExtra(Learn2earnWebViewActivity.EXTRA_WEB_URI)) {
"The Intent property EXTRA_WEB_URI is null"
}
val rawHeaders = intent.getStringArrayListExtra(Learn2earnWebViewActivity.EXTRA_WEB_HEADERS) ?: ArrayList()
return WebViewData(
uri = Uri.parse(uriString),
headers = HeadersConverter().convertBack(rawHeaders),
)
}
}
data class WebViewData(
val uri: Uri,
val headers: Map<String, String>,
)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/coordinator"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar"
style="@style/Widget.MaterialComponents.Toolbar.Surface"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:elevation="0dp">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_scrollFlags="scroll|enterAlways"
app:navigationIcon="@drawable/ic_close_24" />
</com.google.android.material.appbar.AppBarLayout>
<WebView
android:id="@+id/web_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="?attr/actionBarSize" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -31,15 +31,6 @@ interface UserWalletManager {
@Throws(IllegalStateException::class)
suspend fun isTokenAdded(currency: Currency, derivationPath: String?): Boolean
/**
* Adds token to wallet if its not
*
* @param currency to add to wallet
* @param derivationPath if null uses default
*/
@Throws(IllegalStateException::class)
suspend fun addToken(currency: Currency, derivationPath: String?)
suspend fun hideAllTokens()
/**

View file

@ -96,9 +96,6 @@ include(":features:wallet:impl")
include(":features:tokendetails:api")
include(":features:tokendetails:impl")
include(":features:learn2earn:api")
include(":features:learn2earn:impl")
include(":features:send:api")
include(":features:send:impl")