From efe271e1a072b3ab3cd7b0db0fe4803d7e366c21 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 19 Jun 2023 08:51:46 +0300 Subject: [PATCH] Updated on 2026-08-14 --- app/src/main/AndroidManifest.xml | 2 + .../java/com/tangem/tap/TapApplication.kt | 118 +++++++++-------- .../tangem/tap/features/home/HomeFragment.kt | 15 +++ .../features/home/compose/StoriesScreen.kt | 6 +- .../tap/features/home/redux/HomeAction.kt | 2 + .../tap/features/home/redux/HomeReducer.kt | 7 + .../tap/features/home/redux/HomeState.kt | 4 +- .../welcome/redux/WelcomeMiddleware.kt | 16 +-- app/src/main/res/layout/fragment_wallet.xml | 13 +- .../java/com/tangem/utils/extensions/Any.kt | 10 ++ .../domain/DefaultLearn2earnInteractor.kt | 123 ++++++++++++++++++ .../learn2earn/domain/WebViewUriBuilder.kt | 98 ++++++++++++++ .../domain/api/Learn2earnInteractor.kt | 18 +++ .../domain/di/Learn2earnDomainModule.kt | 39 ++++++ .../learn2earn/domain/models/CardType.kt | 9 ++ .../learn2earn/domain/models/MockModels.kt | 76 +++++++++++ .../learn2earn/domain/models/Promotion.kt | 30 +++++ .../domain/models/PromotionError.kt | 15 +++ .../learn2earn/domain/models/WebViewHelper.kt | 17 +++ .../presentation/Learn2earnRouter.kt | 23 ++++ .../presentation/Learn2earnViewModel.kt | 58 +++++++++ .../presentation/Learn2earnWebViewActivity.kt | 90 +++++++++++++ ...eInchStoriesScreen.kt => StoriesScreen.kt} | 8 +- .../src/main/res/layout/activity_web_view.xml | 29 +++++ 24 files changed, 751 insertions(+), 75 deletions(-) create mode 100644 core/utils/src/main/java/com/tangem/utils/extensions/Any.kt create mode 100644 features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/DefaultLearn2earnInteractor.kt create mode 100644 features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/WebViewUriBuilder.kt create mode 100644 features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/api/Learn2earnInteractor.kt create mode 100644 features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/di/Learn2earnDomainModule.kt create mode 100644 features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/models/CardType.kt create mode 100644 features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/models/MockModels.kt create mode 100644 features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/models/Promotion.kt create mode 100644 features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/models/PromotionError.kt create mode 100644 features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/models/WebViewHelper.kt create mode 100644 features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/Learn2earnRouter.kt create mode 100644 features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/Learn2earnViewModel.kt create mode 100644 features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/Learn2earnWebViewActivity.kt rename features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/{OneInchStoriesScreen.kt => StoriesScreen.kt} (94%) create mode 100644 features/learn2earn/impl/src/main/res/layout/activity_web_view.xml 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