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 e4885e7314..95b558c438 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 @@ -39,14 +39,14 @@ class HomeFragment : Fragment(), StoreSubscriber { super.onCreate(savedInstanceState) store.dispatch(HomeAction.OnCreate) store.dispatch(HomeAction.Init) - if (learn2earnViewModel.uiState.storyScreenState.isVisible) { - 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 { + // sync adding story before screen creation + if (learn2earnViewModel.uiState.storyScreenState.isVisible) { + store.dispatch(HomeAction.InsertStory(position = 0, Stories.OneInchPromo)) + homeState.value = store.state.homeState + } return ComposeView(inflater.context).apply { setContent { TangemTheme { diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index 15a3f5c172..71f724d931 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -20,9 +20,7 @@ import com.tangem.tap.common.extensions.eraseContext import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.features.home.BELARUS_COUNTRY_CODE -import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE -import com.tangem.tap.features.home.redux.HomeMiddleware.BUY_WALLET_URL +import com.tangem.tap.features.home.redux.HomeMiddleware.NEW_BUY_WALLET_URL import com.tangem.tap.features.send.redux.states.ButtonState import com.tangem.tap.features.signin.redux.SignInAction import com.tangem.tap.preferencesStorage @@ -40,6 +38,7 @@ object HomeMiddleware { val handler = homeMiddleware const val BUY_WALLET_URL = "https://tangem.com/ru/resellers/" + const val NEW_BUY_WALLET_URL = "https://buy.tangem.com/ru/" } private val homeMiddleware: Middleware = { _, _ -> @@ -67,10 +66,13 @@ private fun handleHomeAction(action: Action) { } is HomeAction.GoToShop -> { Analytics.send(Shop.ScreenOpened()) - when (action.userCountryCode) { - RUSSIA_COUNTRY_CODE, BELARUS_COUNTRY_CODE -> store.dispatchOpenUrl(BUY_WALLET_URL) - else -> store.dispatch(NavigationAction.NavigateTo(AppScreen.Shop)) - } + store.dispatchOpenUrl(NEW_BUY_WALLET_URL) + + // disabled for now in task [REDACTED_JIRA] + // when (action.userCountryCode) { + // RUSSIA_COUNTRY_CODE, BELARUS_COUNTRY_CODE -> store.dispatchOpenUrl(BUY_WALLET_URL) + // else -> store.dispatch(NavigationAction.NavigateTo(AppScreen.Shop)) + // } } } } diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 00f29ea502..7076371f81 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -173,7 +173,7 @@ Бэкап кошелька не был произведен Баланс В сумме учтены не все монеты - 1INCH токены будут зачислены на адрес вашего кошелька в сети %s в течение 24 часов + 1INCH токены будут зачислены на адрес вашего кошелька в сети %s в течение 48 часов По вашему промокоду не было покупки кошелька, а значит вы не можете получить бонус. Купите кошелек Tangem, отсканируйте его в приложении и получите бонус. Чтобы получить доступ ко всем сетям, вам необходимо отсканировать карту Отсканируйте карту diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 2be8d9db1b..e207afb74d 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -170,7 +170,7 @@ Your wallet has not been backed up Total balance The amount does not include some of your funds - 1INCH tokens will be credited to your %s wallet address within 24 hours + 1INCH tokens will be credited to your %s wallet address within 48 hours There was no purchase of a wallet using your promo code, which means you cannot receive a bonus. Buy Tangem wallet, scan it in the app, and get the bonus. To access all the networks you need to scan the card Scan your card diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/InputNumberFormatter.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/InputNumberFormatter.kt index 988e827efd..7ed7c4a7c4 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/InputNumberFormatter.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/InputNumberFormatter.kt @@ -20,13 +20,22 @@ class InputNumberFormatter( val thousandsSeparator = symbols.groupingSeparator val decimalSeparator = symbols.decimalSeparator - if (text.startsWith("0") && text.length > 1 && text[1] != decimalSeparator) { + val lastChar = text.lastOrNull() + val trimmedText = if (text.isNotEmpty() && (lastChar == thousandsSeparator || lastChar == POINT_SEPARATOR)) { + text.dropLast(1) + decimalSeparator + } else { + text + } + + if (trimmedText.startsWith("0") && trimmedText.length > 1 && trimmedText[1] != decimalSeparator) { return "0" } - val filteredChars = text.replace(thousandsSeparator.toString(), "").filterIndexed { index, c -> - val isOneOrZeroPoint = c == decimalSeparator && index != 0 && text.count { it == decimalSeparator } <= 1 - val isIndexPointIndex = c == decimalSeparator && index != 0 && text.indexOf(decimalSeparator) == index + val filteredChars = trimmedText.replace(thousandsSeparator.toString(), "").filterIndexed { index, c -> + val isOneOrZeroPoint = + c == decimalSeparator && index != 0 && trimmedText.count { it == decimalSeparator } <= 1 + val isIndexPointIndex = + c == decimalSeparator && index != 0 && trimmedText.indexOf(decimalSeparator) == index c.isDigit() || isIndexPointIndex || isOneOrZeroPoint } // If dot is present, take first digits before decimal and first decimals digits after decimal @@ -64,5 +73,6 @@ class InputNumberFormatter( companion object { private const val TEXT_CHUNK_THOUSAND = 3 + private const val POINT_SEPARATOR = '.' } } \ No newline at end of file diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/analytics/Learn2earnEvents.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/analytics/Learn2earnEvents.kt index 100a19ef0a..5e4a01d002 100644 --- a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/analytics/Learn2earnEvents.kt +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/analytics/Learn2earnEvents.kt @@ -38,7 +38,10 @@ internal sealed class Learn2earnEvents( params = mapOf(AnalyticsParam.CLIENT_TYPE to clientType.value), ) - class NoticeClaimSuccess : MainScreen("Notice - Claim Successed") + class NoticeClaimSuccess(clientType: AnalyticsParam.ClientType) : MainScreen( + event = "Notice - Successful Claim", + params = mapOf(AnalyticsParam.CLIENT_TYPE to clientType.value), + ) } sealed class PromoScreen( @@ -65,6 +68,6 @@ internal sealed class AnalyticsParam { } companion object Key { - const val CLIENT_TYPE = "Client type" + const val CLIENT_TYPE = "Client Type" } } \ 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 index c2cfa092b7..e7988b5050 100644 --- 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 @@ -29,7 +29,7 @@ import timber.log.Timber /** [REDACTED_AUTHOR] */ -@Suppress("LongParameterList") +@Suppress("LongParameterList", "LargeClass") internal class DefaultLearn2earnInteractor( private val featureToggleManager: Learn2earnFeatureToggleManager, private val repository: Learn2earnRepository, @@ -43,19 +43,22 @@ internal class DefaultLearn2earnInteractor( 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 isTangemWalletFlowSync = false + private var isTangemWalletSync = false private val webViewUriBuilder: WebViewUriBuilder by lazy { WebViewUriBuilder( authCredentialsProvider = dependencyProvider.getWebViewAuthCredentialsProvider(), localeLanguageProvider = dependencyProvider.getLocaleProvider(), - promoCodeProvider = { repository.getUserData().promoCode }, + promoCodeProvider = { promoUserData.promoCode }, ) } @@ -70,12 +73,13 @@ internal class DefaultLearn2earnInteractor( override fun getIsTangemWalletFlow(): Flow = isTangemWalletFlow override fun isUserHadPromoCode(): Boolean { - return repository.getUserData().promoCode != null + return promoUserData.promoCode != null } override suspend fun validateUserWallet(): Result { - val promoCode = repository.getUserData().promoCode + 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 @@ -92,14 +96,13 @@ internal class DefaultLearn2earnInteractor( } override fun isUserRegisteredInPromotion(): Boolean { - return repository.getUserData().isRegisteredInPromotion + return promoUserData.isRegisteredInPromotion } override fun getAwardAmount(): Int = try { - val promoCode = repository.getUserData().promoCode - promotion.getPromotionInfo().getData(promoCode).award.toInt() + promotion.getPromotionInfo().getCardData().award.toInt() } catch (ex: NullPointerException) { - Timber.e(ex) + Timber.w(ex) 0 } @@ -114,7 +117,7 @@ internal class DefaultLearn2earnInteractor( ?: return Result.failure(PromotionError.UnknownError("Currency for award is null")) val walletId = userWalletManager.getWalletId() - val promoCode = repository.getUserData().promoCode + val promoCode = promoUserData.promoCode val domainError = if (promoCode == null) { requestAward(walletId, awardCurrency) @@ -199,11 +202,11 @@ internal class DefaultLearn2earnInteractor( } override fun buildUriForNewUser(): Uri { - return webViewUriBuilder.buildUriForNewUser(repository.getUserData().isLearningStageFinished) + return webViewUriBuilder.buildUriForNewUser(promoUserData.isLearningStageFinished) } override fun buildUriForOldUser(): Uri { - return webViewUriBuilder.buildUriForOldUser(repository.getUserData().isLearningStageFinished) + return webViewUriBuilder.buildUriForOldUser(promoUserData.isLearningStageFinished) } override fun getBasicAuthHeaders(): ArrayList { @@ -251,8 +254,8 @@ internal class DefaultLearn2earnInteractor( val isActive = when { demoModeDatasource.isDemoModeActive -> false !featureToggleManager.isLearn2earnEnabled -> false - repository.getUserData().isAlreadyReceivedAward -> false - else -> !promotion.isError() + promoUserData.isAlreadyReceivedAward -> false + else -> promotion.isReadyToUse() } return isActive @@ -267,10 +270,18 @@ internal class DefaultLearn2earnInteractor( } override fun isPromotionActiveOnMain(): Boolean { - return when { - !isPromotionActive() -> false - !isTangemWalletFlowSync -> false - else -> promotion.getPromotionInfo().oldCard.status == PromotionInfoResponse.Status.ACTIVE + 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 + } } } @@ -325,25 +336,35 @@ internal class DefaultLearn2earnInteractor( ) } - private fun updateUserData(updateBlock: (PromoUserData) -> PromoUserData): PromoUserData { - return updateBlock(repository.getUserData()).apply { - repository.updateUserData(this) - } - } - private fun subscribeToCardTypeResolverFlow() { if (isTangemWalletFlowJob == null || isTangemWalletFlowJob?.isCancelled == true) { isTangemWalletFlowJob = dependencyProvider.getCardTypeResolverFlow() .onEach { - isTangemWalletFlowSync = it?.isTangemWallet() ?: false - isTangemWalletFlow.emit(isTangemWalletFlowSync) + val isTangemWallet = it?.isTangemWallet() ?: false + isTangemWalletSync = isTangemWallet + isTangemWalletFlow.emit(isTangemWallet) } .launchIn(scope) } } - private fun Promotion.PromotionInfo.getData(promoCode: String?): PromotionInfoResponse.Data { - return if (promoCode == null) { + // 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 @@ -360,4 +381,5 @@ internal class DefaultLearn2earnInteractor( WebViewResult.Empty -> WebViewAction.PROCEED } } + // endregion Utils } \ 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 index 463001cb21..98bf0e9474 100644 --- 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 @@ -13,8 +13,6 @@ internal data class Promotion( @Throws(NullPointerException::class) fun getPromotionInfo(): PromotionInfo = info!! - fun isError(): Boolean = error != null - data class PromotionInfo( val newCard: PromotionInfoResponse.Data, val oldCard: PromotionInfoResponse.Data, 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 index 18d8e79293..e836250fe2 100644 --- 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 @@ -20,6 +20,7 @@ sealed class PromotionError(val code: Int, val description: String) : Throwable( 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") 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 index fe171ddbda..875d16a031 100644 --- 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 @@ -55,12 +55,10 @@ class Learn2earnViewModel @Inject constructor( viewModelScope.launch(dispatchers.io) { interactor.getIsTangemWalletFlow() .collect { isTangemWallet -> - updateUi { - uiState - .updateGetBonusVisibility( - isVisible = isTangemWallet && interactor.isPromotionActiveOnMain(), - ) - .changeGetBonusDescription(getBonusDescription()) + if (isTangemWallet) { + updateMainScreenViews() + } else { + updateUi { uiState.updateGetBonusVisibility(isVisible = false) } } } } @@ -103,7 +101,7 @@ class Learn2earnViewModel @Inject constructor( ) } .onFailure { - Timber.e(it) + Timber.w(it) updateUi { uiState.updateGetBonusVisibility(isVisible = false) } } } @@ -119,7 +117,9 @@ class Learn2earnViewModel @Inject constructor( -> { updateUi { uiState.updateViewsVisibility(isVisible = false) } } - is PromotionError.CodeWasNotAppliedInShop -> { + is PromotionError.CodeWasNotAppliedInShop, + is PromotionError.EmptyUserWalletId, + -> { updateUi { uiState.updateGetBonusVisibility(isVisible = false) } } is PromotionError.CodeNotFound, @@ -141,11 +141,11 @@ class Learn2earnViewModel @Inject constructor( } private fun onButtonMainClick() { - if (interactor.isUserHadPromoCode() || interactor.isUserRegisteredInPromotion()) { - analytics.send(Learn2earnEvents.MainScreen.NoticeLear2earn(AnalyticsParam.ClientType.Old())) + val isUserHasPromo = interactor.isUserHadPromoCode() + analytics.send(Learn2earnEvents.MainScreen.NoticeLear2earn(getAnalyticsClientType(isUserHasPromo))) + if (isUserHasPromo || interactor.isUserRegisteredInPromotion()) { requestAward() } else { - analytics.send(Learn2earnEvents.MainScreen.NoticeLear2earn(AnalyticsParam.ClientType.New())) subscribeToWebViewResultEvents() router.openWebView(interactor.buildUriForOldUser(), interactor.getBasicAuthHeaders()) } @@ -159,7 +159,13 @@ class Learn2earnViewModel @Inject constructor( .onSuccess { requestAwardResult -> requestAwardResult.fold( onSuccess = { - analytics.send(Learn2earnEvents.MainScreen.NoticeClaimSuccess()) + analytics.send( + Learn2earnEvents.MainScreen.NoticeClaimSuccess( + clientType = getAnalyticsClientType( + isUserHasPromo = interactor.isUserHadPromoCode(), + ), + ), + ) val onHideDialog = { uiState = uiState.updateViewsVisibility(isVisible = false) .hideDialog() @@ -173,9 +179,13 @@ class Learn2earnViewModel @Inject constructor( }, onFailure = { val errorDialog = createErrorDialog(it.toDomainError()) - updateUi { - uiState.updateProgress(showProgress = false) - .showDialog(errorDialog) + if (errorDialog == null) { + updateUi { uiState.updateProgress(showProgress = false) } + } else { + updateUi { + uiState.updateProgress(showProgress = false) + .showDialog(errorDialog) + } } }, ) @@ -184,8 +194,9 @@ class Learn2earnViewModel @Inject constructor( updateUi { uiState.updateProgress(showProgress = false) } if (exception is UserCancelledException) return@launch - val errorDialog = createErrorDialog(exception.toDomainError()) - updateUi { uiState.showDialog(errorDialog) } + createErrorDialog(exception.toDomainError())?.let { + updateUi { uiState.showDialog(it) } + } } } } @@ -198,7 +209,15 @@ class Learn2earnViewModel @Inject constructor( } } - private fun createErrorDialog(error: PromotionError): MainScreenState.Dialog { + 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() } @@ -237,6 +256,7 @@ class Learn2earnViewModel @Inject constructor( onDismissRequest = onHideDialog, ) } + is PromotionError.EmptyUserWalletId -> null } } diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt index c82675b1db..72f8074791 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt @@ -19,7 +19,7 @@ interface UserWalletManager { fun getNativeTokenForNetwork(networkId: String): Currency /** - * Returns user walletId + * Returns user walletId or empty string */ fun getWalletId(): String