Updated on 2026-08-14
This commit is contained in:
parent
975e404db7
commit
2657cf5b4c
50 changed files with 6 additions and 2590 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
1
features/learn2earn/api/.gitignore
vendored
1
features/learn2earn/api/.gitignore
vendored
|
|
@ -1 +0,0 @@
|
|||
/build
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
plugins {
|
||||
alias(deps.plugins.kotlin.jvm)
|
||||
id("configuration")
|
||||
}
|
||||
1
features/learn2earn/impl/.gitignore
vendored
1
features/learn2earn/impl/.gitignore
vendored
|
|
@ -1 +0,0 @@
|
|||
/build
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="com.tangem.feature.learn2earn.impl">
|
||||
</manifest>
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
package com.tangem.feature.learn2earn.data.api
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface Learn2earnPreferenceStorage {
|
||||
|
||||
var promotionInfo: String?
|
||||
|
||||
var userData: String?
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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 = ":"
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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?>
|
||||
}
|
||||
|
|
@ -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>
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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 = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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>
|
||||
|
|
@ -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()
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue