Updated on 2026-08-14
This commit is contained in:
parent
3ac26a45b7
commit
294df69e93
21 changed files with 427 additions and 100 deletions
|
|
@ -2,12 +2,12 @@ package com.tangem.tap.common.analytics.paramsInterceptor
|
|||
|
||||
import com.tangem.core.analytics.api.ParamsInterceptor
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.domain.card.analytics.IntroductionProcess
|
||||
import com.tangem.domain.card.analytics.ParamCardCurrencyConverter
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
import com.tangem.features.home.impl.analytics.IntroductionProcess
|
||||
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
|
|
|
|||
|
|
@ -131,6 +131,12 @@ internal object WalletsDomainModule {
|
|||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesOpenBuyTangemCardUseCase(): GenerateBuyTangemCardLinkUseCase {
|
||||
return GenerateBuyTangemCardLinkUseCase()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesGetExploreUrlUseCase(walletsManagersFacade: WalletManagersFacade): GetExploreUrlUseCase {
|
||||
|
|
|
|||
|
|
@ -11,13 +11,13 @@ import com.tangem.common.routing.utils.popTo
|
|||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.domain.card.analytics.ParamCardCurrencyConverter
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager.Lockable.UnlockType
|
||||
import com.tangem.domain.wallets.legacy.unlockIfLockable
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.intentHandler.handlers.WalletConnectLinkIntentHandler
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.features.home.impl.analytics
|
||||
package com.tangem.domain.card.analytics
|
||||
|
||||
internal sealed class AnalyticsParam {
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.features.home.impl.analytics
|
||||
package com.tangem.domain.card.analytics
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
|
||||
|
|
@ -1,18 +1,14 @@
|
|||
package com.tangem.tap.common.analytics.converters
|
||||
package com.tangem.domain.card.analytics
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.WalletType
|
||||
import com.tangem.domain.card.CardTypesResolver
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.core.analytics.models.AnalyticsParam as CoreAnalyticsParam
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class ParamCardCurrencyConverter : Converter<CardTypesResolver, CoreAnalyticsParam.WalletType?> {
|
||||
class ParamCardCurrencyConverter : Converter<CardTypesResolver, WalletType?> {
|
||||
|
||||
override fun convert(value: CardTypesResolver): CoreAnalyticsParam.WalletType? {
|
||||
if (value.isMultiwalletAllowed()) return CoreAnalyticsParam.WalletType.MultiCurrency
|
||||
override fun convert(value: CardTypesResolver): WalletType? {
|
||||
if (value.isMultiwalletAllowed()) return WalletType.MultiCurrency
|
||||
|
||||
val type = when {
|
||||
value.isTangemNote() -> AnalyticsParam.CurrencyType.Blockchain(value.getBlockchain())
|
||||
|
|
@ -22,6 +18,6 @@ class ParamCardCurrencyConverter : Converter<CardTypesResolver, CoreAnalyticsPar
|
|||
else -> null
|
||||
} ?: return null
|
||||
|
||||
return CoreAnalyticsParam.WalletType.SingleCurrency(type.value)
|
||||
return WalletType.SingleCurrency(type.value)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.features.home.impl.analytics
|
||||
package com.tangem.domain.card.analytics
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
|
||||
internal sealed class Shop(
|
||||
sealed class Shop(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent("Shop", event, params) {
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package com.tangem.domain.core.wallets.error
|
||||
|
||||
sealed interface SaveFirstColdWalletError {
|
||||
data object CreateWalletError : SaveFirstColdWalletError
|
||||
data class SaveError(val error: SaveWalletError) : SaveFirstColdWalletError
|
||||
data class SelectError(val error: SelectWalletError) : SaveFirstColdWalletError
|
||||
}
|
||||
|
|
@ -37,6 +37,11 @@ dependencies {
|
|||
implementation(tangemDeps.hot.core)
|
||||
// endregion
|
||||
|
||||
/** Other libraries */
|
||||
implementation(platform(deps.firebase.bom))
|
||||
implementation(deps.firebase.analytics)
|
||||
implementation(deps.timber)
|
||||
|
||||
// region DI
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.domain.wallets.usecase
|
||||
|
||||
import com.google.firebase.analytics.ktx.analytics
|
||||
import com.google.firebase.ktx.Firebase
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
class GenerateBuyTangemCardLinkUseCase {
|
||||
|
||||
suspend operator fun invoke(): String = suspendCoroutine { cont ->
|
||||
Firebase.analytics.appInstanceId
|
||||
.addOnSuccessListener { id ->
|
||||
cont.resume("$NEW_BUY_WALLET_URL&app_instance_id=$id")
|
||||
}
|
||||
.addOnFailureListener {
|
||||
cont.resume(NEW_BUY_WALLET_URL)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val NEW_BUY_WALLET_URL = "https://buy.tangem.com/?utm_source=tangem-app&utm_medium=app"
|
||||
}
|
||||
}
|
||||
|
|
@ -4,9 +4,9 @@ import arrow.core.Either
|
|||
import arrow.core.raise.either
|
||||
import arrow.core.right
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.domain.core.wallets.error.SelectWalletError
|
||||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.core.wallets.error.SelectWalletError
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
|
|
|
|||
|
|
@ -18,6 +18,12 @@ dependencies {
|
|||
/** Hot Wallet Feature */
|
||||
implementation(projects.features.hotWallet.api)
|
||||
|
||||
/** Project - Domain */
|
||||
implementation(projects.domain.card)
|
||||
implementation(projects.domain.settings)
|
||||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.models)
|
||||
|
||||
/** Core modules */
|
||||
implementation(projects.core.configToggles)
|
||||
implementation(projects.core.analytics)
|
||||
|
|
|
|||
|
|
@ -1,19 +1,63 @@
|
|||
package com.tangem.features.createwalletselection
|
||||
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.Basic.SignedIn
|
||||
import com.tangem.core.analytics.models.Basic.SignedIn.SignInType
|
||||
import com.tangem.core.decompose.di.GlobalUiMessageSender
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.message.DialogMessage
|
||||
import com.tangem.domain.card.ScanCardProcessor
|
||||
import com.tangem.domain.card.analytics.IntroductionProcess
|
||||
import com.tangem.domain.card.analytics.ParamCardCurrencyConverter
|
||||
import com.tangem.domain.card.analytics.Shop
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.core.wallets.error.SaveWalletError
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.usecase.GenerateBuyTangemCardLinkUseCase
|
||||
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
|
||||
import com.tangem.features.createwalletselection.entity.CreateWalletSelectionUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val HIDE_PROGRESS_DELAY = 400L
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@ModelScoped
|
||||
internal class CreateWalletSelectionModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val router: Router,
|
||||
private val scanCardProcessor: ScanCardProcessor,
|
||||
private val cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val appRouter: AppRouter,
|
||||
private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory,
|
||||
private val saveWalletUseCase: SaveWalletUseCase,
|
||||
private val generateBuyTangemCardLinkUseCase: GenerateBuyTangemCardLinkUseCase,
|
||||
private val urlOpener: UrlOpener,
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
@GlobalUiMessageSender private val uiMessageSender: UiMessageSender,
|
||||
) : Model() {
|
||||
|
||||
internal val uiState: StateFlow<CreateWalletSelectionUM>
|
||||
|
|
@ -31,10 +75,110 @@ internal class CreateWalletSelectionModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun onHardwareWalletClick() {
|
||||
// TODO open card order web page
|
||||
analyticsEventHandler.send(IntroductionProcess.ButtonBuyCards)
|
||||
analyticsEventHandler.send(Shop.ScreenOpened)
|
||||
modelScope.launch {
|
||||
generateBuyTangemCardLinkUseCase.invoke().let { urlOpener.openUrl(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun onScanClick() {
|
||||
// TODO open card scanning
|
||||
analyticsEventHandler.send(IntroductionProcess.ButtonScanCard)
|
||||
scanCard()
|
||||
}
|
||||
|
||||
private fun scanCard() {
|
||||
modelScope.launch {
|
||||
setLoading(true)
|
||||
|
||||
val shouldSaveAccessCodes = settingsRepository.shouldSaveAccessCodes()
|
||||
cardSdkConfigRepository.setAccessCodeRequestPolicy(
|
||||
isBiometricsRequestPolicy = shouldSaveAccessCodes,
|
||||
)
|
||||
|
||||
val analyticsSource = AnalyticsParam.ScreensSources.Intro
|
||||
|
||||
scanCardProcessor.scan(
|
||||
analyticsSource = analyticsSource,
|
||||
onProgressStateChange = { showProgress ->
|
||||
if (!showProgress) {
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
setLoading(false)
|
||||
} else {
|
||||
setLoading(true)
|
||||
}
|
||||
},
|
||||
onFailure = { error ->
|
||||
handleScanError(error)
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
setLoading(false)
|
||||
},
|
||||
onSuccess = { scanResponse ->
|
||||
proceedWithScanResponse(scanResponse)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun proceedWithScanResponse(scanResponse: ScanResponse) {
|
||||
val userWallet = coldUserWalletBuilderFactory.create(scanResponse = scanResponse).build()
|
||||
|
||||
if (userWallet == null) {
|
||||
Timber.e("User wallet not created")
|
||||
setLoading(false)
|
||||
return
|
||||
}
|
||||
|
||||
saveWalletUseCase(userWallet).fold(
|
||||
ifLeft = {
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
setLoading(false)
|
||||
when (it) {
|
||||
is SaveWalletError.DataError -> Timber.e(it.toString(), "Unable to save user wallet")
|
||||
is SaveWalletError.WalletAlreadySaved -> appRouter.replaceAll(AppRoute.Wallet)
|
||||
}
|
||||
},
|
||||
ifRight = {
|
||||
setLoading(false)
|
||||
sendSignedInCardAnalyticsEvent(scanResponse)
|
||||
appRouter.replaceAll(AppRoute.Wallet)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun sendSignedInCardAnalyticsEvent(scanResponse: ScanResponse) {
|
||||
val currency = ParamCardCurrencyConverter().convert(value = scanResponse.cardTypesResolver)
|
||||
if (currency != null) {
|
||||
analyticsEventHandler.send(
|
||||
SignedIn(
|
||||
currency = currency,
|
||||
batch = scanResponse.card.batchId,
|
||||
signInType = SignInType.Card,
|
||||
walletsCount = userWalletsListManager.walletsCount.toString(),
|
||||
hasBackup = scanResponse.card.backupStatus?.isActive,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setLoading(isLoading: Boolean) {
|
||||
uiState.update { it.copy(isScanInProgress = isLoading) }
|
||||
}
|
||||
|
||||
fun handleScanError(error: TangemError) {
|
||||
when (error) {
|
||||
is TangemSdkError.NfcFeatureIsUnavailable -> handleNfcFeatureUnavailable()
|
||||
is TangemSdkError -> Timber.e(error, "Scan error occurred")
|
||||
else -> Timber.e(error, "Error happened")
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleNfcFeatureUnavailable() {
|
||||
uiMessageSender.send(
|
||||
message = DialogMessage(
|
||||
message = resourceReference(R.string.nfc_error_unavailable),
|
||||
title = resourceReference(id = R.string.common_error),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,10 +5,12 @@ import androidx.compose.foundation.background
|
|||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
|
|
@ -18,6 +20,7 @@ import com.tangem.core.ui.components.buttons.common.TangemButton
|
|||
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonSize
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
|
||||
import com.tangem.core.ui.extensions.conditional
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
|
@ -179,6 +182,9 @@ private fun AlreadyHaveTangemWalletBlock(
|
|||
isScanInProgress: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var buttonWidth by remember { mutableStateOf(0) }
|
||||
val density = LocalDensity.current
|
||||
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
|
|
@ -201,9 +207,17 @@ private fun AlreadyHaveTangemWalletBlock(
|
|||
style = TangemTheme.typography.button,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
|
||||
TangemButton(
|
||||
modifier = Modifier
|
||||
.wrapContentWidth(),
|
||||
.conditional(buttonWidth > 0) {
|
||||
width(with(density) { buttonWidth.toDp() })
|
||||
}
|
||||
.onGloballyPositioned { coordinates ->
|
||||
if (buttonWidth == 0) {
|
||||
buttonWidth = coordinates.size.width
|
||||
}
|
||||
},
|
||||
text = stringResourceSafe(R.string.wallet_create_scan_title),
|
||||
onClick = onScanClick,
|
||||
icon = TangemButtonIconPosition.End(iconResId = R.drawable.ic_tangem_24),
|
||||
|
|
|
|||
|
|
@ -51,9 +51,6 @@ dependencies {
|
|||
implementation(deps.compose.coil)
|
||||
implementation(deps.decompose.ext.compose)
|
||||
|
||||
/** Firebase */
|
||||
implementation(deps.firebase.analytics)
|
||||
|
||||
/** Tangem libraries */
|
||||
implementation(tangemDeps.card.android)
|
||||
implementation(tangemDeps.card.core)
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
package com.tangem.features.home.impl.analytics
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.domain.card.CardTypesResolver
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.core.analytics.models.AnalyticsParam as CoreAnalyticsParam
|
||||
|
||||
internal class ParamCardCurrencyConverter : Converter<CardTypesResolver, CoreAnalyticsParam.WalletType?> {
|
||||
|
||||
override fun convert(value: CardTypesResolver): CoreAnalyticsParam.WalletType? {
|
||||
if (value.isMultiwalletAllowed()) return CoreAnalyticsParam.WalletType.MultiCurrency
|
||||
|
||||
val type = when {
|
||||
value.isTangemNote() -> AnalyticsParam.CurrencyType.Blockchain(value.getBlockchain())
|
||||
value.isTangemTwins() -> AnalyticsParam.CurrencyType.Blockchain(Blockchain.Bitcoin)
|
||||
value.getBlockchain() != Blockchain.Unknown -> AnalyticsParam.CurrencyType.Blockchain(value.getBlockchain())
|
||||
value.getPrimaryToken() != null -> AnalyticsParam.CurrencyType.Token(value.getPrimaryToken()!!)
|
||||
else -> null
|
||||
} ?: return null
|
||||
|
||||
return CoreAnalyticsParam.WalletType.SingleCurrency(type.value)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,5 @@
|
|||
package com.tangem.features.home.impl.model
|
||||
|
||||
import com.google.firebase.analytics.ktx.analytics
|
||||
import com.google.firebase.ktx.Firebase
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.routing.AppRoute
|
||||
|
|
@ -23,20 +21,22 @@ import com.tangem.core.ui.R
|
|||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.message.DialogMessage
|
||||
import com.tangem.domain.card.ScanCardProcessor
|
||||
import com.tangem.domain.card.analytics.IntroductionProcess
|
||||
import com.tangem.domain.card.analytics.ParamCardCurrencyConverter
|
||||
import com.tangem.domain.card.analytics.Shop
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.core.wallets.error.SaveWalletError
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
|
||||
import com.tangem.domain.settings.usercountry.models.UserCountry
|
||||
import com.tangem.domain.settings.usercountry.models.needApplyFCARestrictions
|
||||
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.usecase.GenerateBuyTangemCardLinkUseCase
|
||||
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.SelectWalletUseCase
|
||||
import com.tangem.features.home.api.HomeComponent
|
||||
import com.tangem.features.home.impl.analytics.IntroductionProcess
|
||||
import com.tangem.features.home.impl.analytics.ParamCardCurrencyConverter
|
||||
import com.tangem.features.home.impl.analytics.Shop
|
||||
import com.tangem.features.home.impl.ui.state.HomeUM
|
||||
import com.tangem.features.home.impl.ui.state.Stories
|
||||
import com.tangem.features.home.impl.ui.state.getRestrictedStories
|
||||
|
|
@ -64,16 +64,17 @@ internal class HomeModel @Inject constructor(
|
|||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val scanCardProcessor: ScanCardProcessor,
|
||||
private val saveWalletUseCase: SaveWalletUseCase,
|
||||
private val cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val urlOpener: UrlOpener,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory,
|
||||
private val router: Router,
|
||||
private val selectWalletUseCase: SelectWalletUseCase,
|
||||
private val appRouter: AppRouter,
|
||||
private val getUserCountryUseCase: GetUserCountryUseCase,
|
||||
private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory,
|
||||
private val saveWalletUseCase: SaveWalletUseCase,
|
||||
private val generateBuyTangemCardLinkUseCase: GenerateBuyTangemCardLinkUseCase,
|
||||
private val urlOpener: UrlOpener,
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
@GlobalUiMessageSender private val uiMessageSender: UiMessageSender,
|
||||
) : Model() {
|
||||
|
||||
|
|
@ -135,10 +136,9 @@ internal class HomeModel @Inject constructor(
|
|||
private fun onShopClick() {
|
||||
analyticsEventHandler.send(IntroductionProcess.ButtonBuyCards)
|
||||
analyticsEventHandler.send(Shop.ScreenOpened)
|
||||
|
||||
Firebase.analytics.appInstanceId
|
||||
.addOnSuccessListener { urlOpener.openUrl(url = "$NEW_BUY_WALLET_URL&app_instance_id=$it") }
|
||||
.addOnFailureListener { urlOpener.openUrl(url = NEW_BUY_WALLET_URL) }
|
||||
modelScope.launch {
|
||||
generateBuyTangemCardLinkUseCase.invoke().let { urlOpener.openUrl(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSearchTokensClick() {
|
||||
|
|
@ -198,24 +198,17 @@ internal class HomeModel @Inject constructor(
|
|||
|
||||
saveWalletUseCase(userWallet).fold(
|
||||
ifLeft = {
|
||||
Timber.e(it.toString(), "Unable to save user wallet")
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
setLoading(false)
|
||||
when (it) {
|
||||
is SaveWalletError.DataError -> Timber.e(it.toString(), "Unable to save user wallet")
|
||||
is SaveWalletError.WalletAlreadySaved -> appRouter.replaceAll(AppRoute.Wallet)
|
||||
}
|
||||
},
|
||||
ifRight = {
|
||||
setLoading(false)
|
||||
sendSignedInCardAnalyticsEvent(scanResponse)
|
||||
|
||||
// Select the wallet using new mechanism
|
||||
selectWalletUseCase(userWallet.walletId).fold(
|
||||
ifLeft = {
|
||||
Timber.e("Unable to select user wallet: $it")
|
||||
setLoading(false)
|
||||
},
|
||||
ifRight = {
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
setLoading(false)
|
||||
appRouter.replaceAll(AppRoute.Wallet)
|
||||
},
|
||||
)
|
||||
appRouter.replaceAll(AppRoute.Wallet)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -228,7 +221,7 @@ internal class HomeModel @Inject constructor(
|
|||
currency = currency,
|
||||
batch = scanResponse.card.batchId,
|
||||
signInType = SignInType.Card,
|
||||
walletsCount = "1",
|
||||
walletsCount = userWalletsListManager.walletsCount.toString(),
|
||||
hasBackup = scanResponse.card.backupStatus?.isActive,
|
||||
),
|
||||
)
|
||||
|
|
@ -241,15 +234,9 @@ internal class HomeModel @Inject constructor(
|
|||
|
||||
fun handleScanError(error: TangemError) {
|
||||
when (error) {
|
||||
is TangemSdkError.NfcFeatureIsUnavailable -> {
|
||||
handleNfcFeatureUnavailable()
|
||||
}
|
||||
is TangemSdkError -> {
|
||||
Timber.e(error, "Scan error occurred")
|
||||
}
|
||||
else -> {
|
||||
Timber.e(error, "Error happened")
|
||||
}
|
||||
is TangemSdkError.NfcFeatureIsUnavailable -> handleNfcFeatureUnavailable()
|
||||
is TangemSdkError -> Timber.e(error, "Scan error occurred")
|
||||
else -> Timber.e(error, "Error happened")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -261,8 +248,4 @@ internal class HomeModel @Inject constructor(
|
|||
),
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val NEW_BUY_WALLET_URL = "https://buy.tangem.com/?utm_source=tangem-app&utm_medium=app"
|
||||
}
|
||||
}
|
||||
|
|
@ -28,6 +28,7 @@ dependencies {
|
|||
implementation(projects.core.datasource)
|
||||
|
||||
/** Domain */
|
||||
implementation(projects.domain.card)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.wallets.models)
|
||||
|
|
|
|||
|
|
@ -1,18 +1,63 @@
|
|||
package com.tangem.features.hotwallet.addexistingwallet.start
|
||||
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.Basic.SignedIn
|
||||
import com.tangem.core.analytics.models.Basic.SignedIn.SignInType
|
||||
import com.tangem.core.decompose.di.GlobalUiMessageSender
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.message.DialogMessage
|
||||
import com.tangem.domain.card.ScanCardProcessor
|
||||
import com.tangem.domain.card.analytics.IntroductionProcess
|
||||
import com.tangem.domain.card.analytics.ParamCardCurrencyConverter
|
||||
import com.tangem.domain.card.analytics.Shop
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.core.wallets.error.SaveWalletError
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.usecase.GenerateBuyTangemCardLinkUseCase
|
||||
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
|
||||
import com.tangem.features.hotwallet.addexistingwallet.start.entity.AddExistingWalletStartUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val HIDE_PROGRESS_DELAY = 400L
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@ModelScoped
|
||||
internal class AddExistingWalletStartModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val saveWalletUseCase: SaveWalletUseCase,
|
||||
private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory,
|
||||
private val generateBuyTangemCardLinkUseCase: GenerateBuyTangemCardLinkUseCase,
|
||||
private val scanCardProcessor: ScanCardProcessor,
|
||||
private val cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val appRouter: AppRouter,
|
||||
private val urlOpener: UrlOpener,
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
@GlobalUiMessageSender private val uiMessageSender: UiMessageSender,
|
||||
) : Model() {
|
||||
|
||||
private val params: AddExistingWalletStartComponent.Params = paramsContainer.require()
|
||||
|
|
@ -20,10 +65,119 @@ internal class AddExistingWalletStartModel @Inject constructor(
|
|||
internal val uiState: StateFlow<AddExistingWalletStartUM>
|
||||
field = MutableStateFlow(
|
||||
AddExistingWalletStartUM(
|
||||
isScanInProgress = false,
|
||||
onBackClick = params.callbacks::onBackClick,
|
||||
onImportPhraseClick = params.callbacks::onImportPhraseClick,
|
||||
onScanCardClick = { /* [REDACTED_TODO_COMMENT] */ },
|
||||
onBuyCardClick = { /* [REDACTED_TODO_COMMENT] */ },
|
||||
onScanCardClick = ::onScanClick,
|
||||
onBuyCardClick = ::onShopClick,
|
||||
),
|
||||
)
|
||||
|
||||
private fun onShopClick() {
|
||||
analyticsEventHandler.send(IntroductionProcess.ButtonBuyCards)
|
||||
analyticsEventHandler.send(Shop.ScreenOpened)
|
||||
modelScope.launch {
|
||||
generateBuyTangemCardLinkUseCase.invoke().let { urlOpener.openUrl(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun onScanClick() {
|
||||
analyticsEventHandler.send(IntroductionProcess.ButtonScanCard)
|
||||
scanCard()
|
||||
}
|
||||
|
||||
private fun scanCard() {
|
||||
modelScope.launch {
|
||||
setLoading(true)
|
||||
|
||||
val shouldSaveAccessCodes = settingsRepository.shouldSaveAccessCodes()
|
||||
cardSdkConfigRepository.setAccessCodeRequestPolicy(
|
||||
isBiometricsRequestPolicy = shouldSaveAccessCodes,
|
||||
)
|
||||
|
||||
val analyticsSource = AnalyticsParam.ScreensSources.Intro
|
||||
|
||||
scanCardProcessor.scan(
|
||||
analyticsSource = analyticsSource,
|
||||
onProgressStateChange = { showProgress ->
|
||||
if (!showProgress) {
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
setLoading(false)
|
||||
} else {
|
||||
setLoading(true)
|
||||
}
|
||||
},
|
||||
onFailure = { error ->
|
||||
handleScanError(error)
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
setLoading(false)
|
||||
},
|
||||
onSuccess = { scanResponse ->
|
||||
proceedWithScanResponse(scanResponse)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun proceedWithScanResponse(scanResponse: ScanResponse) {
|
||||
val userWallet = coldUserWalletBuilderFactory.create(scanResponse = scanResponse).build()
|
||||
|
||||
if (userWallet == null) {
|
||||
Timber.e("User wallet not created")
|
||||
setLoading(false)
|
||||
return
|
||||
}
|
||||
|
||||
saveWalletUseCase(userWallet).fold(
|
||||
ifLeft = {
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
setLoading(false)
|
||||
when (it) {
|
||||
is SaveWalletError.DataError -> Timber.e(it.toString(), "Unable to save user wallet")
|
||||
is SaveWalletError.WalletAlreadySaved -> appRouter.replaceAll(AppRoute.Wallet)
|
||||
}
|
||||
},
|
||||
ifRight = {
|
||||
setLoading(false)
|
||||
sendSignedInCardAnalyticsEvent(scanResponse)
|
||||
appRouter.replaceAll(AppRoute.Wallet)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun sendSignedInCardAnalyticsEvent(scanResponse: ScanResponse) {
|
||||
val currency = ParamCardCurrencyConverter().convert(value = scanResponse.cardTypesResolver)
|
||||
if (currency != null) {
|
||||
analyticsEventHandler.send(
|
||||
SignedIn(
|
||||
currency = currency,
|
||||
batch = scanResponse.card.batchId,
|
||||
signInType = SignInType.Card,
|
||||
walletsCount = userWalletsListManager.walletsCount.toString(),
|
||||
hasBackup = scanResponse.card.backupStatus?.isActive,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setLoading(isLoading: Boolean) {
|
||||
uiState.update { it.copy(isScanInProgress = isLoading) }
|
||||
}
|
||||
|
||||
fun handleScanError(error: TangemError) {
|
||||
when (error) {
|
||||
is TangemSdkError.NfcFeatureIsUnavailable -> handleNfcFeatureUnavailable()
|
||||
is TangemSdkError -> Timber.e(error, "Scan error occurred")
|
||||
else -> Timber.e(error, "Error happened")
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleNfcFeatureUnavailable() {
|
||||
uiMessageSender.send(
|
||||
message = DialogMessage(
|
||||
message = resourceReference(R.string.nfc_error_unavailable),
|
||||
title = resourceReference(id = R.string.common_error),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.features.hotwallet.addexistingwallet.start.entity
|
||||
|
||||
internal data class AddExistingWalletStartUM(
|
||||
val isScanInProgress: Boolean,
|
||||
val onBackClick: () -> Unit,
|
||||
val onImportPhraseClick: () -> Unit,
|
||||
val onScanCardClick: () -> Unit,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.features.hotwallet.addexistingwallet.start.ui
|
|||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
|
|
@ -74,14 +75,25 @@ internal fun AddExistingWalletStartContent(state: AddExistingWalletStartUM, modi
|
|||
title = stringResourceSafe(R.string.wallet_import_scan_title),
|
||||
description = stringResourceSafe(R.string.wallet_import_scan_description),
|
||||
badge = {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.padding(top = 2.dp)
|
||||
.size(20.dp),
|
||||
painter = painterResource(R.drawable.ic_tangem_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.secondary,
|
||||
)
|
||||
if (state.isScanInProgress) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.padding(top = 2.dp)
|
||||
.size(20.dp)
|
||||
.padding(2.dp),
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
strokeWidth = TangemTheme.dimens.size2,
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.padding(top = 2.dp)
|
||||
.size(20.dp),
|
||||
painter = painterResource(R.drawable.ic_tangem_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.secondary,
|
||||
)
|
||||
}
|
||||
},
|
||||
onClick = state.onScanCardClick,
|
||||
enabled = true,
|
||||
|
|
@ -160,6 +172,7 @@ private fun PreviewCreateWalletContent() {
|
|||
TangemThemePreview {
|
||||
AddExistingWalletStartContent(
|
||||
state = AddExistingWalletStartUM(
|
||||
isScanInProgress = true,
|
||||
onBackClick = {},
|
||||
onImportPhraseClick = {},
|
||||
onScanCardClick = {},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue