Updated on 2026-08-14
This commit is contained in:
commit
e3c0b0b311
47 changed files with 528 additions and 187 deletions
|
|
@ -1 +1 @@
|
|||
Subproject commit fad890b2a0b552be60d124949ca0a3a4d672dac1
|
||||
Subproject commit b791bd4cf6c5eca9778f89e87cd62b72d24f5ce9
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Boolean> {
|
||||
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()!!)
|
||||
|
|
|
|||
|
|
@ -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<PaymentData>) -> Unit)? = null
|
||||
|
||||
suspend fun checkIfGooglePayAvailable(): Result<Boolean> {
|
||||
suspend fun checkIfGooglePayAvailable(isMerchantAvailable: Boolean): Result<Boolean> {
|
||||
if (!isMerchantAvailable) return Result.success(false)
|
||||
val isReadyToPayJson = GooglePayUtil.isReadyToPayRequest() ?: return Result.success(false)
|
||||
val request = IsReadyToPayRequest.fromJson(isReadyToPayJson.toString())
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Pair<Int, List<Any>?>> {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String>,
|
||||
|
||||
@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<List<WcAddChain>>(
|
||||
Types.newParameterizedType(List::class.java, WcAddChain::class.java),
|
||||
).fromJsonFirstOrNull(params) ?: return customRequest
|
||||
WcRequest.AddChain(data = deserializedParams)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<UserWalletsListManager?>
|
||||
get() = _userWalletsListManagerFlow
|
||||
|
||||
private val _userWalletsListManagerFlow = MutableStateFlow<UserWalletsListManager?>(null)
|
||||
|
||||
@Deprecated("Use scan response from selected user wallet")
|
||||
var scanResponse: ScanResponse? = null
|
||||
|
|
|
|||
|
|
@ -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<CardTypesResolver?> {
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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?,
|
||||
)
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -68,7 +68,6 @@
|
|||
<string name="common_exchange">Обменять</string>
|
||||
<string name="common_explore_transaction_history">Посмотреть историю транзакций</string>
|
||||
<string name="common_explorer">Обозреватель</string>
|
||||
<string name="common_learn_and_earn">Учись и получай бонусы!</string>
|
||||
<string name="common_like">Нравится</string>
|
||||
<string name="common_main_network">Основная сеть</string>
|
||||
<string name="common_no">Нет</string>
|
||||
|
|
@ -139,6 +138,7 @@
|
|||
<string name="disclaimer_error_loading">Проверьте подключение с интернетом или переключитесь на другую сеть</string>
|
||||
<string name="disclaimer_title">Условия использования</string>
|
||||
<string name="error_update_app">К сожалению, текущая версия приложения не готова к работе с этой картой, проверьте наличие обновлений</string>
|
||||
<string name="error_wrong_card_type">Данное приложение не предназначено для работы с этой картой или требует обновления</string>
|
||||
<string name="error_wrong_wallet_tapped">Вы использовали карту от другого кошелька. Приложите карту, связанную с этим кошельком.</string>
|
||||
<string name="exchange_receive_view_header">Вы получаете</string>
|
||||
<string name="exchange_send_view_header">Вы отправляете</string>
|
||||
|
|
@ -163,10 +163,10 @@
|
|||
<string name="key_invalidated_warning_description">Вы обновили данные биометрии, отсканируйте свою карту для входа</string>
|
||||
<string name="main_get_bonus_subtitle">Вы успешно прошли все уроки и теперь можете получить 1INCH токены</string>
|
||||
<plurals name="main_learn_subtitle">
|
||||
<item quantity="one">Пройдите 3 урока и получите %d 1INCH токен на свой кошелек</item>
|
||||
<item quantity="few">Пройдите 3 урока и получите %d 1INCH токена на свой кошелек</item>
|
||||
<item quantity="many">Пройдите 3 урока и получите %d 1INCH токена на свой кошелек</item>
|
||||
<item quantity="other">Пройдите 3 урока и получите %d 1INCH токенов на свой кошелек</item>
|
||||
<item quantity="one">Пройдите 3 урока и получите %d 1INCH токен на свой кошелек</item>
|
||||
<item quantity="few">Пройдите 3 урока и получите %d 1INCH токена на свой кошелек</item>
|
||||
<item quantity="many">Пройдите 3 урока и получите %d 1INCH токенов на свой кошелек</item>
|
||||
<item quantity="other">Пройдите 3 урока и получите %d 1INCH токенов на свой кошелек</item>
|
||||
</plurals>
|
||||
<string name="main_manage_tokens">Управление токенами</string>
|
||||
<string name="main_no_backup_warning_subtitle">Чтобы защитить свои активы, мы советуем вам выполнить эту процедуру</string>
|
||||
|
|
@ -214,6 +214,7 @@
|
|||
<string name="onboarding_exit_alert_message">В этом случае вам будет необходимо начать процесс заново.</string>
|
||||
<string name="onboarding_exit_alert_title">Вы хотите выйти из процесса активации?</string>
|
||||
<string name="onboarding_getting_started">Подготовка</string>
|
||||
<string name="onboarding_linking_error_card_with_wallets">Другой кошелек уже был создан на карте, которую вы пытаетесь добавить. Хотите сбросить его и использовать карту для бэкапа?</string>
|
||||
<string name="onboarding_navbar_pin">Код доступа</string>
|
||||
<string name="onboarding_navbar_register_wallet">Подключиться</string>
|
||||
<string name="onboarding_navbar_title_creating_backup">Резервная копия</string>
|
||||
|
|
|
|||
|
|
@ -187,6 +187,7 @@
|
|||
<string name="onboarding_exit_alert_message">這此情況,您必須要重新開始</string>
|
||||
<string name="onboarding_exit_alert_title">您想要離開啟用程序嗎?</string>
|
||||
<string name="onboarding_getting_started">開始</string>
|
||||
<string name="onboarding_linking_error_card_with_wallets">您要添加的卡上已經創建了另一個錢包。你想重置它並將卡用於新錢包嗎</string>
|
||||
<string name="onboarding_navbar_pin">PIN 碼</string>
|
||||
<string name="onboarding_navbar_register_wallet">連接</string>
|
||||
<string name="onboarding_navbar_title_creating_backup">創建備份</string>
|
||||
|
|
|
|||
|
|
@ -137,6 +137,7 @@
|
|||
<string name="disclaimer_error_loading">Check your internet connection or switch to a different network</string>
|
||||
<string name="disclaimer_title">Terms of Service</string>
|
||||
<string name="error_update_app">Oops, the current version of the application is not ready to work with this card, please check for updates.</string>
|
||||
<string name="error_wrong_card_type">This application is not designed to work with this card or needs to be updated</string>
|
||||
<string name="error_wrong_wallet_tapped">You have used a card from another wallet. Tap the card associated with this wallet</string>
|
||||
<string name="exchange_receive_view_header">You Receive</string>
|
||||
<string name="exchange_send_view_header">You Send</string>
|
||||
|
|
@ -161,8 +162,8 @@
|
|||
<string name="key_invalidated_warning_description">You have updated biometrics, scan your card to enter</string>
|
||||
<string name="main_get_bonus_subtitle">You have completed all of the lessons, and are now eligible to receive your 1INCH tokens</string>
|
||||
<plurals name="main_learn_subtitle">
|
||||
<item quantity="one">Complete three lessons and receive %d 1INCH token to your wallet</item>
|
||||
<item quantity="other">Complete three lessons and receive %d 1INCH tokens to your wallet</item>
|
||||
<item quantity="one">Complete three lessons and receive %d 1INCH token to your wallet</item>
|
||||
<item quantity="other">Complete three lessons and receive %d 1INCH tokens to your wallet</item>
|
||||
</plurals>
|
||||
<string name="main_manage_tokens">Manage tokens</string>
|
||||
<string name="main_no_backup_warning_subtitle">To protect your assets, we advise you to carry out this procedure</string>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -1,6 +1,10 @@
|
|||
package com.tangem.domain.wallets.legacy
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface WalletsStateHolder {
|
||||
|
||||
val userWalletsListManager: UserWalletsListManager?
|
||||
|
||||
val userWalletListManagerFlow: Flow<UserWalletsListManager?>
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -121,6 +121,7 @@ internal class DefaultLearn2earnRepository(
|
|||
promoCode = null,
|
||||
isRegisteredInPromotion = false,
|
||||
isAlreadyReceivedAward = false,
|
||||
isLearningStageFinished = false,
|
||||
)
|
||||
|
||||
private companion object {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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<Boolean> = 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<Unit> {
|
||||
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<String> {
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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<CardTypesResolver?>
|
||||
|
||||
fun getLocaleProvider(): () -> String
|
||||
|
||||
fun getWebViewAuthCredentialsProvider(): () -> String?
|
||||
}
|
||||
|
|
@ -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<Boolean>
|
||||
|
||||
fun isUserHadPromoCode(): Boolean
|
||||
|
||||
fun isNeedToShowViewOnStoriesScreen(): Boolean
|
||||
fun isPromotionActive(): Boolean
|
||||
|
||||
suspend fun isNeedToShowViewOnMainScreen(): Boolean
|
||||
fun isPromotionActiveOnStories(): Boolean
|
||||
|
||||
fun isPromotionActiveOnMain(): Boolean
|
||||
|
||||
suspend fun validateUserWallet(): Result<Unit>
|
||||
|
||||
fun isUserRegisteredInPromotion(): Boolean
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ class Learn2earnWebViewActivity : AppCompatActivity() {
|
|||
headers = webViewData.headers,
|
||||
finishSessionHandler = { finish() },
|
||||
)
|
||||
|
||||
webView.loadUrl(
|
||||
webViewData.uri.toString(),
|
||||
webViewData.headers,
|
||||
|
|
|
|||
|
|
@ -22,17 +22,10 @@
|
|||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
<WebView
|
||||
android:id="@+id/web_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<WebView
|
||||
android:id="@+id/web_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
android:layout_marginTop="?attr/actionBarSize" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue