diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index fad890b2a0..b791bd4cf6 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit fad890b2a0b552be60d124949ca0a3a4d672dac1 +Subproject commit b791bd4cf6c5eca9778f89e87cd62b72d24f5ce9 diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 1c4395d748..0d64c958e4 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -289,9 +289,9 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac store.dispatchOnMain(WelcomeAction.SetInitialIntent(intentWhichStartedActivity)) scope.launch { val handler = BackgroundScanIntentHandler(hasSavedUserWalletsProvider = { true }) - val isBackgroundScanNotHandled = handler.handleIntent(intentWhichStartedActivity) + val isBackgroundScanHandled = handler.handleIntent(intentWhichStartedActivity) val hasNotIncompletedBackup = !backupService.hasIncompletedBackup - if (isBackgroundScanNotHandled && hasNotIncompletedBackup) { + if (!isBackgroundScanHandled && hasNotIncompletedBackup) { store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics) } } diff --git a/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt b/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt index 335228e10f..3032ef611a 100644 --- a/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt +++ b/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt @@ -18,7 +18,8 @@ import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope import java.math.BigDecimal -import java.util.* +import java.util.Currency +import java.util.UUID class TangemShopService(application: Application, shopifyShop: ShopifyShop) { @@ -82,16 +83,18 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) { suspend fun checkIfGooglePayAvailable(googlePayService: GooglePayService): Result { this.googlePayService = googlePayService - return googlePayService.checkIfGooglePayAvailable() + return googlePayService.checkIfGooglePayAvailable(shopifyService.shop.merchantID != null) } fun buyWithGooglePay(productType: ProductType) { - val totalPrice = checkouts[productType]!!.totalPriceV2.amount - googlePayService.payWithGooglePay( - totalPriceCents = totalPrice, - currencyCode = checkouts[productType]!!.currencyCode.name, - merchantID = shopifyService.shop.merchantID, - ) + shopifyService.shop.merchantID?.let { merchantId -> + val totalPrice = checkouts[productType]!!.totalPriceV2.amount + googlePayService.payWithGooglePay( + totalPriceCents = totalPrice, + currencyCode = checkouts[productType]!!.currencyCode.name, + merchantID = merchantId, + ) + } } // fun subscribeToGooglePayResult( @@ -185,7 +188,6 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) { ), appliedDiscount = it.getAppliedDiscount(), ), - ) } return Result.failure(result.exceptionOrNull()!!) diff --git a/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayService.kt b/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayService.kt index 757b207741..7526224414 100644 --- a/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayService.kt +++ b/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayService.kt @@ -6,11 +6,7 @@ import android.app.Activity.RESULT_OK import android.content.Intent import android.util.Log import com.google.android.gms.common.api.ApiException -import com.google.android.gms.wallet.AutoResolveHelper -import com.google.android.gms.wallet.IsReadyToPayRequest -import com.google.android.gms.wallet.PaymentData -import com.google.android.gms.wallet.PaymentDataRequest -import com.google.android.gms.wallet.PaymentsClient +import com.google.android.gms.wallet.* import com.tangem.common.core.TangemSdkError import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -24,7 +20,8 @@ class GooglePayService(private val paymentsClient: PaymentsClient, private val a // var responseCallback: ((Result) -> Unit)? = null - suspend fun checkIfGooglePayAvailable(): Result { + suspend fun checkIfGooglePayAvailable(isMerchantAvailable: Boolean): Result { + if (!isMerchantAvailable) return Result.success(false) val isReadyToPayJson = GooglePayUtil.isReadyToPayRequest() ?: return Result.success(false) val request = IsReadyToPayRequest.fromJson(isReadyToPayJson.toString()) diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index 2ac23842a6..5bac8dc2b2 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -7,6 +7,7 @@ import com.tangem.TangemSdk import com.tangem.blockchain.common.Blockchain import com.tangem.common.* import com.tangem.common.biometric.BiometricManager +import com.tangem.common.card.FirmwareVersion import com.tangem.common.core.* import com.tangem.common.extensions.ByteArrayKey import com.tangem.common.usersCode.UserCodeRepository @@ -230,4 +231,28 @@ class TangemSdkManager(private val cardSdkConfigRepository: CardSdkConfigReposit fun getString(@StringRes stringResId: Int, vararg formatArgs: Any?): String { return resources.getString(stringResId, *formatArgs) } + + fun setAccessCodeRequestPolicy(useBiometricsForAccessCode: Boolean) { + tangemSdk.config.userCodeRequestPolicy = if (useBiometricsForAccessCode) { + UserCodeRequestPolicy.AlwaysWithBiometrics(codeType = UserCodeType.AccessCode) + } else { + UserCodeRequestPolicy.Default + } + } + + fun useBiometricsForAccessCode(): Boolean { + val policy = tangemSdk.config.userCodeRequestPolicy + return policy is UserCodeRequestPolicy.AlwaysWithBiometrics && policy.codeType == UserCodeType.AccessCode + } + + companion object { + val config = Config( + linkedTerminal = true, + allowUntrustedCards = true, + filter = CardFilter( + allowedCardTypes = FirmwareVersion.FirmwareType.values().toList(), + maxFirmwareVersion = FirmwareVersion(major = 6, minor = 21), + ), + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt index fc7bdedd39..b986f9f247 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt @@ -64,7 +64,7 @@ sealed class TapSdkError(override val messageResId: Int?) : TangemError(code = 5 override var customMessage: String = code.toString() object CardForDifferentApp : TapSdkError(R.string.alert_unsupported_card) - object CardNotSupportedByRelease : TapSdkError(R.string.error_update_app) + object CardNotSupportedByRelease : TapSdkError(R.string.error_wrong_card_type) } fun TapErrors.assembleErrors(): MutableList?>> { diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt index e891d0dd2c..ec38e494bc 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt @@ -9,6 +9,7 @@ import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction +import com.tangem.domain.common.TapWorkarounds.canSkipBackup import com.tangem.domain.common.extensions.withMainContext import com.tangem.domain.common.util.twinsIsTwinned import com.tangem.domain.models.scan.ScanResponse @@ -153,7 +154,13 @@ internal object LegacyScanProcessor { if (OnboardingHelper.isOnboardingCase(scanResponse)) { Analytics.addContext(scanResponse) onWalletNotCreated() - store.dispatchOnMain(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = true)) + // must check skip backup using card canSkipBackup + store.dispatchOnMain( + GlobalAction.Onboarding.Start( + scanResponse = scanResponse, + canSkipBackup = scanResponse.card.canSkipBackup, + ), + ) val appScreen = OnboardingHelper.whereToNavigate(scanResponse) navigateTo(appScreen) { onProgressStateChange(it) } } else { diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/chains/CheckForOnboardingChain.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/chains/CheckForOnboardingChain.kt index baa5c93445..4f5729135c 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/chains/CheckForOnboardingChain.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/chains/CheckForOnboardingChain.kt @@ -7,6 +7,7 @@ import com.tangem.core.analytics.Analytics import com.tangem.core.navigation.AppScreen import com.tangem.data.source.preferences.PreferencesDataSource import com.tangem.domain.card.ScanCardException +import com.tangem.domain.common.TapWorkarounds.canSkipBackup import com.tangem.domain.common.util.twinsIsTwinned import com.tangem.domain.core.chain.Chain import com.tangem.domain.models.scan.ScanResponse @@ -50,7 +51,13 @@ class CheckForOnboardingChain( return when { OnboardingHelper.isOnboardingCase(previousChainResult) -> { Analytics.addContext(previousChainResult) - store.dispatchOnMain(GlobalAction.Onboarding.Start(previousChainResult, canSkipBackup = true)) + // must check skip backup using card canSkipBackup + store.dispatchOnMain( + GlobalAction.Onboarding.Start( + scanResponse = previousChainResult, + canSkipBackup = previousChainResult.card.canSkipBackup, + ), + ) val appScreen = OnboardingHelper.whereToNavigate(previousChainResult) ScanChainException.OnboardingNeeded(appScreen).left() } diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt index 663e931472..c3f23eb9b4 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt @@ -51,7 +51,7 @@ class ScanProductTask( } val cardDto = CardDTO(card) - val error = getErrorIfExcludedCard(cardDto) + val error = getErrorIfExcludedCard(cardDto, card) if (error != null) { callback(CompletionResult.Failure(error)) return @@ -81,9 +81,11 @@ class ScanProductTask( } } - private fun getErrorIfExcludedCard(card: CardDTO): TangemError? { - if (card.isExcluded) return TapSdkError.CardForDifferentApp - if (card.isNotSupportedInThatRelease) return TapSdkError.CardNotSupportedByRelease + private fun getErrorIfExcludedCard(cardDto: CardDTO, card: Card): TangemError? { + if (cardDto.isExcluded) return TapSdkError.CardForDifferentApp + if (cardDto.isNotSupportedInThatRelease) return TapSdkError.CardNotSupportedByRelease + // todo check isImported to prevent using old app with imported wallet, remove before wallet 2.0 enabled ([REDACTED_TASK_KEY]) + if (card.wallets.any { it.isImported }) return TapSdkError.CardNotSupportedByRelease return null } } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index 18ec89cb9c..f28f3b06ee 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -100,7 +100,7 @@ internal class BiometricUserWalletsListManager( } else { val isWalletSaved = state.value.userWallets .any { - it.walletId == userWallet.walletId || it.cardsInWallet.contains(userWallet.cardId) + it.walletId == userWallet.walletId } if (isWalletSaved) { diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/WalletConnectRepositoryImpl.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/WalletConnectRepositoryImpl.kt index dcd79b0985..9890f9863f 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/WalletConnectRepositoryImpl.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/WalletConnectRepositoryImpl.kt @@ -6,6 +6,7 @@ import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.WalletConnect import com.tangem.tap.domain.walletconnect2.domain.WalletConnectRepository import com.tangem.tap.domain.walletconnect2.domain.WcJrpcRequestsDeserializer +import com.tangem.tap.domain.walletconnect2.domain.WcRequest import com.tangem.tap.domain.walletconnect2.domain.models.* import com.walletconnect.android.Core import com.walletconnect.android.CoreClient @@ -87,7 +88,10 @@ class WalletConnectRepositoryImpl @Inject constructor( private fun defineWalletDelegate(): Web3Wallet.WalletDelegate { return object : Web3Wallet.WalletDelegate { - override fun onSessionProposal(sessionProposal: Wallet.Model.SessionProposal) { + override fun onSessionProposal( + sessionProposal: Wallet.Model.SessionProposal, + verifyContext: Wallet.Model.VerifyContext, + ) { // Triggered when wallet receives the session proposal sent by a Dapp Timber.d("sessionProposal: $sessionProposal") this@WalletConnectRepositoryImpl.sessionProposal = sessionProposal @@ -106,7 +110,10 @@ class WalletConnectRepositoryImpl @Inject constructor( } } - override fun onSessionRequest(sessionRequest: Wallet.Model.SessionRequest) { + override fun onSessionRequest( + sessionRequest: Wallet.Model.SessionRequest, + verifyContext: Wallet.Model.VerifyContext, + ) { // Triggered when a Dapp sends SessionRequest to sign a transaction or a message Timber.d("sessionRequest: $sessionRequest") val request = wcRequestDeserializer.deserialize( @@ -115,22 +122,38 @@ class WalletConnectRepositoryImpl @Inject constructor( ) Timber.d("sessionRequestParsed: $request") - scope.launch { - _events.emit( - WalletConnectEvents.SessionRequest( - request = request, - chainId = sessionRequest.chainId, + when (request) { + is WcRequest.AddChain -> { + // we can send approval automatically, because in WC 2.0 the list of chains is approved when + // initial connection is established + sendRequest( topic = sessionRequest.topic, id = sessionRequest.request.id, - metaUrl = sessionRequest.peerMetaData?.url ?: "", - metaName = sessionRequest.peerMetaData?.name ?: "", - ), - ) + result = "", + ) + } + else -> + scope.launch { + _events.emit( + WalletConnectEvents.SessionRequest( + request = request, + chainId = sessionRequest.chainId, + topic = sessionRequest.topic, + id = sessionRequest.request.id, + metaUrl = sessionRequest.peerMetaData?.url ?: "", + metaName = sessionRequest.peerMetaData?.name ?: "", + ), + ) + } } } - override fun onAuthRequest(authRequest: Wallet.Model.AuthRequest) { + override fun onAuthRequest( + authRequest: Wallet.Model.AuthRequest, + verifyContext: Wallet.Model.VerifyContext, + ) { // Triggered when Dapp / Requester makes an authorization request + Timber.d("onAuthRequest: $authRequest") } override fun onSessionDelete(sessionDelete: Wallet.Model.SessionDelete) { @@ -174,6 +197,7 @@ class WalletConnectRepositoryImpl @Inject constructor( override fun onError(error: Wallet.Model.Error) { // Triggered whenever there is an issue inside the SDK + Timber.d("onError: $error") } } } diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt index 5bd6d8f0a1..e37d7c2949 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt @@ -227,7 +227,15 @@ class WalletConnectInteractor( } } + fun isWalletConnectUri(uri: String): Boolean { + return uri.lowercase().startsWith(WC_SCHEME) + } + private suspend fun prepareRequestData(sessionRequest: WalletConnectEvents.SessionRequest): WcPreparedRequest? { return sessionRequestConverter.prepareRequest(sessionRequest, userWalletId) } + + companion object { + private const val WC_SCHEME = "wc" + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcJrpcMethods.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcJrpcMethods.kt index 86ae71084c..9a0da6c971 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcJrpcMethods.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcJrpcMethods.kt @@ -1,9 +1,6 @@ package com.tangem.tap.domain.walletconnect2.domain -import com.squareup.moshi.JsonAdapter -import com.squareup.moshi.JsonClass -import com.squareup.moshi.Moshi -import com.squareup.moshi.Types +import com.squareup.moshi.* import com.tangem.datasource.di.SdkMoshi import com.tangem.tap.domain.walletconnect2.domain.models.binance.WCBinanceTxConfirmParam import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceCancelOrder @@ -24,6 +21,7 @@ enum class WcJrpcMethods(val code: String) { BNB_SIGN("bnb_sign"), BNB_TRANSACTION_CONFIRM("bnb_tx_confirmation"), SIGN_TRANSACTION("trust_signTransaction"), + WALLET_ADD_ETHEREUM_CHAIN("wallet_addEthereumChain"), ; companion object { @@ -33,13 +31,25 @@ enum class WcJrpcMethods(val code: String) { @JsonClass(generateAdapter = true) data class WCSignTransaction( + @Json(name = "network") val network: Int, + + @Json(name = "transaction") val transaction: String, ) : WcRequestData +@JsonClass(generateAdapter = true) +data class WcAddChain( + @Json(name = "chainId") + val chainId: String, +) : WcRequestData + @JsonClass(generateAdapter = true) data class WcEthereumSignMessage( + @Json(name = "raw") val raw: List, + + @Json(name = "type") val type: WCSignType, ) : WcRequestData { enum class WCSignType { @@ -71,15 +81,34 @@ data class WcEthereumSignMessage( @JsonClass(generateAdapter = true) data class WcEthereumTransaction( + @Json(name = "from") val from: String, + + @Json(name = "to") val to: String?, + + @Json(name = "nonce") val nonce: String?, + + @Json(name = "gasPrice") val gasPrice: String?, + + @Json(name = "maxFeePerGas") val maxFeePerGas: String?, + + @Json(name = "maxPriorityFeePerGas") val maxPriorityFeePerGas: String?, + + @Json(name = "gas") val gas: String?, + + @Json(name = "gasLimit") val gasLimit: String?, + + @Json(name = "value") val value: String?, + + @Json(name = "data") val data: String, ) : WcRequestData @@ -96,6 +125,7 @@ sealed class WcRequest(open val data: WcRequestData) { data class BnbTransfer(override val data: WcBinanceTransferOrder) : WcRequest(data) data class BnbTxConfirm(override val data: WCBinanceTxConfirmParam) : WcRequest(data) data class SignTransaction(override val data: WCSignTransaction) : WcRequest(data) + data class AddChain(override val data: WcAddChain) : WcRequest(data) data class CustomRequest(override val data: WcCustomRequestData) : WcRequest(data) } @@ -167,6 +197,12 @@ class WcJrpcRequestsDeserializer @Inject constructor(@SdkMoshi private val moshi ).fromJsonFirstOrNull(params) ?: return customRequest WcRequest.SignTransaction(data = deserializedParams) } + WcJrpcMethods.WALLET_ADD_ETHEREUM_CHAIN -> { + val deserializedParams: WcAddChain = moshi.adapter>( + Types.newParameterizedType(List::class.java, WcAddChain::class.java), + ).fromJsonFirstOrNull(params) ?: return customRequest + WcRequest.AddChain(data = deserializedParams) + } } } diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt index 802ad1b140..b3b5057a76 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt @@ -70,7 +70,7 @@ class WalletConnectMiddleware { } is WalletConnectAction.StartWalletConnect -> { val uri = action.copiedUri - if (uri != null && WalletConnectManager.isCorrectWcUri(uri)) { + if (uri != null && isWalletConnectUri(uri)) { store.dispatchOnMain(WalletConnectAction.ShowClipboardOrScanQrDialog(uri)) } else { store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.QrScan)) @@ -442,4 +442,8 @@ class WalletConnectMiddleware { ) return walletState.getWalletManager(blockchainNetwork) } + + private fun isWalletConnectUri(uri: String): Boolean { + return WalletConnectManager.isCorrectWcUri(uri) || walletConnectInteractor.isWalletConnectUri(uri) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index baf5ed2fcf..78f8cc873e 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -6,6 +6,7 @@ import com.tangem.common.extensions.guard import com.tangem.core.analytics.Analytics import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction +import com.tangem.domain.common.TapWorkarounds.canSkipBackup import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.common.util.twinsIsTwinned import com.tangem.domain.models.scan.ProductType @@ -37,7 +38,10 @@ object OnboardingHelper { } } - response.cardTypesResolver.isWallet2() -> { + // TODO for Shiba disabled check wallet 2, and only check canSkipBackup, enable when release wallet 2 + // ([REDACTED_TASK_KEY]) + // response.cardTypesResolver.isWallet2() -> { + !response.card.canSkipBackup -> { val emptyWallets = response.card.wallets.isEmpty() val activationInProgress = cardInfoStorage.isActivationInProgress(cardId) val backupNotActive = response.card.backupStatus?.isActive != true diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index 2057707f3d..2ce6725c37 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -9,6 +9,7 @@ import com.tangem.core.analytics.Analytics import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction import com.tangem.domain.common.BlockchainNetwork +import com.tangem.domain.common.TapWorkarounds.canSkipBackup import com.tangem.domain.common.extensions.withMainContext import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.models.scan.CardDTO @@ -340,7 +341,9 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) is CompletionResult.Failure -> { val error = result.error if (error is TangemSdkError.BackupFailedNotEmptyWallets && - onboardingWalletState.wallet2State != null + // todo disabled this check in task with shiba ([REDACTED_TASK_KEY]) and added canSkipBackup + // && onboardingWalletState.wallet2State != null + card?.canSkipBackup == false ) { store.dispatchOnMain( GlobalAction.ShowDialog( diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/utils/WalletDataOperations.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/utils/WalletDataOperations.kt index 9750bb072f..7a43921862 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/utils/WalletDataOperations.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/utils/WalletDataOperations.kt @@ -12,6 +12,7 @@ import com.tangem.tap.common.extensions.toFormattedCryptoCurrencyString import com.tangem.tap.common.extensions.toFormattedFiatValue import com.tangem.tap.domain.model.WalletDataModel import com.tangem.tap.domain.model.WalletStoreModel +import com.tangem.tap.features.wallet.models.PendingTransactionType import com.tangem.tap.features.wallet.models.WalletWarning import com.tangem.tap.features.wallet.redux.WalletMainButton import com.tangem.tap.features.wallet.redux.utils.UNKNOWN_AMOUNT_SIGN @@ -26,15 +27,14 @@ internal fun WalletDataModel.mainButton(blockchainAmount: BigDecimal): WalletMai internal fun WalletDataModel.hasPendingTransactions(): Boolean { // for now check pending ongoing only just for BTC, later test and add other utxo networks - // disabled for release 4.8, test and enable in 4.9 - // val isBitcoinBlockchain = - // currency.blockchain == Blockchain.Bitcoin || currency.blockchain == Blockchain.BitcoinTestnet - // if (currency.isBlockchain() && isBitcoinBlockchain) { - // val outgoingTransactions = status.pendingTransactions.filter { - // it.type == PendingTransactionType.Outgoing - // } - // return outgoingTransactions.isEmpty() - // } + val isBitcoinBlockchain = + currency.blockchain == Blockchain.Bitcoin || currency.blockchain == Blockchain.BitcoinTestnet + if (currency.isBlockchain() && isBitcoinBlockchain) { + val outgoingTransactions = status.pendingTransactions.filter { + it.type == PendingTransactionType.Outgoing + } + return outgoingTransactions.isEmpty() + } return status.pendingTransactions.isEmpty() } diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt index 0754f6c305..472fd9e6a5 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt @@ -7,7 +7,6 @@ import com.tangem.common.services.Result import com.tangem.common.services.performRequest import com.tangem.datasource.api.common.createRetrofitInstance import com.tangem.domain.common.extensions.withIOContext -import com.tangem.tap.common.extensions.urlEncode import com.tangem.tap.common.redux.global.CryptoCurrencyName import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager @@ -103,39 +102,37 @@ class MoonPayService( cryptoCurrencyName: CryptoCurrencyName, fatCurrency: String, walletAddress: String, - ): String? { + ): String { if (action == CurrencyExchangeManager.Action.Buy) throw UnsupportedOperationException() val uri = Uri.Builder() .scheme(SCHEME) .authority(URL_SELL) - .appendQueryParameter("apiKey", apiKey.urlEncode()) - .appendQueryParameter("baseCurrencyCode", cryptoCurrencyName.urlEncode()) - .appendQueryParameter("refundWalletAddress", walletAddress.urlEncode()) - .appendQueryParameter("redirectURL", "tangem://sell-request.tangem.com".urlEncode()) + .appendQueryParameter("apiKey", apiKey) + .appendQueryParameter("baseCurrencyCode", cryptoCurrencyName) + .appendQueryParameter("refundWalletAddress", walletAddress) + .appendQueryParameter("redirectURL", "tangem://sell-request.tangem.com") - val originalQuery = uri.build().toString() + val originalQuery = uri.build().encodedQuery ?: uri.build().toString() val signature = createSignature(originalQuery) - uri.appendQueryParameter("signature", signature.urlEncode()) + uri.appendQueryParameter("signature", signature) - val url = uri.build().toString() - return url + return uri.build().toString() } - override fun getSellCryptoReceiptUrl(action: CurrencyExchangeManager.Action, transactionId: String): String? { - val url = Uri.Builder() + override fun getSellCryptoReceiptUrl(action: CurrencyExchangeManager.Action, transactionId: String): String { + return Uri.Builder() .scheme(SCHEME) .authority(URL_SELL) .appendPath("transaction_receipt") .appendQueryParameter("transactionId", transactionId).build().toString() - return url } private fun createSignature(data: String): String { val sha256Hmac = Mac.getInstance("HmacSHA256") val secretKey = SecretKeySpec(secretKey.toByteArray(), "HmacSHA256") sha256Hmac.init(secretKey) - val sha256encoded = sha256Hmac.doFinal(data.toByteArray()) + val sha256encoded = sha256Hmac.doFinal("?$data".toByteArray()) return Base64.encodeToString(sha256encoded, Base64.NO_WRAP) } diff --git a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt index 7533a00f58..8ea80aec87 100644 --- a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt +++ b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt @@ -12,6 +12,8 @@ import com.tangem.tap.domain.TangemSdkManager import com.tangem.tap.domain.tokens.UserTokensRepository import com.tangem.tap.domain.walletStores.WalletStoresManager import com.tangem.tap.features.wallet.redux.WalletState +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import org.rekotlin.Store import javax.inject.Inject @@ -22,6 +24,15 @@ import javax.inject.Inject class AppStateHolder @Inject constructor() : WalletsStateHolder, NavigationStateHolder { override var userWalletsListManager: UserWalletsListManager? = null + set(value) { + field = value + _userWalletsListManagerFlow.value = value + } + + override val userWalletListManagerFlow: Flow + get() = _userWalletsListManagerFlow + + private val _userWalletsListManagerFlow = MutableStateFlow(null) @Deprecated("Use scan response from selected user wallet") var scanResponse: ScanResponse? = null diff --git a/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt b/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt index cc2e04969d..cf7f41785a 100644 --- a/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt +++ b/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt @@ -3,6 +3,8 @@ package com.tangem.tap.proxy.di import androidx.compose.ui.text.intl.Locale import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.domain.card.repository.CardSdkConfigRepository +import com.tangem.domain.common.CardTypesResolver +import com.tangem.domain.common.util.cardTypesResolver import com.tangem.feature.learn2earn.domain.api.Learn2earnDependencyProvider import com.tangem.lib.crypto.DerivationManager import com.tangem.lib.crypto.TransactionManager @@ -15,6 +17,8 @@ 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 @Module @@ -62,10 +66,19 @@ class ProxyModule { @Singleton fun provideLear2earnDependencies(appStateHolder: AppStateHolder): Learn2earnDependencyProvider { return object : Learn2earnDependencyProvider { - override fun getUserCountryCodeProvider(): () -> String = { - appStateHolder.mainStore?.state?.globalState?.userCountryCode ?: Locale.current.language + + @OptIn(ExperimentalCoroutinesApi::class) + override fun getCardTypeResolverFlow(): Flow { + return appStateHolder.userWalletListManagerFlow + .flatMapLatest { manager -> + manager?.selectedUserWallet + ?.map { it.scanResponse.cardTypesResolver } + ?: flowOf(null) + } } + override fun getLocaleProvider(): () -> String = { Locale.current.language } + override fun getWebViewAuthCredentialsProvider(): () -> String? = { appStateHolder.mainStore?.state?.globalState?.configManager?.config?.tangemComAuthorization } diff --git a/core/datasource/src/main/java/com/tangem/datasource/config/models/ShopifyShop.kt b/core/datasource/src/main/java/com/tangem/datasource/config/models/ShopifyShop.kt index cdda42d58c..c4a09adcfe 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/config/models/ShopifyShop.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/config/models/ShopifyShop.kt @@ -5,8 +5,10 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class ShopifyShop( + @Json(name = "domain") val domain: String, @Json(name = "storefrontApiKeyAndroid") val storefrontApiKey: String, - val merchantID: String, + @Json(name = "merchantID") + val merchantID: String?, ) \ No newline at end of file diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index 5dc4db710d..b5005743ed 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -9,7 +9,7 @@ }, { "name": "NEW_CARD_SCANNING_ENABLED", - "version": "4.9.0" + "version": "4.10.0" }, { "name": "REDESIGNED_WALLET_SCREEN_ENABLED", @@ -17,7 +17,7 @@ }, { "name": "1INCH_LEARN_2_EARN_ENABLED", - "version": "undefined" + "version": "4.9.0" }, { "name": "REDESIGNED_TOKEN_DETAIL_SCREEN_ENABLED", diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 797b9dea53..00f29ea502 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -68,7 +68,6 @@ Обменять Посмотреть историю транзакций Обозреватель - Учись и получай бонусы! Нравится Основная сеть Нет @@ -139,6 +138,7 @@ Проверьте подключение с интернетом или переключитесь на другую сеть Условия использования К сожалению, текущая версия приложения не готова к работе с этой картой, проверьте наличие обновлений + Данное приложение не предназначено для работы с этой картой или требует обновления Вы использовали карту от другого кошелька. Приложите карту, связанную с этим кошельком. Вы получаете Вы отправляете @@ -163,10 +163,10 @@ Вы обновили данные биометрии, отсканируйте свою карту для входа Вы успешно прошли все уроки и теперь можете получить 1INCH токены - Пройдите 3 урока и получите %d 1INCH токен на свой кошелек - Пройдите 3 урока и получите %d 1INCH токена на свой кошелек - Пройдите 3 урока и получите %d 1INCH токена на свой кошелек - Пройдите 3 урока и получите %d 1INCH токенов на свой кошелек + Пройдите 3 урока и получите %d 1INCH токен на свой кошелек + Пройдите 3 урока и получите %d 1INCH токена на свой кошелек + Пройдите 3 урока и получите %d 1INCH токенов на свой кошелек + Пройдите 3 урока и получите %d 1INCH токенов на свой кошелек Управление токенами Чтобы защитить свои активы, мы советуем вам выполнить эту процедуру @@ -214,6 +214,7 @@ В этом случае вам будет необходимо начать процесс заново. Вы хотите выйти из процесса активации? Подготовка + Другой кошелек уже был создан на карте, которую вы пытаетесь добавить. Хотите сбросить его и использовать карту для бэкапа? Код доступа Подключиться Резервная копия diff --git a/core/res/src/main/res/values-zh-rTW/strings.xml b/core/res/src/main/res/values-zh-rTW/strings.xml index ccb6abe736..0b2d148627 100644 --- a/core/res/src/main/res/values-zh-rTW/strings.xml +++ b/core/res/src/main/res/values-zh-rTW/strings.xml @@ -187,6 +187,7 @@ 這此情況,您必須要重新開始 您想要離開啟用程序嗎? 開始 + 您要添加的卡上已經創建了另一個錢包。你想重置它並將卡用於新錢包嗎 PIN 碼 連接 創建備份 diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index cd659ff14e..2be8d9db1b 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -137,6 +137,7 @@ Check your internet connection or switch to a different network Terms of Service Oops, the current version of the application is not ready to work with this card, please check for updates. + This application is not designed to work with this card or needs to be updated You have used a card from another wallet. Tap the card associated with this wallet You Receive You Send @@ -161,8 +162,8 @@ You have updated biometrics, scan your card to enter You have completed all of the lessons, and are now eligible to receive your 1INCH tokens - Complete three lessons and receive %d 1INCH token to your wallet - Complete three lessons and receive %d 1INCH tokens to your wallet + Complete three lessons and receive %d 1INCH token to your wallet + Complete three lessons and receive %d 1INCH tokens to your wallet Manage tokens To protect your assets, we advise you to carry out this procedure diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/TangemCardTypesResolver.kt b/domain/legacy/src/main/java/com/tangem/domain/common/TangemCardTypesResolver.kt index 39991063b5..9f993a407b 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/common/TangemCardTypesResolver.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/common/TangemCardTypesResolver.kt @@ -29,7 +29,8 @@ internal class TangemCardTypesResolver( } override fun isWallet2(): Boolean { - return card.firmwareVersion >= FirmwareVersion.KeysImportAvailable && card.settings.isKeysImportAllowed + // todo for now disabled to prevent Shiba cards using as wallet 2, enable when release wallet 2.0 ([REDACTED_TASK_KEY]) + return false // card.firmwareVersion >= FirmwareVersion.KeysImportAvailable && card.settings.isKeysImportAllowed } override fun isTangemTwins(): Boolean = productType == ProductType.Twins diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/TapWorkarounds.kt b/domain/legacy/src/main/java/com/tangem/domain/common/TapWorkarounds.kt index 5d25521277..ccd30b6eb0 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/common/TapWorkarounds.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/common/TapWorkarounds.kt @@ -3,6 +3,7 @@ package com.tangem.domain.common import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.DerivationStyle import com.tangem.common.card.Card +import com.tangem.common.card.FirmwareVersion import com.tangem.domain.models.scan.CardDTO import java.util.* @@ -23,6 +24,10 @@ object TapWorkarounds { val CardDTO.isTestCard: Boolean get() = batchId == TEST_CARD_BATCH && cardId.startsWith(TEST_CARD_ID_STARTS_WITH) + // for cards 6.21 and higher backup is not skippable + val CardDTO.canSkipBackup: Boolean + get() = this.firmwareVersion < FirmwareVersion.KeysImportAvailable + val CardDTO.useOldStyleDerivation: Boolean get() = batchId == "AC01" || batchId == "AC02" || batchId == "CB95" diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt b/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt index 8c306c3240..da1221c137 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt @@ -190,4 +190,6 @@ fun Blockchain.isSupportedInApp(): Boolean { private val excludedBlockchains = listOf( Blockchain.Unknown, Blockchain.Ducatus, + Blockchain.Telos, // disable in 4.9 + Blockchain.TelosTestnet, // disable in 4.9 ) \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/WalletsStateHolder.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/WalletsStateHolder.kt index 3c7b38bbbe..7aa875c6af 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/WalletsStateHolder.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/WalletsStateHolder.kt @@ -1,6 +1,10 @@ package com.tangem.domain.wallets.legacy +import kotlinx.coroutines.flow.Flow + interface WalletsStateHolder { val userWalletsListManager: UserWalletsListManager? + + val userWalletListManagerFlow: Flow } \ No newline at end of file diff --git a/features/learn2earn/impl/build.gradle.kts b/features/learn2earn/impl/build.gradle.kts index 37925318a5..d85bd7dfba 100644 --- a/features/learn2earn/impl/build.gradle.kts +++ b/features/learn2earn/impl/build.gradle.kts @@ -10,6 +10,7 @@ plugins { dependencies { /** Core modules */ implementation(project(":common")) + implementation(project(":domain:legacy")) implementation(project(":core:analytics")) implementation(projects.core.analytics.models) implementation(project(":core:featuretoggles")) @@ -36,6 +37,7 @@ dependencies { implementation(deps.compose.foundation) implementation(deps.compose.ui) implementation(deps.compose.ui.tooling) + implementation(deps.compose.accompanist.webView) /** Preferences */ implementation(deps.krateSharedPref) diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/data/DefaultLearn2earnRepository.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/data/DefaultLearn2earnRepository.kt index c10f0d57bb..0eefb517be 100644 --- a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/data/DefaultLearn2earnRepository.kt +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/data/DefaultLearn2earnRepository.kt @@ -121,6 +121,7 @@ internal class DefaultLearn2earnRepository( promoCode = null, isRegisteredInPromotion = false, isAlreadyReceivedAward = false, + isLearningStageFinished = false, ) private companion object { diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/data/models/PromoUserData.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/data/models/PromoUserData.kt index 7edf1bc122..b5553c544e 100644 --- a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/data/models/PromoUserData.kt +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/data/models/PromoUserData.kt @@ -5,6 +5,7 @@ package com.tangem.feature.learn2earn.data.models */ data class PromoUserData( val promoCode: String?, + val isLearningStageFinished: Boolean, val isRegisteredInPromotion: Boolean, val isAlreadyReceivedAward: Boolean, ) \ 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 402f88ee82..c2cfa092b7 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 @@ -2,8 +2,10 @@ package com.tangem.feature.learn2earn.domain import android.net.Uri import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.datasource.api.promotion.models.AbstractPromotionResponse 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 @@ -14,27 +16,45 @@ 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") internal class DefaultLearn2earnInteractor( private val featureToggleManager: Learn2earnFeatureToggleManager, private val repository: Learn2earnRepository, private val userWalletManager: UserWalletManager, private val derivationManager: DerivationManager, - private val analyticsEventHandler: AnalyticsEventHandler, - dependencyProvider: Learn2earnDependencyProvider, + private val analytics: AnalyticsEventHandler, + private val demoModeDatasource: DemoModeDatasource, + private val dependencyProvider: Learn2earnDependencyProvider, + dispatchers: AppCoroutineDispatcherProvider, ) : Learn2earnInteractor { override var webViewResultHandler: WebViewResultHandler? = null 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 val webViewUriBuilder: WebViewUriBuilder by lazy { WebViewUriBuilder( authCredentialsProvider = dependencyProvider.getWebViewAuthCredentialsProvider(), - userCountryCodeProvider = dependencyProvider.getUserCountryCodeProvider(), + localeLanguageProvider = dependencyProvider.getLocaleProvider(), promoCodeProvider = { repository.getUserData().promoCode }, ) } @@ -47,28 +67,27 @@ internal class DefaultLearn2earnInteractor( initPromotionInfo() } + override fun getIsTangemWalletFlow(): Flow = isTangemWalletFlow + override fun isUserHadPromoCode(): Boolean { return repository.getUserData().promoCode != null } - override fun isNeedToShowViewOnStoriesScreen(): Boolean { - return promotionIsActive() - } - - override suspend fun isNeedToShowViewOnMainScreen(): Boolean { - if (!promotionIsActive()) return false - + override suspend fun validateUserWallet(): Result { val promoCode = repository.getUserData().promoCode val userWalletId = userWalletManager.getWalletId() - return if (promoCode == null) { - val response = repository.validate(userWalletId) - response.valid == true + + val domainError = if (promoCode == null) { + repository.validate(userWalletId).error } else { - val response = repository.validateCode(userWalletId, promoCode) - when (val error = response.error?.toDomainError()) { - null -> response.valid == true - else -> error !is PromotionError.CodeWasNotAppliedInShop - } + repository.validateCode(userWalletId, promoCode).error + }?.toDomainError() + + return if (domainError == null) { + Result.success(Unit) + } else { + handlePromotionError(domainError) + Result.failure(domainError) } } @@ -76,11 +95,12 @@ internal class DefaultLearn2earnInteractor( return repository.getUserData().isRegisteredInPromotion } - override fun getAwardAmount(): Int { + override fun getAwardAmount(): Int = try { val promoCode = repository.getUserData().promoCode - val awardAmount = promotion.getPromotionInfo().getData(promoCode).award.toInt() - - return awardAmount + promotion.getPromotionInfo().getData(promoCode).award.toInt() + } catch (ex: NullPointerException) { + Timber.e(ex) + 0 } override fun getAwardNetworkName(): String { @@ -96,22 +116,21 @@ internal class DefaultLearn2earnInteractor( val walletId = userWalletManager.getWalletId() val promoCode = repository.getUserData().promoCode - val error = if (promoCode == null) { + val domainError = if (promoCode == null) { requestAward(walletId, awardCurrency) } else { requestAwardWithPromoCode(walletId, awardCurrency, promoCode) } - return if (error == null) { + return if (domainError == null) { Result.success(Unit) } else { - val domainError = error.toDomainError() handlePromotionError(domainError) Result.failure(domainError) } } - private suspend fun requestAward(walletId: String, awardCurrency: Currency): AbstractPromotionResponse.Error? { + private suspend fun requestAward(walletId: String, awardCurrency: Currency): PromotionError? { val validateResponse = repository.validate(walletId) return if (validateResponse.valid == true) { val address = getWalletAddressForAward(awardCurrency) @@ -124,14 +143,14 @@ internal class DefaultLearn2earnInteractor( } } else { validateResponse.error - } + }?.toDomainError() } private suspend fun requestAwardWithPromoCode( walletId: String, awardCurrency: Currency, promoCode: String, - ): AbstractPromotionResponse.Error? { + ): PromotionError? { val codeValidateResponse = repository.validateCode(walletId, promoCode) return if (codeValidateResponse.valid == true) { val address = getWalletAddressForAward(awardCurrency) @@ -144,7 +163,7 @@ internal class DefaultLearn2earnInteractor( } } else { codeValidateResponse.error - } + }?.toDomainError() } private suspend fun getWalletAddressForAward(currency: Currency): String { @@ -180,11 +199,11 @@ internal class DefaultLearn2earnInteractor( } override fun buildUriForNewUser(): Uri { - return webViewUriBuilder.buildUriForNewUser() + return webViewUriBuilder.buildUriForNewUser(repository.getUserData().isLearningStageFinished) } override fun buildUriForOldUser(): Uri { - return webViewUriBuilder.buildUriForOldUser() + return webViewUriBuilder.buildUriForOldUser(repository.getUserData().isLearningStageFinished) } override fun getBasicAuthHeaders(): ArrayList { @@ -194,11 +213,22 @@ internal class DefaultLearn2earnInteractor( override fun handleRedirect(uri: Uri): WebViewAction { val result = webViewUriParser.parse(uri) when (result) { - is WebViewResult.PromoCode -> { + 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, ) } } @@ -206,7 +236,7 @@ internal class DefaultLearn2earnInteractor( updateUserData { it.copy(isRegisteredInPromotion = true) } } is WebViewResult.Learn2earnAnalyticsEvent -> { - analyticsEventHandler.send(result.event) + analytics.send(result.event) } WebViewResult.Empty -> Unit } @@ -215,21 +245,35 @@ internal class DefaultLearn2earnInteractor( return result.toWebViewAction() } - private fun promotionIsActive(): Boolean { - val userData = repository.getUserData() + override fun isPromotionActive(): Boolean { + subscribeToCardTypeResolverFlow() + val isActive = when { + demoModeDatasource.isDemoModeActive -> false !featureToggleManager.isLearn2earnEnabled -> false - userData.isAlreadyReceivedAward -> false - promotion.isError() -> false - else -> { - val data = promotion.getPromotionInfo().getData(userData.promoCode) - data.status == PromotionInfoResponse.Status.ACTIVE - } + repository.getUserData().isAlreadyReceivedAward -> false + else -> !promotion.isError() } return isActive } + override fun isPromotionActiveOnStories(): Boolean { + return if (isPromotionActive()) { + promotion.getPromotionInfo().newCard.status == PromotionInfoResponse.Status.ACTIVE + } else { + false + } + } + + override fun isPromotionActiveOnMain(): Boolean { + return when { + !isPromotionActive() -> false + !isTangemWalletFlowSync -> false + else -> promotion.getPromotionInfo().oldCard.status == PromotionInfoResponse.Status.ACTIVE + } + } + private suspend fun initPromotionInfo() { promotion = if (featureToggleManager.isLearn2earnEnabled) { repository.getPromotionInfo() @@ -287,20 +331,33 @@ internal class DefaultLearn2earnInteractor( } } + private fun subscribeToCardTypeResolverFlow() { + if (isTangemWalletFlowJob == null || isTangemWalletFlowJob?.isCancelled == true) { + isTangemWalletFlowJob = dependencyProvider.getCardTypeResolverFlow() + .onEach { + isTangemWalletFlowSync = it?.isTangemWallet() ?: false + isTangemWalletFlow.emit(isTangemWalletFlowSync) + } + .launchIn(scope) + } + } + private fun Promotion.PromotionInfo.getData(promoCode: String?): PromotionInfoResponse.Data { return if (promoCode == null) { - newCard - } else { oldCard + } else { + newCard } } private fun WebViewResult.toWebViewAction(): WebViewAction { return when (this) { - WebViewResult.Empty -> WebViewAction.PROCEED + is WebViewResult.NewUserLearningFinished, + WebViewResult.OldUserLearningFinished, + is WebViewResult.Learn2earnAnalyticsEvent, + -> WebViewAction.NOTHING WebViewResult.ReadyForAward -> WebViewAction.FINISH_SESSION - is WebViewResult.Learn2earnAnalyticsEvent -> WebViewAction.NOTHING - is WebViewResult.PromoCode -> WebViewAction.NOTHING + WebViewResult.Empty -> WebViewAction.PROCEED } } } \ 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 index 0b2f6f6450..ac763f5a52 100644 --- 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 @@ -8,12 +8,12 @@ import com.tangem.feature.learn2earn.impl.BuildConfig */ internal class WebViewUriBuilder( private val authCredentialsProvider: () -> String?, - private val userCountryCodeProvider: () -> String, + private val localeLanguageProvider: () -> String, private val promoCodeProvider: () -> String?, ) { - fun buildUriForNewUser(): Uri { - val builder = makeWebViewUriBuilder() + fun buildUriForNewUser(learningIsFinished: Boolean): Uri { + val builder = makeWebViewUriBuilder(learningIsFinished) .appendQueryParameter("type", QUERY_NEW_CARD) promoCodeProvider.invoke()?.let { @@ -23,9 +23,9 @@ internal class WebViewUriBuilder( return builder.build() } - fun buildUriForOldUser(): Uri { - val builder = makeWebViewUriBuilder() - .appendQueryParameter("type", QUERY_EXISTED_CARD) + fun buildUriForOldUser(learningIsFinished: Boolean): Uri { + val builder = makeWebViewUriBuilder(learningIsFinished) + .appendQueryParameter("type", QUERY_EXISTING_CARD) return builder.build() } @@ -39,15 +39,25 @@ internal class WebViewUriBuilder( } } - private fun makeWebViewUriBuilder(): Uri.Builder = Uri.Builder().apply { + private fun makeWebViewUriBuilder(learningIsFinished: Boolean): Uri.Builder = Uri.Builder().apply { scheme(SCHEME) if (BuildConfig.DEBUG) { authority(DEV_BASE_URL) } else { authority(BASE_URL) } - appendPath(userCountryCodeProvider.invoke()) + appendPath(getLocaleLanguage(localeLanguageProvider.invoke())) appendPath(PATH_PROMOTION) + appendQueryParameter(QUERY_FINISHED, learningIsFinished.toString()) + } + + // TODO: locale: This can be used by another feature. Move it to the appropriate location + private fun getLocaleLanguage(language: String): String { + return if (LOCALE_LANG_RU.equals(language, true) || LOCALE_LANG_BY.equals(language, true)) { + LOCALE_LANG_RU + } else { + LOCALE_LANG_EN + } } private companion object { @@ -57,8 +67,13 @@ internal class WebViewUriBuilder( const val PATH_PROMOTION = "promotion" const val QUERY_NEW_CARD = "new-card" - const val QUERY_EXISTED_CARD = "existed-card" + const val QUERY_EXISTING_CARD = "existing-card" + const val QUERY_FINISHED = "finished" const val DEV_BASE_URL = "devweb.tangem.com" + + const val LOCALE_LANG_RU = "ru" + const val LOCALE_LANG_BY = "by" + const val LOCALE_LANG_EN = "en" } } \ No newline at end of file diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/WebViewUriParser.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/WebViewUriParser.kt index f8d1d18e22..d169613478 100644 --- a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/WebViewUriParser.kt +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/WebViewUriParser.kt @@ -1,7 +1,6 @@ package com.tangem.feature.learn2earn.domain import android.net.Uri -import com.tangem.feature.learn2earn.analytics.AnalyticsParam import com.tangem.feature.learn2earn.analytics.Learn2earnEvents import com.tangem.feature.learn2earn.analytics.Learn2earnEvents.PromoScreen import com.tangem.feature.learn2earn.domain.api.WebViewResult @@ -22,14 +21,17 @@ internal class WebViewUriParser( WebViewResult.Learn2earnAnalyticsEvent(event) } } - isPromoCodeRedirect(uri) -> { + isSuccessNewUserRedirect(uri) -> { val promoCode = extractPromoCode(uri) if (promoCode == null) { WebViewResult.Empty } else { - WebViewResult.PromoCode(promoCode) + WebViewResult.NewUserLearningFinished(promoCode) } } + isSuccessOldUserRedirect(uri) -> { + WebViewResult.OldUserLearningFinished + } isReadyForAwardRedirect(uri) -> { WebViewResult.ReadyForAward } @@ -40,13 +42,18 @@ internal class WebViewUriParser( return uri.lastPathSegment == PATH_READY_FOR_AWARD } - private fun isPromoCodeRedirect(uri: Uri): Boolean { - return uri.lastPathSegment == PATH_PROMO_CODE_CREATED && + 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 (isPromoCodeRedirect(uri)) { + return if (isSuccessNewUserRedirect(uri)) { uri.getQueryParameter(QUERY_PROMO_CODE) } else { null @@ -68,16 +75,14 @@ internal class WebViewUriParser( val analyticsEvent = when (event) { EVENT_PROMO_BUY -> PromoScreen.ButtonBuy() - EVENT_PROMO_SUCCESS_NEW_USER -> PromoScreen.SuccessScreenOpened(AnalyticsParam.ClientType.New()) - EVENT_PROMO_SUCCESS_OLD_USER -> PromoScreen.SuccessScreenOpened(AnalyticsParam.ClientType.Old()) else -> null } return analyticsEvent } private companion object { - const val PATH_PROMO_CODE_CREATED = "code-created" - const val PATH_READY_FOR_AWARD = "ready-for-existed-card-award" + 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" @@ -85,7 +90,5 @@ internal class WebViewUriParser( const val QUERY_PROGRAM_NAME = "programName" const val EVENT_PROMO_BUY = "promotion-buy" - const val EVENT_PROMO_SUCCESS_NEW_USER = "promotion-success-new-user" - const val EVENT_PROMO_SUCCESS_OLD_USER = "promotion-success-old-user" } } \ No newline at end of file diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/api/Learn2earnDependencyProvider.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/api/Learn2earnDependencyProvider.kt index b0261884ec..60a23a88fb 100644 --- a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/api/Learn2earnDependencyProvider.kt +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/api/Learn2earnDependencyProvider.kt @@ -1,5 +1,8 @@ package com.tangem.feature.learn2earn.domain.api +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 * @@ -7,7 +10,9 @@ package com.tangem.feature.learn2earn.domain.api */ interface Learn2earnDependencyProvider { - fun getUserCountryCodeProvider(): () -> String + fun getCardTypeResolverFlow(): Flow + + fun getLocaleProvider(): () -> String fun getWebViewAuthCredentialsProvider(): () -> String? } \ 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 index 95bb9d7ad9..85f15629df 100644 --- 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 @@ -1,6 +1,7 @@ package com.tangem.feature.learn2earn.domain.api import android.net.Uri +import kotlinx.coroutines.flow.Flow /** [REDACTED_AUTHOR] @@ -11,11 +12,17 @@ interface Learn2earnInteractor : WebViewRedirectHandler { suspend fun init() + fun getIsTangemWalletFlow(): Flow + fun isUserHadPromoCode(): Boolean - fun isNeedToShowViewOnStoriesScreen(): Boolean + fun isPromotionActive(): Boolean - suspend fun isNeedToShowViewOnMainScreen(): Boolean + fun isPromotionActiveOnStories(): Boolean + + fun isPromotionActiveOnMain(): Boolean + + suspend fun validateUserWallet(): Result fun isUserRegisteredInPromotion(): Boolean diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/api/WebViewResultHandler.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/api/WebViewResultHandler.kt index e9cc14f222..f537006cea 100644 --- a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/api/WebViewResultHandler.kt +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/api/WebViewResultHandler.kt @@ -12,8 +12,14 @@ interface WebViewResultHandler { } sealed class WebViewResult { + object Empty : WebViewResult() - data class PromoCode(val promoCode: String) : WebViewResult() + + data class NewUserLearningFinished(val promoCode: String?) : WebViewResult() + + object OldUserLearningFinished : WebViewResult() + object ReadyForAward : WebViewResult() + data class Learn2earnAnalyticsEvent(val event: AnalyticsEvent) : WebViewResult() } \ 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 index 72c05a6953..ae96ed3668 100644 --- 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 @@ -3,6 +3,7 @@ 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.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 @@ -13,6 +14,7 @@ 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 @@ -45,14 +47,18 @@ internal class Learn2earnDomainModule { userWalletManager: UserWalletManager, analyticsEventHandler: AnalyticsEventHandler, derivationManager: DerivationManager, + demoModeDatasource: DemoModeDatasource, + dispatchers: AppCoroutineDispatcherProvider, ): Learn2earnInteractor { return DefaultLearn2earnInteractor( featureToggleManager = featureToggleManager, repository = repository, userWalletManager = userWalletManager, derivationManager = derivationManager, - analyticsEventHandler = analyticsEventHandler, + analytics = analyticsEventHandler, + demoModeDatasource = demoModeDatasource, dependencyProvider = dependencyProvider, + dispatchers = dispatchers, ) } 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 8c3cbfb221..fe171ddbda 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 @@ -6,18 +6,21 @@ 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 /** @@ -42,7 +45,25 @@ class Learn2earnViewModel @Inject constructor( private set init { - uiState = uiState.updateStoriesVisibility(interactor.isNeedToShowViewOnStoriesScreen()) + subscribeToWalletChanges() + uiState = uiState + .updateStoriesVisibility(isVisible = interactor.isPromotionActiveOnStories()) + .updateGetBonusVisibility(isVisible = interactor.isPromotionActiveOnMain()) + } + + private fun subscribeToWalletChanges() { + viewModelScope.launch(dispatchers.io) { + interactor.getIsTangemWalletFlow() + .collect { isTangemWallet -> + updateUi { + uiState + .updateGetBonusVisibility( + isVisible = isTangemWallet && interactor.isPromotionActiveOnMain(), + ) + .changeGetBonusDescription(getBonusDescription()) + } + } + } } fun onMainScreenCreated() { @@ -54,15 +75,63 @@ class Learn2earnViewModel @Inject constructor( } private fun updateMainScreenViews() { + if (!interactor.isPromotionActiveOnMain()) { + uiState = uiState.updateGetBonusVisibility(isVisible = false) + return + } + viewModelScope.launch(dispatchers.io) { - if (interactor.isNeedToShowViewOnMainScreen()) { - updateUi { - uiState.changeGetBounsDescription(getBonusDescription()) - .updateGetBonusVisibility(isVisible = true) + 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) } + } + }, + ) } - } else { + .onFailure { + Timber.e(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 -> { updateUi { uiState.updateGetBonusVisibility(isVisible = false) } } + is PromotionError.CodeNotFound, + -> { + updateUi { + uiState.updateViewsVisibility(isVisible = true) + .changeGetBonusDescription(getBonusDescription()) + } + } + is PromotionError.UnknownError, + PromotionError.NetworkUnreachable, + -> Unit } } @@ -72,14 +141,17 @@ class Learn2earnViewModel @Inject constructor( } private fun onButtonMainClick() { - if (!interactor.isUserHadPromoCode() && !interactor.isUserRegisteredInPromotion()) { + if (interactor.isUserHadPromoCode() || interactor.isUserRegisteredInPromotion()) { + analytics.send(Learn2earnEvents.MainScreen.NoticeLear2earn(AnalyticsParam.ClientType.Old())) + requestAward() + } else { analytics.send(Learn2earnEvents.MainScreen.NoticeLear2earn(AnalyticsParam.ClientType.New())) subscribeToWebViewResultEvents() router.openWebView(interactor.buildUriForOldUser(), interactor.getBasicAuthHeaders()) - return } + } - analytics.send(Learn2earnEvents.MainScreen.NoticeLear2earn(AnalyticsParam.ClientType.Old())) + private fun requestAward() { viewModelScope.launch(dispatchers.io) { updateUi { uiState.updateProgress(showProgress = true) } @@ -89,7 +161,7 @@ class Learn2earnViewModel @Inject constructor( onSuccess = { analytics.send(Learn2earnEvents.MainScreen.NoticeClaimSuccess()) val onHideDialog = { - uiState = uiState.updateGetBonusVisibility(isVisible = false) + uiState = uiState.updateViewsVisibility(isVisible = false) .hideDialog() } val successDialog = MainScreenState.Dialog.Claimed( @@ -152,7 +224,7 @@ class Learn2earnViewModel @Inject constructor( .updateGetBonusVisibility(isVisible = false) } MainScreenState.Dialog.Error( - error = error, + textReference = TextReference.Str(error.description), onOk = onHideDialog, onDismissRequest = onHideDialog, ) @@ -160,7 +232,7 @@ class Learn2earnViewModel @Inject constructor( is PromotionError.UnknownError, PromotionError.NetworkUnreachable -> { val onHideDialog = { uiState = uiState.hideDialog() } MainScreenState.Dialog.Error( - error = error, + textReference = TextReference.Res(R.string.common_server_unavailable), onOk = onHideDialog, onDismissRequest = onHideDialog, ) @@ -171,9 +243,19 @@ class Learn2earnViewModel @Inject constructor( private fun subscribeToWebViewResultEvents() { interactor.webViewResultHandler = object : WebViewResultHandler { override fun handleResult(result: WebViewResult) { - interactor.webViewResultHandler = null - if (result is WebViewResult.ReadyForAward) { - updateMainScreenViews() + when (result) { + is WebViewResult.NewUserLearningFinished -> { + interactor.webViewResultHandler = null + } + WebViewResult.ReadyForAward -> { + interactor.webViewResultHandler = null + updateMainScreenViews() + requestAward() + } + is WebViewResult.OldUserLearningFinished, + is WebViewResult.Learn2earnAnalyticsEvent, + WebViewResult.Empty, + -> Unit } } } diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/GetBonusView.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/GetBonusView.kt index 5c4aff6b96..a36d44f760 100644 --- a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/GetBonusView.kt +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/GetBonusView.kt @@ -77,7 +77,7 @@ internal fun GetBonusView(state: MainScreenState, modifier: Modifier = Modifier) Text( text = state.description.subtitle.resolveReference(), style = TangemTypography.caption, - color = TangemTheme.colors.text.secondary, + color = TangemTheme.colors.text.tertiary, ) } } diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/Learn2earnDialogs.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/Learn2earnDialogs.kt index 9a21f29231..3c855863ac 100644 --- a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/Learn2earnDialogs.kt +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/Learn2earnDialogs.kt @@ -5,6 +5,7 @@ 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 @@ -72,7 +73,7 @@ private fun ErrorDialog(dialog: MainScreenState.Dialog.Error) { Text(text = stringResource(id = R.string.common_error)) }, text = { - Text(text = dialog.error.description) + Text(text = dialog.textReference.resolveReference()) }, confirmButton = { TextButton( diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/state/Learn2earnState.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/state/Learn2earnState.kt index 4ba08f14bb..4e0e2054cd 100644 --- a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/state/Learn2earnState.kt +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/state/Learn2earnState.kt @@ -2,7 +2,6 @@ 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.domain.models.PromotionError import com.tangem.feature.learn2earn.impl.R /** @@ -62,7 +61,7 @@ data class MainScreenState( ) : Dialog() data class Error( - val error: PromotionError, + val textReference: TextReference, val onOk: () -> Unit, val onDismissRequest: () -> Unit, ) : Dialog() diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/state/StateBuilder.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/state/StateBuilder.kt index 90e41829ab..a473dd04d2 100644 --- a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/state/StateBuilder.kt +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/ui/state/StateBuilder.kt @@ -43,7 +43,12 @@ internal fun Learn2earnState.updateGetBonusVisibility(isVisible: Boolean): Learn } } -internal fun Learn2earnState.changeGetBounsDescription(description: MainScreenState.Description): Learn2earnState { +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 { diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/webView/Learn2earnWebViewActivity.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/webView/Learn2earnWebViewActivity.kt index 7300fcdb06..b5d341df31 100644 --- a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/webView/Learn2earnWebViewActivity.kt +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/presentation/webView/Learn2earnWebViewActivity.kt @@ -42,6 +42,7 @@ class Learn2earnWebViewActivity : AppCompatActivity() { headers = webViewData.headers, finishSessionHandler = { finish() }, ) + webView.loadUrl( webViewData.uri.toString(), webViewData.headers, 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 index 8737917c56..8925162b7e 100644 --- a/features/learn2earn/impl/src/main/res/layout/activity_web_view.xml +++ b/features/learn2earn/impl/src/main/res/layout/activity_web_view.xml @@ -22,17 +22,10 @@ - - - - - - + android:layout_marginTop="?attr/actionBarSize" /> \ No newline at end of file diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 847e4191a7..701091b1d4 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -73,8 +73,8 @@ mviCore = "1.3.1" kotlinSerialization = "1.4.1" arrow = "1.2.0" reactiveNetwork = "3.0.8" -walletConnectCore = "1.17.0" -walletConnectWeb3 = "1.10.0" +walletConnectCore = "1.18.0" +walletConnectWeb3 = "1.11.0" prettyLogger = "2.2.0" okHttp-prettyLogging = "3.1.0" # endregion Other libraries @@ -82,7 +82,7 @@ okHttp-prettyLogging = "3.1.0" # region Tangem tangemBlockchainSdk = "develop-289" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "develop-273" +tangemCardSdk = "develop-278" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds # endregion Tangem