diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ef93ea2179..13d09bd76f 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -138,6 +138,8 @@
android:name="com.tangem.tap.features.sprinklr.ui.SprinklrActivity"
android:theme="@style/AppTheme" />
+
+
@@ -136,6 +144,9 @@ class TapApplication : Application(), ImageLoaderFactory {
@Inject
lateinit var walletConnectSessionsRepository: WalletConnectSessionsRepository
+ @Inject
+ lateinit var learn2ernInteractor: Learn2earnInteractor
+
override fun onCreate() {
super.onCreate()
@@ -189,8 +200,9 @@ class TapApplication : Application(), ImageLoaderFactory {
appStateHolder.userTokensRepository = userTokensRepository
appStateHolder.walletStoresManager = walletStoresManager
- scope.launch {
+ runBlocking {
featureTogglesManager.init()
+ learn2ernInteractor.init()
}
initTopUpController()
diff --git a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt
index bdb1d84900..591f7b6369 100644
--- a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt
@@ -12,26 +12,38 @@ import androidx.compose.ui.platform.ComposeView
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
import com.tangem.core.analytics.Analytics
import com.tangem.core.ui.res.TangemTheme
+import com.tangem.feature.learn2earn.presentation.Learn2earnViewModel
import com.tangem.tap.common.analytics.events.IntroductionProcess
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.features.home.compose.StoriesScreen
import com.tangem.tap.features.home.redux.HomeAction
import com.tangem.tap.features.home.redux.HomeState
+import com.tangem.tap.features.home.redux.Stories
import com.tangem.tap.features.tokens.legacy.redux.TokensAction
import com.tangem.tap.store
+import dagger.hilt.android.AndroidEntryPoint
import org.rekotlin.StoreSubscriber
+@AndroidEntryPoint
class HomeFragment : Fragment(), StoreSubscriber {
private var homeState: MutableState = mutableStateOf(store.state.homeState)
+ private val learn2earnViewModel by viewModels()
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
store.dispatch(HomeAction.OnCreate)
store.dispatch(HomeAction.Init)
+ if (learn2earnViewModel.isNeedToShowViewOnStoriesScreen()) {
+ store.dispatch(HomeAction.InsertStory(position = 0, Stories.OneInchPromo))
+ // re init homeState after inserting learn2earn story
+ homeState = mutableStateOf(store.state.homeState)
+ }
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
@@ -79,6 +91,9 @@ class HomeFragment : Fragment(), StoreSubscriber {
private fun ScreenContent() {
StoriesScreen(
homeState,
+ onLearn2earnClick = {
+ learn2earnViewModel.onStoriesClick()
+ },
onScanButtonClick = {
Analytics.send(IntroductionProcess.ButtonScanCard())
store.dispatch(HomeAction.ReadCard())
diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt
index af5df09c59..46ef676001 100644
--- a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt
+++ b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt
@@ -26,7 +26,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.tangem.core.ui.res.TangemTheme
-import com.tangem.feature.learn2earn.presentation.ui.OneInchStoriesScreen
+import com.tangem.feature.learn2earn.presentation.ui.StoriesScreen
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.StoriesProgressBar
@@ -39,6 +39,7 @@ import kotlin.math.max
@Composable
fun StoriesScreen(
homeState: MutableState,
+ onLearn2earnClick: () -> Unit,
onScanButtonClick: () -> Unit,
onShopButtonClick: () -> Unit,
onSearchTokensClick: () -> Unit,
@@ -152,7 +153,7 @@ fun StoriesScreen(
colorFilter = if (currentStory.isDarkBackground) null else ColorFilter.tint(Color.Black),
)
when (currentStory) {
- Stories.OneInchPromo -> OneInchStoriesScreen({})
+ Stories.OneInchPromo -> StoriesScreen(onLearn2earnClick)
Stories.TangemIntro -> FirstStoriesContent(isPaused, currentStory.duration) { hideContent.value = it }
Stories.RevolutionaryWallet -> StoriesRevolutionaryWallet(currentStory.duration)
Stories.UltraSecureBackup -> StoriesUltraSecureBackup(isPaused, currentStory.duration)
@@ -215,6 +216,7 @@ fun StoriesScreen(
@Composable
private fun StoriesScreenPreview() {
StoriesScreen(
+ onLearn2earnClick = {},
onScanButtonClick = {},
onShopButtonClick = {},
onSearchTokensClick = {},
diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt
index bb9be2edc3..86b139defb 100644
--- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt
+++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt
@@ -11,6 +11,8 @@ sealed class HomeAction : Action {
object OnCreate : HomeAction()
object Init : HomeAction()
+ data class InsertStory(val position: Int, val story: Stories) : HomeAction()
+
data class ReadCard(
val analyticsEvent: AnalyticsEvent? = Basic.CardWasScanned(AnalyticsParam.ScannedFrom.Introduction),
) : HomeAction()
diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt
index f9f2911c32..6843253f5a 100644
--- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt
@@ -12,6 +12,13 @@ private fun internalReduce(action: Action, state: AppState): HomeState {
var state = state.homeState
when (action) {
+ is HomeAction.InsertStory -> {
+ state = state.copy(
+ stories = state.stories.toMutableList().apply {
+ add(action.position, action.story)
+ },
+ )
+ }
is HomeAction.ScanInProgress -> {
state = state.copy(scanInProgress = action.scanInProgress)
}
diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt
index 9342420e6b..415deb938d 100644
--- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt
+++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt
@@ -11,7 +11,8 @@ data class HomeState(
val stories: List = initDefaultStories(),
) : StateType {
- val firstStory: Stories = stories[0]
+ val firstStory: Stories
+ get() = stories[0]
val btnScanStateInProgress: Boolean
get() = btnScanState.progressState == ProgressState.Loading
@@ -20,7 +21,6 @@ data class HomeState(
companion object {
fun initDefaultStories(): List = listOf(
- Stories.OneInchPromo,
Stories.TangemIntro,
Stories.RevolutionaryWallet,
Stories.UltraSecureBackup,
diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt
index 7f22650f5e..005bcca0a4 100644
--- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt
@@ -1,8 +1,10 @@
package com.tangem.tap.features.welcome.redux
-import android.content.Intent
-import com.tangem.common.*
import com.tangem.common.core.TangemSdkError
+import com.tangem.common.doOnFailure
+import com.tangem.common.doOnResult
+import com.tangem.common.doOnSuccess
+import com.tangem.common.flatMap
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.*
import com.tangem.tap.common.analytics.events.AnalyticsParam
@@ -17,7 +19,6 @@ import com.tangem.tap.domain.model.builders.UserWalletBuilder
import com.tangem.tap.domain.scanCard.ScanCardProcessor
import com.tangem.tap.domain.userWalletList.unlockIfLockable
import com.tangem.tap.features.intentHandler.handlers.WalletConnectLinkIntentHandler
-import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError
import com.tangem.tap.features.signin.redux.SignInAction
import kotlinx.coroutines.launch
import org.rekotlin.Middleware
@@ -98,15 +99,6 @@ internal class WelcomeMiddleware {
}
}
- private fun handleInitialIntent(intent: Intent?) {
- val isBackgroundScanNotHandled = !intentHandler.handleBackgroundScan(intent, hasSavedUserWallets = true)
- val hasNotIncompletedBackup = !backupService.hasIncompletedBackup
-
- if (isBackgroundScanNotHandled && hasNotIncompletedBackup) {
- store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics)
- }
- }
-
private suspend inline fun scanCardInternal(crossinline onCardScanned: suspend (ScanResponse) -> Unit) {
tangemSdkManager.setAccessCodeRequestPolicy(
useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes,
diff --git a/app/src/main/res/layout/fragment_wallet.xml b/app/src/main/res/layout/fragment_wallet.xml
index b4287625d4..ac04bb12dd 100644
--- a/app/src/main/res/layout/fragment_wallet.xml
+++ b/app/src/main/res/layout/fragment_wallet.xml
@@ -46,8 +46,8 @@
android:id="@+id/cl_wallet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:clipToPadding="false"
android:clipChildren="false"
+ android:clipToPadding="false"
android:paddingBottom="92dp">
+
+
T.toWeakReference(): WeakReference {
+ return WeakReference(this)
+}
\ No newline at end of file
diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/DefaultLearn2earnInteractor.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/DefaultLearn2earnInteractor.kt
new file mode 100644
index 0000000000..fcb7c48add
--- /dev/null
+++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/DefaultLearn2earnInteractor.kt
@@ -0,0 +1,123 @@
+package com.tangem.feature.learn2earn.domain
+
+import android.net.Uri
+import com.tangem.datasource.api.promotion.models.PromotionInfoResponse
+import com.tangem.feature.learn2earn.data.api.Learn2earnRepository
+import com.tangem.feature.learn2earn.domain.api.Learn2earnInteractor
+import com.tangem.feature.learn2earn.domain.models.CardType
+import com.tangem.feature.learn2earn.domain.models.Promotion
+import com.tangem.feature.learn2earn.domain.models.PromotionError
+import com.tangem.feature.learn2earn.domain.models.PromotionError.Companion.toDomainError
+import com.tangem.feature.learn2earn.domain.models.RedirectConsequences
+import com.tangem.lib.auth.BasicAuthProvider
+
+/**
+[REDACTED_AUTHOR]
+ */
+class DefaultLearn2earnInteractor(
+ private val repository: Learn2earnRepository,
+ basicAuthProvider: BasicAuthProvider,
+ userCountryCodeProvider: () -> String,
+) : Learn2earnInteractor {
+
+ private val webViewUriBuilder = WebViewUriBuilder(
+ basicAuthProvider = basicAuthProvider,
+ userCountryCodeProvider = userCountryCodeProvider,
+ promoCodeProvider = { repository.getPromoCode() },
+ promoNameProvider = { repository.getProgramName() },
+ )
+
+ private lateinit var promotion: Promotion
+
+ override suspend fun init() {
+ initPromotionInfo()
+ }
+
+ override fun isNeedToShowViewOnStoriesScreen(): Boolean {
+ return promotionIsActive()
+ }
+
+ override fun getBasicAuthHeaders(): Map {
+ return webViewUriBuilder.getBasicAuthHeaders()
+ }
+
+ override suspend fun isNeedToShowViewOnWalletScreen(walletId: String): Boolean {
+ val response = repository.validate(walletId)
+
+ return if (response.valid == false && response.isError()) {
+ false
+ } else {
+ promotionIsActive()
+ }
+ }
+
+ override fun buildUriForStories(): Uri {
+ val type = if (repository.isHadActivatedCards()) {
+ CardType.NEW
+ } else {
+ CardType.EXISTED
+ }
+
+ return webViewUriBuilder.buildUriForStories(type)
+ }
+
+ override fun buildUriForMainPage(walletId: String, cardId: String, cardPubKey: String): Uri {
+ return webViewUriBuilder.buildUriForMainPage(walletId, cardId, cardPubKey)
+ }
+
+ override fun handleWebViewRedirect(uri: Uri): RedirectConsequences {
+ if (webViewUriBuilder.isReadyForExistedCardAwardRedirect(uri)) {
+ return RedirectConsequences.FINISH_SESSION
+ }
+
+ return if (webViewUriBuilder.isPromoCodeRedirect(uri)) {
+ webViewUriBuilder.extractPromoCode(uri)?.let { repository.savePromoCode(it) }
+ RedirectConsequences.NOTHING
+ } else {
+ RedirectConsequences.PROCEED
+ }
+ }
+
+ private fun promotionIsActive(): Boolean {
+ val isActive = when {
+ repository.isAlreadyReceivedAward() -> false
+ promotion.isError() -> false
+ else -> promotion.getInfo().status == PromotionInfoResponse.Status.ACTIVE
+ }
+
+ return isActive
+ }
+
+ private suspend fun initPromotionInfo() {
+ // return Promotion.dummyActive()
+
+ promotion = repository.getPromotionInfo()
+ .fold(
+ onSuccess = { response ->
+ val responseError = response.error
+ if (responseError == null) {
+ Promotion(
+ info = Promotion.PromotionInfo(
+ status = response.status!!,
+ awardForNewCard = response.awardForNewCard!!,
+ awardForOldCard = response.awardForOldCard!!,
+ awardPaymentToken = response.awardPaymentToken!!,
+ ),
+ error = null,
+ )
+ } else {
+ Promotion(
+ info = null,
+ error = responseError.toDomainError(),
+ )
+ }
+ },
+ onFailure = {
+ Promotion(
+ info = null,
+ error = PromotionError.NetworkUnreachable,
+ )
+ },
+ )
+ }
+}
\ No newline at end of file
diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/WebViewUriBuilder.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/WebViewUriBuilder.kt
new file mode 100644
index 0000000000..c70e983a15
--- /dev/null
+++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/WebViewUriBuilder.kt
@@ -0,0 +1,98 @@
+package com.tangem.feature.learn2earn.domain
+
+import android.net.Uri
+import com.tangem.feature.learn2earn.domain.models.CardType
+import com.tangem.feature.learn2earn.impl.BuildConfig
+import com.tangem.lib.auth.BasicAuthProvider
+
+/**
+[REDACTED_AUTHOR]
+ */
+class WebViewUriBuilder(
+ private val basicAuthProvider: BasicAuthProvider,
+ private val userCountryCodeProvider: () -> String,
+ private val promoCodeProvider: () -> String?,
+ private val promoNameProvider: () -> String,
+) {
+
+ fun buildUriForStories(type: CardType): Uri {
+ val builder = makeWebViewUriBuilder()
+ .appendQueryParameter("type", type.typeName)
+
+ promoCodeProvider.invoke()?.let {
+ builder.appendQueryParameter("code", it)
+ }
+
+ return builder.build()
+ }
+
+ fun buildUriForMainPage(walletId: String, cardId: String, cardPubKey: String): Uri {
+ val builder = makeWebViewUriBuilder()
+ .appendQueryParameter("type", CardType.EXISTED.typeName)
+ .appendQueryParameter("cardPublicKey", cardPubKey)
+ .appendQueryParameter("cardId", cardId)
+ .appendQueryParameter("walletId", walletId)
+ .appendQueryParameter("programName", promoNameProvider.invoke())
+
+ promoCodeProvider.invoke()?.let {
+ builder.appendQueryParameter("code", it)
+ }
+
+ return builder.build()
+ }
+
+ fun getBasicAuthHeaders(): Map {
+ return basicAuthProvider.getCredentials()?.let {
+ mapOf("Authorization" to "Basic $it")
+ } ?: mapOf()
+ }
+
+ fun isPromoCodeRedirect(uri: Uri): Boolean {
+ return uri.toString().contains(SUFFIX_CODE_CREATED)
+ }
+
+ fun isReadyForExistedCardAwardRedirect(uri: Uri): Boolean {
+ return uri.toString().endsWith(SUFFIX_READY_FOR_AWARD)
+ }
+
+ fun extractPromoCode(uri: Uri): String? {
+ val url = uri.toString()
+
+ return if (isPromoCodeRedirect(uri)) {
+ val replaceString = makeWebViewUriBuilder().build().toString() + SUFFIX_CODE_CREATED
+ val code = url.replace(replaceString, "")
+ code
+ } else {
+ null
+ }
+ }
+
+ private fun makeWebViewUriBuilder(): Uri.Builder {
+ val builder = Uri.Builder()
+ builder.scheme("https")
+
+ if (BuildConfig.DEBUG) {
+ builder.authority(DEV_WEB_VIEW_BASE_URL)
+ builder.appendPath(userCountryCodeProvider.invoke())
+ builder.appendPath("promotion-test")
+ } else {
+ builder.authority(WEB_VIEW_BASE_URL)
+ builder.appendPath(userCountryCodeProvider.invoke())
+ builder.appendPath("promotion")
+ }
+
+ return builder
+ }
+
+ private companion object {
+ const val WEB_VIEW_BASE_URL = "tangem.com"
+
+ const val DEV_WEB_VIEW_BASE_URL = "devweb.tangem.com"
+ const val DEV_WEB_VIEW_BASIC_AUTH = "Basic dGFuZ2VtOnRhbmdlbWRldjc="
+ // TODO: 1inch: replace by invalid auth
+ // const val DEV_WEB_VIEW_BASIC_AUTH = "Basic paste valid value"
+
+ const val SUFFIX_CODE_CREATED = "/code-created?code="
+ const val SUFFIX_READY_FOR_AWARD = "/ready-for-exsited-card-award"
+ }
+}
\ No newline at end of file
diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/api/Learn2earnInteractor.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/api/Learn2earnInteractor.kt
new file mode 100644
index 0000000000..4aa046c5e3
--- /dev/null
+++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/api/Learn2earnInteractor.kt
@@ -0,0 +1,18 @@
+package com.tangem.feature.learn2earn.domain.api
+
+import android.net.Uri
+import com.tangem.feature.learn2earn.domain.models.RedirectConsequences
+
+/**
+[REDACTED_AUTHOR]
+ */
+interface Learn2earnInteractor {
+
+ suspend fun init()
+ fun isNeedToShowViewOnStoriesScreen(): Boolean
+ suspend fun isNeedToShowViewOnWalletScreen(walletId: String): Boolean
+ fun getBasicAuthHeaders(): Map
+ fun buildUriForStories(): Uri
+ fun buildUriForMainPage(walletId: String, cardId: String, cardPubKey: String): Uri
+ fun handleWebViewRedirect(uri: Uri): RedirectConsequences
+}
\ No newline at end of file
diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/di/Learn2earnDomainModule.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/di/Learn2earnDomainModule.kt
new file mode 100644
index 0000000000..d33f81422f
--- /dev/null
+++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/di/Learn2earnDomainModule.kt
@@ -0,0 +1,39 @@
+package com.tangem.feature.learn2earn.domain.di
+
+import android.content.Context
+import com.tangem.feature.learn2earn.data.api.Learn2earnRepository
+import com.tangem.feature.learn2earn.domain.DefaultLearn2earnInteractor
+import com.tangem.feature.learn2earn.domain.api.Learn2earnInteractor
+import com.tangem.feature.learn2earn.presentation.Learn2earnRouter
+import com.tangem.lib.auth.BasicAuthProvider
+import com.tangem.utils.extensions.toWeakReference
+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)
+class Learn2earnDomainModule {
+
+ @Provides
+ @Singleton
+ fun provideRouter(@ApplicationContext context: Context): Learn2earnRouter {
+ return Learn2earnRouter(context.toWeakReference())
+ }
+
+ @Provides
+ @Singleton
+ fun provideInteractor(
+ repository: Learn2earnRepository,
+ basicAuthProvider: BasicAuthProvider,
+ ): Learn2earnInteractor {
+ return DefaultLearn2earnInteractor(
+ repository = repository,
+ basicAuthProvider = basicAuthProvider,
+ userCountryCodeProvider = { "ru" },
+ )
+ }
+}
\ No newline at end of file
diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/models/CardType.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/models/CardType.kt
new file mode 100644
index 0000000000..1e05115373
--- /dev/null
+++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/models/CardType.kt
@@ -0,0 +1,9 @@
+package com.tangem.feature.learn2earn.domain.models
+
+/**
+[REDACTED_AUTHOR]
+ */
+enum class CardType(val typeName: String) {
+ NEW("new-card"),
+ EXISTED("existed-card"),
+}
\ No newline at end of file
diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/models/MockModels.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/models/MockModels.kt
new file mode 100644
index 0000000000..340de5e151
--- /dev/null
+++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/models/MockModels.kt
@@ -0,0 +1,76 @@
+package com.tangem.feature.learn2earn.domain.models
+
+import com.tangem.datasource.api.promotion.models.PromotionInfoResponse
+
+/**
+[REDACTED_AUTHOR]
+ */
+internal fun Promotion.Companion.dummyPending(): Promotion {
+ return Promotion(
+ info = Promotion.PromotionInfo.dummy().copy(
+ status = PromotionInfoResponse.Status.PENDING,
+ ),
+ error = null,
+ )
+}
+
+internal fun Promotion.Companion.dummyActive(): Promotion {
+ return Promotion(
+ info = Promotion.PromotionInfo.dummy().copy(
+ status = PromotionInfoResponse.Status.ACTIVE,
+ ),
+ error = null,
+ )
+}
+
+internal fun Promotion.Companion.dummyFinished(): Promotion {
+ return Promotion(
+ info = Promotion.PromotionInfo.dummy().copy(
+ status = PromotionInfoResponse.Status.FINISHED,
+ ),
+ error = null,
+ )
+}
+
+internal fun Promotion.Companion.dummyError(): Promotion {
+ return Promotion(
+ info = null,
+ error = PromotionError.Error(
+ code = 105,
+ description = "any",
+ ),
+ )
+}
+
+internal fun Promotion.Companion.dummyErrorUnreachable(): Promotion {
+ return Promotion(
+ info = null,
+ error = PromotionError.NetworkUnreachable,
+ )
+}
+
+private fun Promotion.PromotionInfo.Companion.dummy(): Promotion.PromotionInfo {
+ return Promotion.PromotionInfo(
+ status = PromotionInfoResponse.Status.ACTIVE,
+ awardForNewCard = 10f,
+ awardForOldCard = 5.5f,
+ awardPaymentToken = Promotion.tokenInfo(),
+ )
+}
+
+private fun Promotion.Companion.tokenInfo(): PromotionInfoResponse.TokenInfo {
+ return PromotionInfoResponse.TokenInfo(
+ id = "1inch",
+ name = "1inch",
+ symbol = "1INCH",
+ active = true,
+ networks = listOf(
+ PromotionInfoResponse.TokenInfo.Network(
+ networkId = "polygon-pos",
+ exchangeable = false,
+ contractAddress = "0x9c2c5fd7b07e95ee044ddeba0e97a665f142394f",
+ decimalCount = 1,
+ ),
+ ),
+ )
+}
\ No newline at end of file
diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/models/Promotion.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/models/Promotion.kt
new file mode 100644
index 0000000000..e5b071a15d
--- /dev/null
+++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/models/Promotion.kt
@@ -0,0 +1,30 @@
+package com.tangem.feature.learn2earn.domain.models
+
+import com.tangem.datasource.api.promotion.models.PromotionInfoResponse
+
+/**
+[REDACTED_AUTHOR]
+ */
+data class Promotion(
+ val info: PromotionInfo?,
+ val error: PromotionError?,
+) {
+
+ @Throws(NullPointerException::class)
+ fun getInfo(): PromotionInfo = info!!
+
+ fun isError(): Boolean = error != null
+
+ fun isUnreachable(): Boolean = error == PromotionError.NetworkUnreachable
+
+ data class PromotionInfo(
+ val status: PromotionInfoResponse.Status,
+ val awardForNewCard: Float,
+ val awardForOldCard: Float,
+ val awardPaymentToken: PromotionInfoResponse.TokenInfo,
+ ) {
+ companion object
+ }
+
+ companion object
+}
\ No newline at end of file
diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/models/PromotionError.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/models/PromotionError.kt
new file mode 100644
index 0000000000..8cd24cfa2c
--- /dev/null
+++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/models/PromotionError.kt
@@ -0,0 +1,15 @@
+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) {
+ object NetworkUnreachable : PromotionError(-1, "Network or service is unreachable")
+ class Error(code: Int, description: String) : PromotionError(code, description)
+
+ companion object {
+ fun AbstractPromotionResponse.Error.toDomainError(): PromotionError = Error(code, description)
+ }
+}
\ No newline at end of file
diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/models/WebViewHelper.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/models/WebViewHelper.kt
new file mode 100644
index 0000000000..0075b17435
--- /dev/null
+++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/models/WebViewHelper.kt
@@ -0,0 +1,17 @@
+package com.tangem.feature.learn2earn.domain.models
+
+import android.net.Uri
+
+/**
+[REDACTED_AUTHOR]
+ */
+interface WebViewHelper {
+ fun handleWebViewRedirect(uri: Uri): RedirectConsequences
+ fun getWebViewHeaders(): Map
+}
+
+enum class RedirectConsequences {
+ NOTHING,
+ PROCEED,
+ FINISH_SESSION,
+}
\ No newline at end of file
diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/Learn2earnRouter.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/Learn2earnRouter.kt
new file mode 100644
index 0000000000..0c6c1a7e34
--- /dev/null
+++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/Learn2earnRouter.kt
@@ -0,0 +1,23 @@
+package com.tangem.feature.learn2earn.presentation
+
+import android.content.Context
+import android.content.Intent
+import timber.log.Timber
+import java.lang.ref.WeakReference
+
+/**
+[REDACTED_AUTHOR]
+ */
+class Learn2earnRouter(
+ private val wContext: WeakReference,
+) {
+
+ fun openWebView() {
+ val context = wContext.get()
+ if (context == null) {
+ Timber.e("Can't open the Learn2earnWebViewActivity")
+ } else {
+ context.startActivity(Intent(context, Learn2earnWebViewActivity::class.java))
+ }
+ }
+}
\ No newline at end of file
diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/Learn2earnViewModel.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/Learn2earnViewModel.kt
new file mode 100644
index 0000000000..f39aee6032
--- /dev/null
+++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/Learn2earnViewModel.kt
@@ -0,0 +1,58 @@
+package com.tangem.feature.learn2earn.presentation
+
+import android.net.Uri
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.tangem.feature.learn2earn.domain.api.Learn2earnInteractor
+import com.tangem.feature.learn2earn.domain.models.RedirectConsequences
+import com.tangem.feature.learn2earn.domain.models.WebViewHelper
+import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import javax.inject.Inject
+
+/**
+[REDACTED_AUTHOR]
+ */
+@HiltViewModel
+class Learn2earnViewModel @Inject constructor(
+ private val interactor: Learn2earnInteractor,
+ private val router: Learn2earnRouter,
+ private val dispatchers: AppCoroutineDispatcherProvider,
+) : ViewModel(), WebViewHelper {
+
+ var webViewUri: Uri = Uri.parse("https://localhost")
+ private set
+
+ fun isNeedToShowViewOnStoriesScreen(): Boolean = interactor.isNeedToShowViewOnStoriesScreen()
+
+ fun onStoriesClick() {
+ webViewUri = interactor.buildUriForStories()
+ router.openWebView()
+ }
+
+ fun isNeedToShowViewOnWalletScreen(walletId: String, callback: (Boolean) -> Unit) {
+ viewModelScope.launch(dispatchers.io) {
+ val isNeedToShow = interactor.isNeedToShowViewOnWalletScreen(walletId)
+ withContext(dispatchers.main) {
+ callback(isNeedToShow)
+ }
+ }
+ }
+
+ fun onMainPageClick(walletId: String, cardId: String, cardPubKey: String) {
+ webViewUri = interactor.buildUriForMainPage(walletId, cardId, cardPubKey)
+ router.openWebView()
+ }
+
+ // region WebViewHelper
+ override fun handleWebViewRedirect(uri: Uri): RedirectConsequences {
+ return interactor.handleWebViewRedirect(uri)
+ }
+
+ override fun getWebViewHeaders(): Map {
+ return interactor.getBasicAuthHeaders()
+ }
+ // endregion WebViewHelper
+}
\ No newline at end of file
diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/Learn2earnWebViewActivity.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/Learn2earnWebViewActivity.kt
new file mode 100644
index 0000000000..c054b21ef0
--- /dev/null
+++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/Learn2earnWebViewActivity.kt
@@ -0,0 +1,90 @@
+package com.tangem.feature.learn2earn.presentation
+
+import android.os.Bundle
+import android.view.MenuItem
+import android.webkit.WebResourceRequest
+import android.webkit.WebView
+import android.webkit.WebViewClient
+import androidx.activity.viewModels
+import androidx.appcompat.app.ActionBar
+import androidx.appcompat.app.AppCompatActivity
+import com.tangem.feature.learn2earn.domain.models.RedirectConsequences
+import com.tangem.feature.learn2earn.domain.models.WebViewHelper
+import com.tangem.feature.learn2earn.impl.R
+import dagger.hilt.android.AndroidEntryPoint
+
+/**
+[REDACTED_AUTHOR]
+ */
+@AndroidEntryPoint
+class Learn2earnWebViewActivity : AppCompatActivity() {
+
+ private val learn2earnViewModel by viewModels()
+
+ private lateinit var actionBar: ActionBar
+ private lateinit var webView: WebView
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_web_view)
+
+ setSupportActionBar(findViewById(R.id.toolbar))
+ actionBar = supportActionBar!!
+ actionBar.setDisplayHomeAsUpEnabled(true)
+ actionBar.setDisplayShowHomeEnabled(true)
+ actionBar.title = learn2earnViewModel.webViewUri.authority
+
+ webView = findViewById(R.id.web_view)
+ webView.settings.javaScriptEnabled = true
+ webView.settings.domStorageEnabled = true
+ webView.settings.loadsImagesAutomatically = true
+
+ webView.webViewClient = Learn2earnWebViewClient(
+ helper = learn2earnViewModel,
+ finishSessionHandler = { finish() },
+ )
+ webView.loadUrl(
+ learn2earnViewModel.webViewUri.toString(),
+ learn2earnViewModel.getWebViewHeaders(),
+ )
+ }
+
+ 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()
+ }
+}
+
+private class Learn2earnWebViewClient(
+ private val helper: WebViewHelper,
+ private val finishSessionHandler: () -> Unit,
+) : WebViewClient() {
+
+ override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
+ when (helper.handleWebViewRedirect(request.url)) {
+ RedirectConsequences.NOTHING -> Unit
+ RedirectConsequences.PROCEED -> {
+ val headers = helper.getWebViewHeaders().toMutableMap()
+ request.requestHeaders?.let { headers.putAll(it) }
+ view.loadUrl(request.url.toString(), headers)
+ }
+ RedirectConsequences.FINISH_SESSION -> {
+ finishSessionHandler.invoke()
+ }
+ }
+ return true
+ }
+}
\ No newline at end of file
diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/OneInchStoriesScreen.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/StoriesScreen.kt
similarity index 94%
rename from features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/OneInchStoriesScreen.kt
rename to features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/StoriesScreen.kt
index ff3c3218b7..74f3584e6b 100644
--- a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/OneInchStoriesScreen.kt
+++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/StoriesScreen.kt
@@ -24,9 +24,9 @@ import com.tangem.feature.learn2earn.presentation.ui.component.GradientCircle
/**
[REDACTED_AUTHOR]
*/
-// TODO: fixme: make function as internal after adding feature interface
+// TODO: 1inch: make function as internal after adding feature interface
@Composable
-fun OneInchStoriesScreen(onLearnClick: () -> Unit, modifier: Modifier = Modifier) {
+fun StoriesScreen(onLearnClick: () -> Unit, modifier: Modifier = Modifier) {
Box(modifier = modifier.fillMaxSize()) {
ContentBackground(
modifier = Modifier
@@ -111,7 +111,7 @@ private fun OneInchStoriesContentPreview_Light() {
TangemTheme(
isDark = false,
) {
- OneInchStoriesScreen(
+ StoriesScreen(
onLearnClick = {},
)
}
@@ -123,7 +123,7 @@ private fun OneInchStoriesContentPreview_Dark() {
TangemTheme(
isDark = true,
) {
- OneInchStoriesScreen(
+ StoriesScreen(
onLearnClick = {},
)
}
diff --git a/features/learn2earn/impl/src/main/res/layout/activity_web_view.xml b/features/learn2earn/impl/src/main/res/layout/activity_web_view.xml
new file mode 100644
index 0000000000..46968b0a14
--- /dev/null
+++ b/features/learn2earn/impl/src/main/res/layout/activity_web_view.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file