Updated on 2026-08-14
This commit is contained in:
commit
92849d43c2
1407 changed files with 64092 additions and 11990 deletions
|
|
@ -34,18 +34,11 @@ class CustomerIoAnalyticsHandler(
|
|||
class Builder : AnalyticsHandlerBuilder {
|
||||
override fun build(data: AnalyticsHandlerBuilder.Data): AnalyticsHandler? {
|
||||
val cdpApiKey = data.config.customerIoCdpApiKey
|
||||
return if (data.logConfig.isCustomerIoLogEnabled) {
|
||||
CustomerIoAnalyticsHandler(client = CustomerIoLogClient())
|
||||
} else if (!cdpApiKey.isNullOrBlank()) {
|
||||
CustomerIoAnalyticsHandler(
|
||||
client = CustomerIoClient(
|
||||
application = data.application,
|
||||
cdpApiKey = cdpApiKey,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
if (cdpApiKey.isNullOrBlank()) return null
|
||||
|
||||
return CustomerIoAnalyticsHandler(
|
||||
client = CustomerIoClient(application = data.application, cdpApiKey = cdpApiKey),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
package com.tangem.tap.common.analytics.handlers.customerio
|
||||
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
|
||||
/**
|
||||
* Log client for Customer.io (used in debug mode).
|
||||
*
|
||||
* Logs all operations to Timber instead of sending them to Customer.io.
|
||||
*/
|
||||
internal class CustomerIoLogClient : CustomerIoAnalyticsClient {
|
||||
|
||||
private var userId: String? = null
|
||||
|
||||
override fun setUserId(userId: String) {
|
||||
this.userId = userId
|
||||
TangemLogger.withTag(CustomerIoAnalyticsHandler.ID).d("identify: userId=$userId")
|
||||
}
|
||||
|
||||
override fun clearUserId() {
|
||||
TangemLogger.withTag(CustomerIoAnalyticsHandler.ID).d("clearIdentify: previous userId=$userId")
|
||||
this.userId = null
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,17 @@
|
|||
package com.tangem.tap.data
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import com.tangem.utils.info.AppInfoProvider
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import java.util.Locale
|
||||
import java.util.TimeZone
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultAppInfoProvider @Inject constructor() : AppInfoProvider {
|
||||
internal class DefaultAppInfoProvider @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
) : AppInfoProvider {
|
||||
override val platform: String
|
||||
get() = "Android"
|
||||
override val device: String
|
||||
|
|
@ -18,6 +22,8 @@ internal class DefaultAppInfoProvider @Inject constructor() : AppInfoProvider {
|
|||
get() = Build.VERSION.SDK_INT
|
||||
override val language: String
|
||||
get() = Locale.getDefault().toLanguageTag()
|
||||
override val deviceScale: Float
|
||||
get() = context.resources.displayMetrics.density
|
||||
override val timezone: String
|
||||
get() = TimeZone.getDefault().id
|
||||
override val appVersion: String = BuildConfig.VERSION_NAME
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import com.tangem.sdk.api.TangemSdkManager
|
|||
import com.tangem.tap.domain.sdk.impl.DefaultTangemSdkManager
|
||||
import com.tangem.tap.domain.sdk.impl.MockTangemSdkManager
|
||||
import com.tangem.tap.domain.tasks.visa.TangemPayGenerateAddressAndSignChallengeTask
|
||||
import com.tangem.tap.domain.tasks.visa.TangemPayGenerateVirtualAccountAddressTask
|
||||
import com.tangem.tap.domain.tasks.visa.VisaCardActivationTask
|
||||
import com.tangem.tap.domain.visa.VisaCardScanHandler
|
||||
import dagger.Module
|
||||
|
|
@ -31,6 +32,7 @@ internal class TangemSdkManagerModule {
|
|||
visaCardScanHandler: VisaCardScanHandler,
|
||||
visaCardActivationTaskFactory: VisaCardActivationTask.Factory,
|
||||
tangemPayChallengeTaskFactory: TangemPayGenerateAddressAndSignChallengeTask.Factory,
|
||||
tangemPayVirtualAccountTaskFactory: TangemPayGenerateVirtualAccountAddressTask.Factory,
|
||||
onboardingV2FeatureToggles: OnboardingV2FeatureToggles,
|
||||
analyticsErrorHandler: AnalyticsErrorHandler,
|
||||
cardRepository: CardRepository,
|
||||
|
|
@ -44,6 +46,7 @@ internal class TangemSdkManagerModule {
|
|||
visaCardScanHandler = visaCardScanHandler,
|
||||
visaCardActivationTaskFactory = visaCardActivationTaskFactory,
|
||||
tangemPayChallengeTaskFactory = tangemPayChallengeTaskFactory,
|
||||
tangemPayVirtualAccountTaskFactory = tangemPayVirtualAccountTaskFactory,
|
||||
onboardingV2FeatureToggles = onboardingV2FeatureToggles,
|
||||
analyticsErrorHandler = analyticsErrorHandler,
|
||||
cardRepository = cardRepository,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import com.tangem.core.navigation.deeplink.DeeplinkLauncher
|
|||
import com.tangem.core.navigation.finisher.AppFinisher
|
||||
import com.tangem.core.navigation.settings.SettingsManager
|
||||
import com.tangem.core.navigation.share.ShareManager
|
||||
import com.tangem.core.navigation.url.AppStoreOpener
|
||||
import com.tangem.core.navigation.url.DefaultAppStoreOpener
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.utils.coroutines.AppCoroutineScope
|
||||
import com.tangem.tap.common.finisher.AndroidAppFinisher
|
||||
|
|
@ -35,6 +37,10 @@ internal interface UtilsModule {
|
|||
@Singleton
|
||||
fun bindAppInfoProvider(impl: DefaultAppInfoProvider): AppInfoProvider
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindAppStoreOpener(impl: DefaultAppStoreOpener): AppStoreOpener
|
||||
|
||||
companion object {
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -1,11 +1,21 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.domain.addressbook.crypto.AddressBookCipher
|
||||
import com.tangem.domain.addressbook.interactor.GetVerifiedContactsInteractor
|
||||
import com.tangem.domain.addressbook.interactor.SaveContactInteractor
|
||||
import com.tangem.domain.addressbook.repository.AddressBookRepository
|
||||
import com.tangem.domain.addressbook.time.DefaultIsoTimestampProvider
|
||||
import com.tangem.domain.addressbook.time.IsoTimestampProvider
|
||||
import com.tangem.domain.addressbook.usecase.CheckAddressDuplicateUseCase
|
||||
import com.tangem.domain.addressbook.usecase.DeleteContactUseCase
|
||||
import com.tangem.domain.addressbook.usecase.GetContactByIdUseCase
|
||||
import com.tangem.domain.addressbook.usecase.GetContactsUseCase
|
||||
import com.tangem.domain.addressbook.usecase.SyncAddressBooksUseCase
|
||||
import com.tangem.domain.addressbook.usecase.ValidateContactAddressUseCase
|
||||
import com.tangem.domain.addressbook.usecase.VerifyAddressEntriesUseCase
|
||||
import com.tangem.domain.addressbook.usecase.ValidateContactNameUseCase
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.tokens.GetNetworkAddressesUseCase
|
||||
import com.tangem.domain.transaction.usecase.SignUseCase
|
||||
import com.tangem.domain.transaction.usecase.ValidateWalletAddressUseCase
|
||||
import com.tangem.domain.transaction.usecase.VerifySecp256k1MessagesUseCase
|
||||
import dagger.Module
|
||||
|
|
@ -32,10 +42,68 @@ object AddressBookDomainModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideVerifyAddressEntriesUseCase(
|
||||
fun provideValidateContactNameUseCase(repository: AddressBookRepository): ValidateContactNameUseCase {
|
||||
return ValidateContactNameUseCase(repository = repository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetContactsUseCase(repository: AddressBookRepository): GetContactsUseCase {
|
||||
return GetContactsUseCase(repository = repository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetVerifiedContactsInteractor(
|
||||
getContactsUseCase: GetContactsUseCase,
|
||||
verifyMessagesUseCase: VerifySecp256k1MessagesUseCase,
|
||||
): VerifyAddressEntriesUseCase {
|
||||
return VerifyAddressEntriesUseCase(verifyMessagesUseCase = verifyMessagesUseCase)
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
): GetVerifiedContactsInteractor {
|
||||
return GetVerifiedContactsInteractor(
|
||||
getContacts = getContactsUseCase,
|
||||
verifyMessages = verifyMessagesUseCase,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSaveContactInteractor(
|
||||
repository: AddressBookRepository,
|
||||
validateContactNameUseCase: ValidateContactNameUseCase,
|
||||
signUseCase: SignUseCase,
|
||||
timestampProvider: IsoTimestampProvider,
|
||||
): SaveContactInteractor {
|
||||
return SaveContactInteractor(
|
||||
repository = repository,
|
||||
validateContactName = validateContactNameUseCase,
|
||||
signUseCase = signUseCase,
|
||||
timestampProvider = timestampProvider,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideDeleteContactUseCase(repository: AddressBookRepository): DeleteContactUseCase {
|
||||
return DeleteContactUseCase(repository = repository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetContactByIdUseCase(repository: AddressBookRepository): GetContactByIdUseCase {
|
||||
return GetContactByIdUseCase(repository = repository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideCheckAddressDuplicateUseCase(repository: AddressBookRepository): CheckAddressDuplicateUseCase {
|
||||
return CheckAddressDuplicateUseCase(repository = repository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSyncAddressBooksUseCase(repository: AddressBookRepository): SyncAddressBooksUseCase {
|
||||
return SyncAddressBooksUseCase(repository = repository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -56,6 +56,14 @@ internal object ManageTokensDomainModule {
|
|||
return ValidateDerivationPathUseCase(customTokensRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideCheckDerivationPathSupportedUseCase(
|
||||
customTokensRepository: CustomTokensRepository,
|
||||
): CheckDerivationPathSupportedUseCase {
|
||||
return CheckDerivationPathSupportedUseCase(customTokensRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideCheckCurrencyUnsupportedUseCase(repository: ManageTokensRepository): CheckCurrencyUnsupportedUseCase {
|
||||
|
|
|
|||
|
|
@ -4,13 +4,11 @@ import com.tangem.domain.swap.SwapErrorResolver
|
|||
import com.tangem.domain.swap.SwapRepositoryV2
|
||||
import com.tangem.domain.swap.SwapTransactionRepository
|
||||
import com.tangem.domain.swap.usecase.*
|
||||
import com.tangem.feature.swap.domain.GetAvailablePairsUseCase
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
import com.tangem.feature.swap.domain.api.SwapRepository as OldSwapRepository
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -19,12 +17,6 @@ import com.tangem.feature.swap.domain.api.SwapRepository as OldSwapRepository
|
|||
@InstallIn(SingletonComponent::class)
|
||||
internal object SwapDomainModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetAvailablePairsUseCase(swapRepository: OldSwapRepository): GetAvailablePairsUseCase {
|
||||
return GetAvailablePairsUseCase(swapRepository = swapRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetSwapSupportedPairsUseCase(
|
||||
|
|
|
|||
|
|
@ -6,9 +6,13 @@ import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
|||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.demo.models.DemoConfig
|
||||
import com.tangem.core.configtoggle.FeatureToggles
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.domain.dynamicaddresses.DynamicAddressesFeatureToggles
|
||||
import com.tangem.domain.dynamicaddresses.GetDynamicReceiveAddressUseCase
|
||||
import com.tangem.domain.dynamicaddresses.repository.DynamicAddressesRepository
|
||||
import com.tangem.domain.transaction.GaslessYieldRepository
|
||||
import com.tangem.domain.transaction.usecase.gasless.ResolveGaslessFeePlanUseCase
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
|
||||
import com.tangem.domain.notifications.repository.PushNotificationsRepository
|
||||
|
|
@ -319,30 +323,50 @@ internal object TransactionDomainModule {
|
|||
gaslessTransactionRepository: GaslessTransactionRepository,
|
||||
singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||
currencyChecksRepository: CurrencyChecksRepository,
|
||||
featureTogglesManager: FeatureTogglesManager,
|
||||
): GetAvailableFeeTokensUseCase {
|
||||
return GetAvailableFeeTokensUseCase(
|
||||
singleAccountStatusListSupplier = singleAccountStatusListSupplier,
|
||||
gaslessTransactionRepository = gaslessTransactionRepository,
|
||||
currencyChecksRepository = currencyChecksRepository,
|
||||
isYieldWithdrawEnabled = featureTogglesManager.isFeatureEnabled(
|
||||
toggle = FeatureToggles.AND_15632_GASLESS_YIELD_WITHDRAW_ENABLED,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideResolveGaslessFeePlanUseCase(
|
||||
gaslessYieldRepository: GaslessYieldRepository,
|
||||
): ResolveGaslessFeePlanUseCase {
|
||||
return ResolveGaslessFeePlanUseCase(gaslessYieldRepository = gaslessYieldRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetFeeForGaslessUseCase(
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
gaslessTransactionRepository: GaslessTransactionRepository,
|
||||
gaslessYieldRepository: GaslessYieldRepository,
|
||||
getFeeUseCase: GetFeeUseCase,
|
||||
singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||
currencyChecksRepository: CurrencyChecksRepository,
|
||||
resolveGaslessFeePlanUseCase: ResolveGaslessFeePlanUseCase,
|
||||
featureTogglesManager: FeatureTogglesManager,
|
||||
): GetFeeForGaslessUseCase {
|
||||
return GetFeeForGaslessUseCase(
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
demoConfig = DemoConfig,
|
||||
gaslessTransactionRepository = gaslessTransactionRepository,
|
||||
gaslessYieldRepository = gaslessYieldRepository,
|
||||
singleAccountStatusListSupplier = singleAccountStatusListSupplier,
|
||||
getFeeUseCase = getFeeUseCase,
|
||||
currencyChecksRepository = currencyChecksRepository,
|
||||
resolveGaslessFeePlanUseCase = resolveGaslessFeePlanUseCase,
|
||||
isYieldWithdrawEnabled = featureTogglesManager.isFeatureEnabled(
|
||||
toggle = FeatureToggles.AND_15632_GASLESS_YIELD_WITHDRAW_ENABLED,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -351,15 +375,23 @@ internal object TransactionDomainModule {
|
|||
fun provideGetFeeForTokenUseCase(
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
gaslessTransactionRepository: GaslessTransactionRepository,
|
||||
gaslessYieldRepository: GaslessYieldRepository,
|
||||
singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||
currencyChecksRepository: CurrencyChecksRepository,
|
||||
resolveGaslessFeePlanUseCase: ResolveGaslessFeePlanUseCase,
|
||||
featureTogglesManager: FeatureTogglesManager,
|
||||
): GetFeeForTokenUseCase {
|
||||
return GetFeeForTokenUseCase(
|
||||
gaslessTransactionRepository = gaslessTransactionRepository,
|
||||
gaslessYieldRepository = gaslessYieldRepository,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
demoConfig = DemoConfig,
|
||||
singleAccountStatusListSupplier = singleAccountStatusListSupplier,
|
||||
currencyChecksRepository = currencyChecksRepository,
|
||||
resolveGaslessFeePlanUseCase = resolveGaslessFeePlanUseCase,
|
||||
isYieldWithdrawEnabled = featureTogglesManager.isFeatureEnabled(
|
||||
toggle = FeatureToggles.AND_15632_GASLESS_YIELD_WITHDRAW_ENABLED,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -379,6 +411,7 @@ internal object TransactionDomainModule {
|
|||
singleAccountListSupplier: SingleAccountListSupplier,
|
||||
cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
tangemHotWalletSignerFactory: TangemHotWalletSigner.Factory,
|
||||
featureTogglesManager: FeatureTogglesManager,
|
||||
): CreateAndSendGaslessTransactionUseCase {
|
||||
return CreateAndSendGaslessTransactionUseCase(
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
|
|
@ -386,6 +419,9 @@ internal object TransactionDomainModule {
|
|||
gaslessTransactionRepository = gaslessTransactionRepository,
|
||||
cardSdkConfigRepository = cardSdkConfigRepository,
|
||||
getHotWalletSigner = tangemHotWalletSignerFactory::create,
|
||||
isGaslessV2Enabled = featureTogglesManager.isFeatureEnabled(
|
||||
toggle = FeatureToggles.AND_15632_GASLESS_YIELD_WITHDRAW_ENABLED,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -394,15 +430,21 @@ internal object TransactionDomainModule {
|
|||
fun provideEstimateFeeForTokenUseCase(
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
gaslessTransactionRepository: GaslessTransactionRepository,
|
||||
gaslessYieldRepository: GaslessYieldRepository,
|
||||
singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||
currencyChecksRepository: CurrencyChecksRepository,
|
||||
featureTogglesManager: FeatureTogglesManager,
|
||||
): EstimateFeeForTokenUseCase {
|
||||
return EstimateFeeForTokenUseCase(
|
||||
gaslessTransactionRepository = gaslessTransactionRepository,
|
||||
gaslessYieldRepository = gaslessYieldRepository,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
demoConfig = DemoConfig,
|
||||
singleAccountStatusListSupplier = singleAccountStatusListSupplier,
|
||||
currencyChecksRepository = currencyChecksRepository,
|
||||
isYieldWithdrawEnabled = featureTogglesManager.isFeatureEnabled(
|
||||
toggle = FeatureToggles.AND_15632_GASLESS_YIELD_WITHDRAW_ENABLED,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -411,12 +453,14 @@ internal object TransactionDomainModule {
|
|||
fun provideEstimateFeeForGaslessTxUseCase(
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
gaslessTransactionRepository: GaslessTransactionRepository,
|
||||
gaslessYieldRepository: GaslessYieldRepository,
|
||||
singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||
estimateFeeUseCase: EstimateFeeUseCase,
|
||||
currencyChecksRepository: CurrencyChecksRepository,
|
||||
): EstimateFeeForGaslessTxUseCase {
|
||||
return EstimateFeeForGaslessTxUseCase(
|
||||
gaslessTransactionRepository = gaslessTransactionRepository,
|
||||
gaslessYieldRepository = gaslessYieldRepository,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
demoConfig = DemoConfig,
|
||||
singleAccountStatusListSupplier = singleAccountStatusListSupplier,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.tap.di.domain
|
|||
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||
import com.tangem.domain.common.wallets.UserWalletDataCleaner
|
||||
import com.tangem.domain.common.wallets.UserWalletSelectedHandler
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.transaction.WalletAddressServiceRepository
|
||||
|
|
@ -25,6 +26,7 @@ import com.tangem.feature.wallet.presentation.wallet.domain.IsWalletNFTEnabledSy
|
|||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletNameMigrationUseCase
|
||||
import com.tangem.operations.attestation.CardArtworksProvider
|
||||
import com.tangem.tap.domain.DefaultUserWalletSelectedHandler
|
||||
import com.tangem.utils.coroutines.AppCoroutineScope
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -188,8 +190,16 @@ internal object WalletsDomainModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesDeleteWalletUseCase(userWalletsListRepository: UserWalletsListRepository): DeleteWalletUseCase {
|
||||
return DeleteWalletUseCase(userWalletsListRepository = userWalletsListRepository)
|
||||
fun providesDeleteWalletUseCase(
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
userWalletDataCleaners: Set<@JvmSuppressWildcards UserWalletDataCleaner>,
|
||||
appCoroutineScope: AppCoroutineScope,
|
||||
): DeleteWalletUseCase {
|
||||
return DeleteWalletUseCase(
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
userWalletDataCleaners = userWalletDataCleaners,
|
||||
appCoroutineScope = appCoroutineScope,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ import com.tangem.tap.common.analytics.events.TangemSdkErrorEvent
|
|||
import com.tangem.tap.common.analytics.paramsInterceptor.CardContextInterceptor
|
||||
import com.tangem.tap.domain.tasks.product.*
|
||||
import com.tangem.tap.domain.tasks.visa.TangemPayGenerateAddressAndSignChallengeTask
|
||||
import com.tangem.tap.domain.tasks.visa.TangemPayGenerateVirtualAccountAddressTask
|
||||
import com.tangem.tap.domain.tasks.visa.TangemPaySignWithdrawalHashTask
|
||||
import com.tangem.tap.domain.tasks.visa.VisaCardActivationTask
|
||||
import com.tangem.tap.domain.tasks.visa.VisaCustomerWalletApproveTask
|
||||
|
|
@ -72,6 +73,7 @@ internal class DefaultTangemSdkManager(
|
|||
private val visaCardScanHandler: VisaCardScanHandler,
|
||||
private val visaCardActivationTaskFactory: VisaCardActivationTask.Factory,
|
||||
private val tangemPayChallengeTaskFactory: TangemPayGenerateAddressAndSignChallengeTask.Factory,
|
||||
private val tangemPayVirtualAccountTaskFactory: TangemPayGenerateVirtualAccountAddressTask.Factory,
|
||||
private val onboardingV2FeatureToggles: OnboardingV2FeatureToggles,
|
||||
private val analyticsErrorHandler: AnalyticsErrorHandler,
|
||||
private val cardRepository: CardRepository,
|
||||
|
|
@ -86,7 +88,7 @@ internal class DefaultTangemSdkManager(
|
|||
secureStorage = tangemSdk.secureStorage,
|
||||
)
|
||||
}
|
||||
override val needEnrollBiometrics: Boolean
|
||||
override val isEnrollBiometricsNeeded: Boolean
|
||||
get() {
|
||||
val isNeedEnrollBiometrics = tangemSdk.authenticationManager.needEnrollBiometrics
|
||||
if (isNeedEnrollBiometrics) {
|
||||
|
|
@ -102,7 +104,7 @@ internal class DefaultTangemSdkManager(
|
|||
|
||||
override val canUseBiometry: Boolean
|
||||
get() {
|
||||
val isCanUseBiometry = tangemSdk.authenticationManager.canAuthenticate || needEnrollBiometrics
|
||||
val isCanUseBiometry = tangemSdk.authenticationManager.canAuthenticate || isEnrollBiometricsNeeded
|
||||
if (!isCanUseBiometry) {
|
||||
analyticsErrorHandler.sendErrorEvent(
|
||||
AnalyticsEvent(
|
||||
|
|
@ -124,7 +126,7 @@ internal class DefaultTangemSdkManager(
|
|||
get() = tangemSdk.config.userCodeRequestPolicy
|
||||
|
||||
override suspend fun checkNeedEnrollBiometrics(awaitInitialization: Boolean): Boolean {
|
||||
return needEnrollBiometrics
|
||||
return isEnrollBiometricsNeeded
|
||||
}
|
||||
|
||||
override suspend fun checkCanUseBiometry(awaitInitialization: Boolean): Boolean {
|
||||
|
|
@ -531,6 +533,24 @@ internal class DefaultTangemSdkManager(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun tangemPayProduceVirtualAccountData(
|
||||
preflightReadFilter: PreflightReadFilter,
|
||||
): Either<Throwable, VirtualAccountActivationData> {
|
||||
return coroutineScope {
|
||||
val result = runTaskAsyncReturnOnMain(
|
||||
runnable = tangemPayVirtualAccountTaskFactory.create(coroutineScope = this),
|
||||
cardId = null,
|
||||
initialMessage = Message(resources.getStringSafe(R.string.initial_message_tap_header)),
|
||||
preflightReadFilter = preflightReadFilter,
|
||||
)
|
||||
|
||||
return@coroutineScope when (result) {
|
||||
is CompletionResult.Failure<*> -> result.error.left()
|
||||
is CompletionResult.Success<VirtualAccountActivationData> -> result.data.right()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getWithdrawalSignature(
|
||||
hash: String,
|
||||
preflightReadFilter: PreflightReadFilter,
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import com.tangem.domain.models.scan.ScanResponse
|
|||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.WithdrawalSignatureResult
|
||||
import com.tangem.domain.visa.model.TangemPayInitialCredentials
|
||||
import com.tangem.domain.visa.model.VirtualAccountActivationData
|
||||
import com.tangem.domain.visa.model.VisaActivationInput
|
||||
import com.tangem.domain.visa.model.VisaDataForApprove
|
||||
import com.tangem.domain.visa.model.VisaSignedDataByCustomerWallet
|
||||
|
|
@ -46,7 +47,7 @@ class MockTangemSdkManager(
|
|||
|
||||
override val canUseBiometry: Boolean = false
|
||||
|
||||
override val needEnrollBiometrics: Boolean = false
|
||||
override val isEnrollBiometricsNeeded: Boolean = false
|
||||
|
||||
override val keystoreManager = DummyKeystoreManager()
|
||||
|
||||
|
|
@ -57,7 +58,7 @@ class MockTangemSdkManager(
|
|||
|
||||
override suspend fun checkCanUseBiometry(awaitInitialization: Boolean): Boolean = canUseBiometry
|
||||
|
||||
override suspend fun checkNeedEnrollBiometrics(awaitInitialization: Boolean): Boolean = needEnrollBiometrics
|
||||
override suspend fun checkNeedEnrollBiometrics(awaitInitialization: Boolean): Boolean = isEnrollBiometricsNeeded
|
||||
|
||||
override suspend fun scanProduct(
|
||||
cardId: String?,
|
||||
|
|
@ -241,6 +242,12 @@ class MockTangemSdkManager(
|
|||
error("Not implemented")
|
||||
}
|
||||
|
||||
override suspend fun tangemPayProduceVirtualAccountData(
|
||||
preflightReadFilter: PreflightReadFilter,
|
||||
): Either<Throwable, VirtualAccountActivationData> {
|
||||
error("Not implemented")
|
||||
}
|
||||
|
||||
override suspend fun getWithdrawalSignature(
|
||||
hash: String,
|
||||
preflightReadFilter: PreflightReadFilter,
|
||||
|
|
|
|||
|
|
@ -33,6 +33,9 @@ object MockProvider {
|
|||
MockOption("Shiba (No Backup, No Wallets)") { ShibaNoBackupNoWalletsMockContent },
|
||||
MockOption("Ed25519 Curve") { EdCurveMockContent },
|
||||
MockOption("Secp256k1 Curve") { Secpk1CurveMockContent },
|
||||
MockOption("Wallet 2 (No ed25519_slip0010)") { Wallet2NoEd25519Slip0010MockContent },
|
||||
MockOption("Wallet 1 (Legacy derivation)") { Wallet1LegacyDerivationMockContent },
|
||||
MockOption("Firmware 4.51") { Firmware451MockContent },
|
||||
MockOption("Backup Wallet") { BackupWalletMockContent },
|
||||
MockOption("Dev Wallet") { DevWalletMockContent },
|
||||
MockOption("Firmware 4.12") { Firmware412MockContent },
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.tap.domain.sdk.mocks.content
|
||||
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.domain.sdk.mocks.MockContent
|
||||
|
||||
// Firmware 4.51: HD-capable (>= 4.39) but below SolanaTokensAvailable (4.52), so Solana tokens are firmware-limited.
|
||||
object Firmware451MockContent : MockContent by WalletMockContent {
|
||||
|
||||
override val cardDto: CardDTO = WalletMockContent.cardDto.copy(
|
||||
firmwareVersion = WalletMockContent.cardDto.firmwareVersion.copy(minor = 51),
|
||||
)
|
||||
|
||||
override val scanResponse: ScanResponse = WalletMockContent.scanResponse.copy(card = cardDto)
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.tap.domain.sdk.mocks.content
|
||||
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.domain.sdk.mocks.MockContent
|
||||
|
||||
// Wallet1 on a first-batch id (AC01) — resolves to the V1 (legacy) derivation style.
|
||||
object Wallet1LegacyDerivationMockContent : MockContent by WalletMockContent {
|
||||
|
||||
override val cardDto: CardDTO = WalletMockContent.cardDto.copy(batchId = "AC01")
|
||||
|
||||
override val scanResponse: ScanResponse = WalletMockContent.scanResponse.copy(card = cardDto)
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.tap.domain.sdk.mocks.content
|
||||
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.domain.sdk.mocks.MockContent
|
||||
|
||||
// Wallet2 without its ed25519_slip0010 wallet, so adding Solana warns UnsupportedCurve (no wallet for its curve).
|
||||
object Wallet2NoEd25519Slip0010MockContent : MockContent by Wallet2WithSeedPhraseMockContent {
|
||||
|
||||
override val cardDto: CardDTO = Wallet2WithSeedPhraseMockContent.cardDto.copy(
|
||||
wallets = Wallet2WithSeedPhraseMockContent.cardDto.wallets.filterNot {
|
||||
it.curve == EllipticCurve.Ed25519Slip0010
|
||||
},
|
||||
)
|
||||
|
||||
override val scanResponse: ScanResponse = Wallet2WithSeedPhraseMockContent.scanResponse.copy(card = cardDto)
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
package com.tangem.tap.domain.tasks.visa
|
||||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.CardWallet
|
||||
import com.tangem.common.core.CardSession
|
||||
import com.tangem.common.core.CardSessionRunnable
|
||||
import com.tangem.common.core.CompletionCallback
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.extensions.toMapKey
|
||||
import com.tangem.core.error.ext.tangemError
|
||||
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||
import com.tangem.domain.card.common.visa.VisaUtilities
|
||||
import com.tangem.domain.visa.error.VisaActivationError
|
||||
import com.tangem.domain.visa.model.VirtualAccountActivationData
|
||||
import com.tangem.operations.derivation.DeriveWalletPublicKeyTask
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* Derives the Virtual Account key ([VisaUtilities.virtualAccountDerivationPath]) on the card and
|
||||
* generates its deposit address. The derived key is returned (keyed by the seed wallet public key)
|
||||
* so the caller can persist it via `DerivationsRepository.storeDerivedKeys` — no second tap needed.
|
||||
*/
|
||||
class TangemPayGenerateVirtualAccountAddressTask @AssistedInject constructor(
|
||||
@Assisted private val coroutineScope: CoroutineScope,
|
||||
) : CardSessionRunnable<VirtualAccountActivationData> {
|
||||
|
||||
override fun run(session: CardSession, callback: CompletionCallback<VirtualAccountActivationData>) {
|
||||
coroutineScope.launch {
|
||||
callback(runSuspend(session = session))
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun runSuspend(session: CardSession): CompletionResult<VirtualAccountActivationData> {
|
||||
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
val wallet = card.wallets.firstOrNull { it.curve == VisaUtilities.curve }
|
||||
?: return CompletionResult.Failure(VisaActivationError.MissingWallet.tangemError)
|
||||
|
||||
val extendedPublicKey = when (val derivationResult = runDerivationTask(session, wallet)) {
|
||||
is CompletionResult.Failure<*> -> return CompletionResult.Failure(derivationResult.error)
|
||||
is CompletionResult.Success<ExtendedPublicKey> -> derivationResult.data
|
||||
}
|
||||
|
||||
val address = VisaUtilities.generateAddressFromExtendedKey(extendedPublicKey = extendedPublicKey)
|
||||
|
||||
val derivedKeys = mapOf(
|
||||
wallet.publicKey.toMapKey() to ExtendedPublicKeysMap(
|
||||
mapOf(VisaUtilities.virtualAccountDerivationPath to extendedPublicKey),
|
||||
),
|
||||
)
|
||||
|
||||
return CompletionResult.Success(
|
||||
data = VirtualAccountActivationData(address = address, derivedKeys = derivedKeys),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun runDerivationTask(
|
||||
session: CardSession,
|
||||
wallet: CardWallet,
|
||||
): CompletionResult<ExtendedPublicKey> {
|
||||
val deferred = CompletableDeferred<CompletionResult<ExtendedPublicKey>>()
|
||||
val derivationTask = DeriveWalletPublicKeyTask(
|
||||
walletPublicKey = wallet.publicKey,
|
||||
derivationPath = VisaUtilities.virtualAccountDerivationPath,
|
||||
)
|
||||
|
||||
derivationTask.run(session = session, callback = deferred::complete)
|
||||
return deferred.await()
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(coroutineScope: CoroutineScope): TangemPayGenerateVirtualAccountAddressTask
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.tap.domain.userWalletList.di
|
||||
|
||||
import com.tangem.domain.common.wallets.UserWalletDataCleaner
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.Multibinds
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface UserWalletDataCleanerModule {
|
||||
|
||||
@Multibinds
|
||||
fun userWalletDataCleaners(): Set<UserWalletDataCleaner>
|
||||
}
|
||||
|
|
@ -628,5 +628,6 @@ internal class DefaultUserWalletsListRepository(
|
|||
private suspend fun onAllWalletsDeleted() {
|
||||
// reset the referral attribution (set from AF deeplink) after removing the last wallet
|
||||
clearAppsFlyerDeeplinkUseCase(AppsFlyerDeeplinkSource.Referral)
|
||||
appPreferencesStore.editData { it.remove(PreferencesKeys.USEDESK_CLIENT_ID_KEY) }
|
||||
}
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@ internal fun AppSettingsScreen(state: AppSettingsScreenState, onBackClick: () ->
|
|||
modifier = modifier,
|
||||
titleRes = R.string.app_settings_title,
|
||||
addBottomInsets = false,
|
||||
backButtonTestTag = AppSettingsScreenTestTags.BACK_BUTTON,
|
||||
content = {
|
||||
when (state) {
|
||||
is AppSettingsScreenState.Content -> AppSettings(state = state)
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ internal class AppSettingsModel @Inject constructor(
|
|||
private fun observeBiometricsStatusChanges() {
|
||||
flow {
|
||||
do {
|
||||
val isEnrollBiometricsNeeded = runCatching(tangemSdkManager::needEnrollBiometrics).getOrNull()
|
||||
val isEnrollBiometricsNeeded = runCatching(tangemSdkManager::isEnrollBiometricsNeeded).getOrNull()
|
||||
if (isEnrollBiometricsNeeded != null) {
|
||||
emit(isEnrollBiometricsNeeded)
|
||||
}
|
||||
|
|
@ -366,7 +366,7 @@ internal class AppSettingsModel @Inject constructor(
|
|||
localState.update { state ->
|
||||
state.copy(
|
||||
hasSecuredWallets = userWalletsListRepository.hasSecuredWallets(),
|
||||
isEnrollBiometricsNeeded = runCatching(tangemSdkManager::needEnrollBiometrics).getOrNull() == true,
|
||||
isEnrollBiometricsNeeded = runCatching(tangemSdkManager::isEnrollBiometricsNeeded).getOrNull() == true,
|
||||
isBiometricAuthenticationUsed = walletsRepository.useBiometricAuthentication(),
|
||||
isAccessCodeRequired = walletsRepository.requireAccessCode(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.PrimaryButtonIconEnd
|
||||
|
|
@ -23,6 +24,7 @@ internal fun SettingsScreensScaffold(
|
|||
@StringRes titleRes: Int? = null,
|
||||
addBottomInsets: Boolean = true,
|
||||
snackbarHostState: SnackbarHostState = remember { SnackbarHostState() },
|
||||
backButtonTestTag: String? = null,
|
||||
content: @Composable () -> Unit,
|
||||
fab: @Composable () -> Unit = {},
|
||||
) {
|
||||
|
|
@ -35,6 +37,7 @@ internal fun SettingsScreensScaffold(
|
|||
modifier = Modifier.statusBarsPadding(),
|
||||
onBackClick = onBackClick,
|
||||
backgroundColor = backgroundColor,
|
||||
backButtonTestTag = backButtonTestTag,
|
||||
)
|
||||
},
|
||||
modifier = modifier,
|
||||
|
|
@ -91,13 +94,17 @@ internal fun EmptyTopBarWithNavigation(
|
|||
onBackClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
backgroundColor: Color = TangemTheme.colors.background.primary,
|
||||
backButtonTestTag: String? = null,
|
||||
) {
|
||||
TopAppBar(
|
||||
modifier = modifier,
|
||||
title = { },
|
||||
navigationIcon =
|
||||
{
|
||||
IconButton(onClick = onBackClick) {
|
||||
IconButton(
|
||||
onClick = onBackClick,
|
||||
modifier = if (backButtonTestTag != null) Modifier.testTag(backButtonTestTag) else Modifier,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_back_24),
|
||||
contentDescription = null,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.tap.features.root
|
||||
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
internal class DefaultRootWarningContinuation @Inject constructor() : RootWarningContinuation {
|
||||
|
||||
private val dismissals = Channel<Unit>(capacity = Channel.CONFLATED)
|
||||
|
||||
override suspend fun awaitDismiss() {
|
||||
dismissals.receive()
|
||||
}
|
||||
|
||||
override fun dismiss() {
|
||||
dismissals.trySend(Unit)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,68 +1,40 @@
|
|||
package com.tangem.tap.features.root
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.arkivanov.essenty.instancekeeper.getOrCreateSimple
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.components.DialogFullScreen
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.security.DeviceSecurityInfoProvider
|
||||
import com.tangem.security.isSecurityExposed
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* Full-screen root-detected security warning. Presentational only — its visibility is controlled by the
|
||||
* startup gate (via a childSlot); "Continue" resolves [RootWarningContinuation]. Whether it should be shown
|
||||
* at all (and marking it as shown) is decided by the gate.
|
||||
*/
|
||||
@Suppress("UnusedPrivateProperty")
|
||||
class RootDetectedWarningComponent @AssistedInject constructor(
|
||||
internal class RootDetectedWarningComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted params: Unit,
|
||||
private val securityInfoProvider: DeviceSecurityInfoProvider,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val rootWarningContinuation: RootWarningContinuation,
|
||||
) : AppComponentContext by appComponentContext, ComposableContentComponent {
|
||||
|
||||
private val isShown = instanceKeeper.getOrCreateSimple { MutableStateFlow(false) }
|
||||
|
||||
suspend fun shouldShowWarning(): Boolean {
|
||||
return settingsRepository.isRootDetectedWarningShown().not() && securityInfoProvider.isSecurityExposed()
|
||||
}
|
||||
|
||||
suspend fun tryToShowWarningAndWaitContinuation() {
|
||||
if (isShown.value) return
|
||||
|
||||
if (shouldShowWarning()) {
|
||||
isShown.value = true
|
||||
}
|
||||
|
||||
isShown.first { it == false } // Wait until the warning is dismissed
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val isShownState by isShown.collectAsStateWithLifecycle()
|
||||
|
||||
if (isShownState) {
|
||||
DialogFullScreen(onDismissRequest = {}) {
|
||||
RootDetectedWarningContent(
|
||||
modifier = modifier,
|
||||
onContinueClick = remember(this) { ::onContinueClick },
|
||||
)
|
||||
}
|
||||
DialogFullScreen(onDismissRequest = {}) {
|
||||
RootDetectedWarningContent(
|
||||
modifier = modifier,
|
||||
onContinueClick = remember(this) { ::onContinueClick },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onContinueClick() {
|
||||
componentScope.launch {
|
||||
settingsRepository.setRootDetectedWarningShown(true)
|
||||
isShown.value = false
|
||||
}
|
||||
rootWarningContinuation.dismiss()
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.tap.features.root
|
||||
|
||||
/**
|
||||
* Resumes the startup gate after the root-detected security warning is dismissed.
|
||||
*
|
||||
* The gate awaits [awaitDismiss] while the warning is shown, and the screen calls [dismiss] on "Continue".
|
||||
*/
|
||||
interface RootWarningContinuation {
|
||||
|
||||
suspend fun awaitDismiss()
|
||||
|
||||
fun dismiss()
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.tap.features.root.di
|
||||
|
||||
import com.tangem.tap.features.root.DefaultRootWarningContinuation
|
||||
import com.tangem.tap.features.root.RootWarningContinuation
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface RootModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindRootWarningContinuation(impl: DefaultRootWarningContinuation): RootWarningContinuation
|
||||
}
|
||||
|
|
@ -43,7 +43,7 @@ internal fun RootContent(
|
|||
modifier: Modifier = Modifier,
|
||||
wcContent: @Composable (modifier: Modifier) -> Unit,
|
||||
hotAccessCodeContent: @Composable (modifier: Modifier) -> Unit,
|
||||
rootDetectedWarningContent: @Composable (modifier: Modifier) -> Unit,
|
||||
startupGateContent: @Composable (modifier: Modifier) -> Unit,
|
||||
scanFailsContent: @Composable (modifier: Modifier) -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
|
@ -82,7 +82,7 @@ internal fun RootContent(
|
|||
|
||||
hotAccessCodeContent(Modifier.fillMaxSize())
|
||||
|
||||
rootDetectedWarningContent(Modifier.fillMaxSize())
|
||||
startupGateContent(Modifier.fillMaxSize())
|
||||
|
||||
scanFailsContent(Modifier.fillMaxSize())
|
||||
|
||||
|
|
|
|||
|
|
@ -54,13 +54,13 @@ import com.tangem.sdk.api.BackupServiceHolder
|
|||
import com.tangem.tap.common.SnackbarHandler
|
||||
import com.tangem.tap.common.analytics.appsflyer.AppsFlyerReferralParamsHandler
|
||||
import com.tangem.tap.features.hot.TangemHotSDKProxy
|
||||
import com.tangem.tap.features.root.RootDetectedWarningComponent
|
||||
import com.tangem.tap.features.scanfails.ScanFailsComponent
|
||||
import com.tangem.tap.features.scanfails.ScanFailsRequesterProxy
|
||||
import com.tangem.tap.routing.RootContent
|
||||
import com.tangem.tap.routing.component.RoutingComponent
|
||||
import com.tangem.tap.routing.component.RoutingComponent.Child
|
||||
import com.tangem.tap.routing.configurator.AppRouterConfig
|
||||
import com.tangem.tap.routing.startup.AppStartupGateComponent
|
||||
import com.tangem.tap.routing.utils.ChildFactory
|
||||
import com.tangem.tap.routing.utils.DeepLinkFactory
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
|
|
@ -87,7 +87,7 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
|
|||
private val tangemHotSDKProxy: TangemHotSDKProxy,
|
||||
private val hotAccessCodeRequestComponentFactory: HotAccessCodeRequestComponent.Factory,
|
||||
private val hotAccessCodeRequesterProxy: HotWalletPasswordRequesterProxy,
|
||||
private val rootDetectedWarningComponentFactory: RootDetectedWarningComponent.Factory,
|
||||
private val appStartupGateComponentFactory: AppStartupGateComponent.Factory,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val cardRepository: CardRepository,
|
||||
private val onboardingRepository: OnboardingRepository,
|
||||
|
|
@ -117,9 +117,8 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
|
|||
.create(child("hotAccessCodeRequestComponent"), Unit)
|
||||
}
|
||||
|
||||
private val rootDetectedWarningComponent: RootDetectedWarningComponent by lazy {
|
||||
rootDetectedWarningComponentFactory
|
||||
.create(child("rootDetectedWarningComponent"), Unit)
|
||||
private val appStartupGateComponent: AppStartupGateComponent by lazy {
|
||||
appStartupGateComponentFactory.create(child("appStartupGate"))
|
||||
}
|
||||
|
||||
private val scanFailsComponent: ScanFailsComponent by lazy {
|
||||
|
|
@ -175,40 +174,37 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
|
|||
|
||||
private fun initializeInitialNavigation() {
|
||||
if (initialStack.isNullOrEmpty()) {
|
||||
componentScope.launch {
|
||||
val initialRoute = resolveInitialRoute()
|
||||
if (rootDetectedWarningComponent.shouldShowWarning()) {
|
||||
launch(dispatchers.main) {
|
||||
rootDetectedWarningComponent.tryToShowWarningAndWaitContinuation()
|
||||
router.replaceAll(initialRoute)
|
||||
}
|
||||
} else {
|
||||
router.replaceAll(initialRoute)
|
||||
}
|
||||
}
|
||||
componentScope.launch { resolveAndNavigate() }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun resolveInitialRoute(): AppRoute {
|
||||
private suspend fun resolveAndNavigate() {
|
||||
appStartupGateComponent.await()
|
||||
navigateToStartRoute()
|
||||
}
|
||||
|
||||
private suspend fun navigateToStartRoute() {
|
||||
val initialRoute = resolveStartRoute()
|
||||
onInitialRouteResolved(initialRoute)
|
||||
router.replaceAll(initialRoute)
|
||||
}
|
||||
|
||||
private suspend fun resolveStartRoute(): AppRoute {
|
||||
val userWallets = userWalletsListRepository.userWalletsSync()
|
||||
|
||||
return when {
|
||||
userWallets.isEmpty() -> navigateForEmptyWallets()
|
||||
userWallets.any { it.isLocked } -> {
|
||||
AppRoute.Welcome(
|
||||
launchMode = launchMode,
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
trackSignInEvent()
|
||||
AppRoute.Wallet
|
||||
}
|
||||
}.also {
|
||||
appRouterConfig.initializedState.value = true
|
||||
checkForUnfinishedBackup()
|
||||
userWallets.any { it.isLocked } -> AppRoute.Welcome(launchMode = launchMode)
|
||||
else -> AppRoute.Wallet
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun onInitialRouteResolved(route: AppRoute) {
|
||||
appRouterConfig.initializedState.value = true
|
||||
if (route is AppRoute.Wallet) trackSignInEvent()
|
||||
checkForUnfinishedBackup()
|
||||
}
|
||||
|
||||
private suspend fun navigateForEmptyWallets(): AppRoute {
|
||||
val afterEmptyRoute = resolveAppsFlyerOnboardingRoute()
|
||||
?: AppRoute.Home(launchMode = launchMode)
|
||||
|
|
@ -279,7 +275,7 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
|
|||
modifier = modifier,
|
||||
wcContent = { wcRoutingComponent.Content(it) },
|
||||
hotAccessCodeContent = { hotAccessCodeRequestComponent.Content(it) },
|
||||
rootDetectedWarningContent = { rootDetectedWarningComponent.Content(it) },
|
||||
startupGateContent = { appStartupGateComponent.Content(it) },
|
||||
scanFailsContent = { scanFailsComponent.Content(it) },
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,141 @@
|
|||
package com.tangem.tap.routing.startup
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.arkivanov.decompose.extensions.compose.subscribeAsState
|
||||
import com.arkivanov.decompose.router.slot.ChildSlot
|
||||
import com.arkivanov.decompose.router.slot.SlotNavigation
|
||||
import com.arkivanov.decompose.router.slot.activate
|
||||
import com.arkivanov.decompose.router.slot.childSlot
|
||||
import com.arkivanov.decompose.router.slot.dismiss
|
||||
import com.arkivanov.decompose.value.Value
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.context.childByContext
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.appupdate.model.AppUpdateState
|
||||
import com.tangem.domain.appupdate.usecase.GetAppUpdateStateUseCase
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.features.forceupdate.ForceUpdateComponent
|
||||
import com.tangem.features.forceupdate.ForceUpdateContinuation
|
||||
import com.tangem.features.forceupdate.ForceUpdateFeatureToggles
|
||||
import com.tangem.security.DeviceSecurityInfoProvider
|
||||
import com.tangem.security.isSecurityExposed
|
||||
import com.tangem.tap.features.root.RootDetectedWarningComponent
|
||||
import com.tangem.tap.features.root.RootWarningContinuation
|
||||
import com.tangem.tap.routing.configurator.AppRouterConfig
|
||||
import com.tangem.utils.coroutines.runSuspendCatching
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* Owns the pre-start gates shown before the regular startup navigation — the force-update screen and the
|
||||
* root-detected security warning — as interchangeable full-screen overlays in a single [childSlot].
|
||||
* [await] runs them in order and returns when the app may proceed, so the routing component stays agnostic.
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
internal class AppStartupGateComponent @AssistedInject constructor(
|
||||
@Assisted context: AppComponentContext,
|
||||
private val getAppUpdateStateUseCase: GetAppUpdateStateUseCase,
|
||||
private val forceUpdateFeatureToggles: ForceUpdateFeatureToggles,
|
||||
private val forceUpdateContinuation: ForceUpdateContinuation,
|
||||
private val forceUpdateComponentFactory: ForceUpdateComponent.Factory,
|
||||
private val rootDetectedWarningComponentFactory: RootDetectedWarningComponent.Factory,
|
||||
private val rootWarningContinuation: RootWarningContinuation,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val securityInfoProvider: DeviceSecurityInfoProvider,
|
||||
private val appRouterConfig: AppRouterConfig,
|
||||
) : AppComponentContext by context, ComposableContentComponent {
|
||||
|
||||
private val slotNavigation = SlotNavigation<GateConfig>()
|
||||
|
||||
private val slot: Value<ChildSlot<GateConfig, ComposableContentComponent>> = childSlot(
|
||||
source = slotNavigation,
|
||||
serializer = null,
|
||||
handleBackButton = false,
|
||||
childFactory = { config, childContext ->
|
||||
when (config) {
|
||||
is GateConfig.ForceUpdate -> forceUpdateComponentFactory.create(
|
||||
context = childByContext(childContext),
|
||||
params = ForceUpdateComponent.Params(mode = config.mode),
|
||||
)
|
||||
GateConfig.RootWarning -> rootDetectedWarningComponentFactory.create(
|
||||
context = childByContext(childContext),
|
||||
params = Unit,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
/** Runs the pre-start gates in order; returns when the app may proceed to normal startup. */
|
||||
suspend fun await() {
|
||||
awaitForceUpdate()
|
||||
awaitRootWarning()
|
||||
}
|
||||
|
||||
private suspend fun awaitForceUpdate() {
|
||||
val mode = runSuspendCatching { resolveForceUpdateMode() }
|
||||
.onFailure { error -> TangemLogger.e("App update check failed, proceeding with normal startup", error) }
|
||||
.getOrNull()
|
||||
?: return
|
||||
|
||||
showGate(GateConfig.ForceUpdate(mode))
|
||||
forceUpdateContinuation.awaitDismiss()
|
||||
slotNavigation.dismiss()
|
||||
}
|
||||
|
||||
private suspend fun awaitRootWarning() {
|
||||
if (settingsRepository.isRootDetectedWarningShown() || !securityInfoProvider.isSecurityExposed()) return
|
||||
|
||||
showGate(GateConfig.RootWarning)
|
||||
rootWarningContinuation.awaitDismiss()
|
||||
settingsRepository.setRootDetectedWarningShown(true)
|
||||
slotNavigation.dismiss()
|
||||
}
|
||||
|
||||
private fun showGate(config: GateConfig) {
|
||||
// The gate overlay is drawn on top of the splash, so mark navigation initialized to dismiss the splash.
|
||||
appRouterConfig.initializedState.value = true
|
||||
slotNavigation.activate(config)
|
||||
}
|
||||
|
||||
private suspend fun resolveForceUpdateMode(): ForceUpdateComponent.Mode? {
|
||||
if (!forceUpdateFeatureToggles.isForceUpdateEnabled) return null
|
||||
|
||||
val mode = getAppUpdateStateUseCase.getCached().toForceUpdateModeOrNull()
|
||||
|
||||
// The force-update screen re-checks on open, so a one-shot refresh is only needed when no screen is shown.
|
||||
if (mode == null) {
|
||||
componentScope.launch { getAppUpdateStateUseCase.refresh() }
|
||||
}
|
||||
|
||||
return mode
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val child by slot.subscribeAsState()
|
||||
child.child?.instance?.Content(modifier)
|
||||
}
|
||||
|
||||
private fun AppUpdateState.toForceUpdateModeOrNull(): ForceUpdateComponent.Mode? = when (this) {
|
||||
AppUpdateState.ForceUpdate -> ForceUpdateComponent.Mode.Force
|
||||
AppUpdateState.Brick -> ForceUpdateComponent.Mode.Brick
|
||||
AppUpdateState.OsTooOld -> ForceUpdateComponent.Mode.OsTooOld
|
||||
AppUpdateState.OptionalUpdate -> ForceUpdateComponent.Mode.Optional
|
||||
AppUpdateState.NoUpdate -> null
|
||||
}
|
||||
|
||||
private sealed interface GateConfig {
|
||||
data class ForceUpdate(val mode: ForceUpdateComponent.Mode) : GateConfig
|
||||
data object RootWarning : GateConfig
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(context: AppComponentContext): AppStartupGateComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -21,7 +21,6 @@ import com.tangem.features.feed.entry.components.FeedEntryRoute
|
|||
import com.tangem.features.home.api.HomeComponent
|
||||
import com.tangem.features.hotwallet.*
|
||||
import com.tangem.features.kyc.KycComponent
|
||||
import com.tangem.features.survey.SurveyComponent
|
||||
import com.tangem.features.managetokens.component.ChooseManagedTokensComponent
|
||||
import com.tangem.features.managetokens.component.ManageTokensComponent
|
||||
import com.tangem.features.managetokens.component.ManageTokensMode
|
||||
|
|
@ -37,12 +36,14 @@ import com.tangem.features.send.api.NFTSendComponent
|
|||
import com.tangem.features.send.api.SendComponent
|
||||
import com.tangem.features.send.api.SendEntryPointComponent
|
||||
import com.tangem.features.staking.api.StakingComponent
|
||||
import com.tangem.features.survey.SurveyComponent
|
||||
import com.tangem.features.swap.SwapComponent
|
||||
import com.tangem.features.tangempay.components.TangemPayHotWalletOnboardingComponent
|
||||
import com.tangem.features.tangempay.components.TangemPayDetailsContainerComponent
|
||||
import com.tangem.features.tangempay.components.TangemPayHotWalletOnboardingComponent
|
||||
import com.tangem.features.tangempay.components.TangemPayOnboardingComponent
|
||||
import com.tangem.features.tangempay.components.TangemPayOnboardingComponent.Params.*
|
||||
import com.tangem.features.tokendetails.TokenDetailsComponent
|
||||
import com.tangem.features.virtualaccount.onboarding.component.VirtualAccountOnboardingComponent
|
||||
import com.tangem.features.wallet.WalletEntryComponent
|
||||
import com.tangem.features.walletconnect.components.WalletConnectEntryComponent
|
||||
import com.tangem.features.yield.supply.api.YieldSupplyEntryComponent
|
||||
|
|
@ -71,7 +72,6 @@ internal class ChildFactory @Inject constructor(
|
|||
private val onrampSuccessComponentFactory: OnrampSuccessComponent.Factory,
|
||||
private val buyCryptoComponentFactory: BuyCryptoComponent.Factory,
|
||||
private val sellCryptoComponentFactory: SellCryptoComponent.Factory,
|
||||
private val swapSelectTokensComponentFactory: SwapSelectTokensComponent.Factory,
|
||||
private val onboardingEntryComponentFactory: OnboardingEntryComponent.Factory,
|
||||
private val newWelcomeComponentFactory: NewWelcomeComponent.Factory,
|
||||
private val storiesComponentFactory: StoriesComponent.Factory,
|
||||
|
|
@ -114,6 +114,7 @@ internal class ChildFactory @Inject constructor(
|
|||
private val tangemPayDetailsContainerComponentFactory: TangemPayDetailsContainerComponent.Factory,
|
||||
private val tangemPayOnboardingComponentFactory: TangemPayOnboardingComponent.Factory,
|
||||
private val tangemPayWalletOnboardingComponentFactory: TangemPayHotWalletOnboardingComponent.Factory,
|
||||
private val virtualAccountOnboardingComponentFactory: VirtualAccountOnboardingComponent.Factory,
|
||||
private val kycComponentFactory: KycComponent.Factory,
|
||||
private val surveyComponentFactory: SurveyComponent.Factory,
|
||||
private val yieldSupplyEntryComponentFactory: YieldSupplyEntryComponent.Factory,
|
||||
|
|
@ -253,13 +254,6 @@ internal class ChildFactory @Inject constructor(
|
|||
componentFactory = sellCryptoComponentFactory,
|
||||
)
|
||||
}
|
||||
is AppRoute.SwapCrypto -> {
|
||||
createComponentChild(
|
||||
context = context,
|
||||
params = SwapSelectTokensComponent.Params(userWalletId = route.userWalletId),
|
||||
componentFactory = swapSelectTokensComponentFactory,
|
||||
)
|
||||
}
|
||||
is AppRoute.Onboarding -> {
|
||||
createComponentChild(
|
||||
context = context,
|
||||
|
|
@ -380,6 +374,7 @@ internal class ChildFactory @Inject constructor(
|
|||
is AppRoute.QrScanning.Source.Send -> SourceType.SEND
|
||||
is AppRoute.QrScanning.Source.WalletConnect -> SourceType.WALLET_CONNECT
|
||||
is AppRoute.QrScanning.Source.MainScreen -> SourceType.MAIN_SCREEN
|
||||
is AppRoute.QrScanning.Source.AddressBook -> SourceType.ADDRESS_BOOK
|
||||
}
|
||||
createComponentChild(
|
||||
context = context,
|
||||
|
|
@ -495,10 +490,14 @@ internal class ChildFactory @Inject constructor(
|
|||
componentFactory = feedEntryComponentFactory,
|
||||
)
|
||||
}
|
||||
is AppRoute.Usedesk -> { // TODO [REDACTED_TASK_KEY] pass params
|
||||
is AppRoute.Usedesk -> {
|
||||
createComponentChild(
|
||||
context = context,
|
||||
params = UsedeskComponent.Params(),
|
||||
params = UsedeskComponent.Params(
|
||||
userWalletId = route.walletMetaInfo.userWalletId?.stringValue,
|
||||
source = route.source,
|
||||
prefilledMessage = route.prefilledMessage,
|
||||
),
|
||||
componentFactory = usedeskComponentFactory,
|
||||
)
|
||||
}
|
||||
|
|
@ -702,6 +701,23 @@ internal class ChildFactory @Inject constructor(
|
|||
componentFactory = tangemPayWalletOnboardingComponentFactory,
|
||||
)
|
||||
}
|
||||
is AppRoute.VirtualAccountOnboarding -> {
|
||||
createComponentChild(
|
||||
context = context,
|
||||
params = when (val mode = route.mode) {
|
||||
is AppRoute.VirtualAccountOnboarding.Mode.Deeplink ->
|
||||
VirtualAccountOnboardingComponent.Params.Deeplink(
|
||||
userWalletId = mode.userWalletId,
|
||||
deeplink = mode.deeplink,
|
||||
)
|
||||
is AppRoute.VirtualAccountOnboarding.Mode.FromMain ->
|
||||
VirtualAccountOnboardingComponent.Params.FromMain(userWalletId = mode.userWalletId)
|
||||
is AppRoute.VirtualAccountOnboarding.Mode.FromDetailsScreen ->
|
||||
VirtualAccountOnboardingComponent.Params.FromDetailsScreen(userWalletId = mode.userWalletId)
|
||||
},
|
||||
componentFactory = virtualAccountOnboardingComponentFactory,
|
||||
)
|
||||
}
|
||||
is AppRoute.Kyc -> {
|
||||
createComponentChild(
|
||||
context = context,
|
||||
|
|
@ -759,7 +775,7 @@ internal class ChildFactory @Inject constructor(
|
|||
is AppRoute.AddressBook -> {
|
||||
createComponentChild(
|
||||
context = context,
|
||||
params = AddressBookComponent.Params(route.predefinedAddress),
|
||||
params = AddressBookComponent.Params(addressBookOpenMode = route.addressBookOpenMode),
|
||||
componentFactory = addressBookComponentFactory,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import com.tangem.features.send.api.deeplink.SellRedirectDeepLinkHandler
|
|||
import com.tangem.features.staking.api.deeplink.StakingDeepLinkHandler
|
||||
import com.tangem.features.survey.deeplink.SurveyDeepLinkHandler
|
||||
import com.tangem.features.tangempay.deeplink.OnboardVisaDeepLinkHandler
|
||||
import com.tangem.features.virtualaccount.onboarding.deeplink.OnboardVirtualAccountsDeepLinkHandler
|
||||
import com.tangem.features.tangempay.deeplink.TangemPayMainDeepLinkHandler
|
||||
import com.tangem.features.tokendetails.deeplink.TokenDetailsDeepLinkHandler
|
||||
import com.tangem.features.wallet.deeplink.PromoDeeplinkHandler
|
||||
|
|
@ -57,6 +58,7 @@ internal class DeepLinkFactory @Inject constructor(
|
|||
private val swapDeepLink: SwapDeepLinkHandler.Factory,
|
||||
private val promoDeepLink: PromoDeeplinkHandler.Factory,
|
||||
private val onboardVisaDeepLink: OnboardVisaDeepLinkHandler.Factory,
|
||||
private val onboardVirtualAccountsDeepLink: OnboardVirtualAccountsDeepLinkHandler.Factory,
|
||||
private val marketsTokenExchangesDeepLink: MarketsTokenExchangesDeepLinkHandler.Factory,
|
||||
private val tangemPayMainDeepLink: TangemPayMainDeepLinkHandler.Factory,
|
||||
private val newsDetailsDeepLink: NewsDetailsDeepLinkHandler.Factory,
|
||||
|
|
@ -173,6 +175,7 @@ internal class DeepLinkFactory @Inject constructor(
|
|||
DeepLinkRoute.WalletConnect.host -> walletConnectDeepLink.create(deeplinkUri)
|
||||
DeepLinkRoute.Promo.host -> promoDeepLink.create(coroutineScope, queryParams)
|
||||
DeepLinkRoute.OnboardVisa.host -> onboardVisaDeepLink.create(deeplinkUri)
|
||||
DeepLinkRoute.OnboardVirtualAccounts.host -> onboardVirtualAccountsDeepLink.create(deeplinkUri)
|
||||
DeepLinkRoute.News.host -> newsDeepLink.create(queryParams)
|
||||
DeepLinkRoute.Earn.host -> earnDeepLink.create(queryParams)
|
||||
DeepLinkRoute.Yield.host -> yieldDeepLink.create(coroutineScope, queryParams)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue