Updated on 2026-08-14

This commit is contained in:
Tangem 2025-09-04 12:47:51 +03:00
commit ff24b00af4
404 changed files with 12634 additions and 3818 deletions

View file

@ -29,7 +29,7 @@ import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
import com.tangem.domain.card.ScanCardProcessor
import com.tangem.domain.card.repository.CardRepository
import com.tangem.domain.core.wallets.UserWalletsListRepository
import com.tangem.domain.feedback.GetCardInfoUseCase
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
@ -107,7 +107,7 @@ interface ApplicationEntryPoint {
fun getSendFeedbackEmailUseCase(): SendFeedbackEmailUseCase
fun getGetCardInfoUseCase(): GetCardInfoUseCase
fun getWalletMetaInfoUseCase(): GetWalletMetaInfoUseCase
fun getUrlOpener(): UrlOpener

View file

@ -47,7 +47,7 @@ import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
import com.tangem.domain.card.ScanCardProcessor
import com.tangem.domain.card.repository.CardRepository
import com.tangem.domain.common.LogConfig
import com.tangem.domain.feedback.GetCardInfoUseCase
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
@ -77,6 +77,7 @@ import com.tangem.wallet.BuildConfig
import dagger.hilt.EntryPoints
import kotlinx.coroutines.*
import org.rekotlin.Store
import timber.log.Timber
import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository as WalletConnect2Repository
lateinit var store: Store<AppState>
@ -165,8 +166,8 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase
get() = entryPoint.getSendFeedbackEmailUseCase()
private val getCardInfoUseCase: GetCardInfoUseCase
get() = entryPoint.getGetCardInfoUseCase()
private val getWalletMetaInfoUseCase: GetWalletMetaInfoUseCase
get() = entryPoint.getWalletMetaInfoUseCase()
private val urlOpener
get() = entryPoint.getUrlOpener()
@ -289,15 +290,16 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
tangemAppLoggerInitializer.initialize()
Timber.i("APP STARTED")
if (BuildConfig.TESTER_MENU_ENABLED) {
Timber.i(featureTogglesManager.toString())
Timber.i(excludedBlockchainsManager.toString())
}
foregroundActivityObserver = ForegroundActivityObserver()
registerActivityLifecycleCallbacks(foregroundActivityObserver.callbacks)
// We need to initialize the toggles and excludedBlockchainsManager before the MainActivity starts using them.
runBlocking {
awaitAll(
async { featureTogglesManager.init() },
async { excludedBlockchainsManager.init() },
)
initWithConfigDependency(environmentConfig = environmentConfigStorage.initialize())
}
@ -357,7 +359,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
settingsRepository = settingsRepository,
blockchainSDKFactory = blockchainSDKFactory,
sendFeedbackEmailUseCase = sendFeedbackEmailUseCase,
getCardInfoUseCase = getCardInfoUseCase,
getWalletMetaInfoUseCase = getWalletMetaInfoUseCase,
issuersConfigStorage = issuersConfigStorage,
urlOpener = urlOpener,
shareManager = shareManager,

View file

@ -0,0 +1,13 @@
package com.tangem.tap.common.buildconfig
import com.tangem.utils.buildConfig.AppConfigurationProvider
import com.tangem.wallet.BuildConfig
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
internal class AppConfigurationProviderImpl @Inject constructor() : AppConfigurationProvider {
override fun isDebug(): Boolean = BuildConfig.BUILD_TYPE == "debug"
override fun isHuawei(): Boolean = BuildConfig.FLAVOR_NAME == "huawei"
}

View file

@ -0,0 +1,102 @@
package com.tangem.tap.common.pushes
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.net.Uri
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toBitmap
import coil.executeBlocking
import coil.request.ImageRequest
import com.tangem.domain.common.LogConfig
import com.tangem.tap.MainActivity
import com.tangem.tap.common.images.createCoilImageLoader
import com.tangem.tap.features.intentHandler.handlers.OnPushClickedIntentHandler
import com.tangem.wallet.R
class PushNotificationDelegate(private val context: Context) {
@Suppress("LongParameterList")
fun showNotification(
dataMap: Map<String, String>,
title: String?,
body: String?,
channelId: String,
priority: Int,
imageUrl: Uri? = null,
vibratePattern: LongArray?,
) {
val intent = Intent(context, MainActivity::class.java).apply {
dataMap.forEach { (key, value) ->
putExtra(key, value)
}
putExtra(OnPushClickedIntentHandler.OPENED_FROM_GCM_PUSH, true)
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
}
val pendingIntent = PendingIntent.getActivity(
/* context = */ context,
/* requestCode = */ PUSH_NOTIFICATION_REQUEST_CODE,
/* intent = */ intent,
/* flags = */ PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE,
)
val notificationBuilder = NotificationCompat.Builder(context, channelId)
.setSmallIcon(R.drawable.ic_tangem_24)
.setContentTitle(title)
.setContentText(body)
.setPriority(priority)
.setAutoCancel(true)
.setContentIntent(pendingIntent)
.setVibrate(vibratePattern)
.apply {
imageUrl?.let { uri ->
val bitmap = getBitmapImageFromUrl(uri)
setStyle(
NotificationCompat
.BigPictureStyle()
.bigPicture(bitmap),
).setLargeIcon(bitmap)
}
}
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationChannel = NotificationChannel(
channelId,
ContextCompat.getString(context, R.string.tangem_app_name),
NotificationManager.IMPORTANCE_HIGH,
)
notificationManager.createNotificationChannel(notificationChannel)
}
// Generating unique notification id
val uniqueId = (System.currentTimeMillis() % Integer.MAX_VALUE).toInt()
notificationManager.notify(
/* id = */ uniqueId,
/* notification = */ notificationBuilder.build(),
)
}
private fun getBitmapImageFromUrl(url: Uri): Bitmap? {
return createCoilImageLoader(
context,
logEnabled = LogConfig.imageLoader,
).executeBlocking(
ImageRequest.Builder(context)
.data(url)
.build(),
).drawable?.toBitmap()
}
private companion object {
const val PUSH_NOTIFICATION_REQUEST_CODE = 123
}
}

View file

@ -1,30 +1,17 @@
package com.tangem.tap.common.pushes
import android.annotation.SuppressLint
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Intent
import android.graphics.Bitmap
import android.net.Uri
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toBitmap
import coil.executeBlocking
import coil.request.ImageRequest
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import com.tangem.domain.common.LogConfig
import com.tangem.tap.MainActivity
import com.tangem.tap.common.images.createCoilImageLoader
import com.tangem.tap.features.intentHandler.handlers.OnPushClickedIntentHandler
import com.tangem.wallet.R
import timber.log.Timber
@SuppressLint("MissingFirebaseInstanceTokenRefresh")
internal class TangemPushNotificationService : FirebaseMessagingService() {
private val pushNotificationDelegate: PushNotificationDelegate by lazy {
PushNotificationDelegate(applicationContext)
}
override fun onNewToken(token: String) {
super.onNewToken(token)
Timber.d("New FCM token received: $token")
@ -36,73 +23,18 @@ internal class TangemPushNotificationService : FirebaseMessagingService() {
val notification = message.notification ?: return
val channelId = notification.channelId ?: TANGEM_CHANNEL_ID
val intent = Intent(applicationContext, MainActivity::class.java).apply {
message.data.forEach {
putExtra(it.key, it.value)
}
putExtra(OnPushClickedIntentHandler.OPENED_FROM_GCM_PUSH, true)
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
}
val pendingIntent = PendingIntent.getActivity(
/* context = */ this,
/* requestCode = */ PUSH_NOTIFICATION_REQUEST_CODE,
/* intent = */ intent,
/* flags = */ PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE,
pushNotificationDelegate.showNotification(
dataMap = message.data,
title = notification.title,
body = notification.body,
channelId = channelId,
priority = message.priority,
imageUrl = notification.imageUrl,
vibratePattern = notification.vibrateTimings,
)
val notificationBuilder =
NotificationCompat.Builder(applicationContext, channelId)
.setSmallIcon(R.drawable.ic_tangem_24)
.setContentTitle(notification.title)
.setContentText(notification.body)
.setPriority(message.priority)
.setAutoCancel(true)
.setContentIntent(pendingIntent)
.setVibrate(notification.vibrateTimings)
.apply {
notification.imageUrl?.let { uri ->
val bitmap = getBitmapImageFromUrl(uri)
setStyle(
NotificationCompat
.BigPictureStyle()
.bigPicture(bitmap),
).setLargeIcon(bitmap)
}
}
val notificationManager = applicationContext.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationChannel = NotificationChannel(
channelId,
ContextCompat.getString(applicationContext, R.string.tangem_app_name),
NotificationManager.IMPORTANCE_HIGH,
)
notificationManager.createNotificationChannel(notificationChannel)
}
// Generating unique notification id
val uniqueId = (System.currentTimeMillis() % Integer.MAX_VALUE).toInt()
notificationManager.notify(
/* id = */ uniqueId,
/* notification = */ notificationBuilder.build(),
)
}
private fun getBitmapImageFromUrl(url: Uri): Bitmap? {
return createCoilImageLoader(
applicationContext,
logEnabled = LogConfig.imageLoader,
).executeBlocking(
ImageRequest.Builder(applicationContext)
.data(url)
.build(),
).drawable?.toBitmap()
}
private companion object {
const val TANGEM_CHANNEL_ID = "Tangem General" // General channel for notifications
const val PUSH_NOTIFICATION_REQUEST_CODE = 123
}
}

View file

@ -0,0 +1,39 @@
package com.tangem.tap.data
import android.content.Context
import com.tangem.datasource.local.visa.TangemPayStorage
import com.tangem.sdk.storage.AndroidSecureStorageV2
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.withContext
import javax.inject.Inject
import javax.inject.Singleton
private const val DEFAULT_KEY = "tangem_pay_default_key"
@Singleton
internal class DefaultTangemPayStorage @Inject constructor(
@ApplicationContext applicationContext: Context,
private val dispatcherProvider: CoroutineDispatcherProvider,
) : TangemPayStorage {
private val secureStorage by lazy {
AndroidSecureStorageV2(
appContext = applicationContext,
useStrongBox = false,
name = "tangem_pay_storage",
)
}
override suspend fun store(authHeader: String) = withContext(dispatcherProvider.io) {
secureStorage.store(authHeader.encodeToByteArray(throwOnInvalidSequence = true), DEFAULT_KEY)
}
override suspend fun get(): String? = withContext(dispatcherProvider.io) {
secureStorage.get(DEFAULT_KEY)?.decodeToString(throwOnInvalidSequence = true)
}
override suspend fun clear() = withContext(dispatcherProvider.io) {
secureStorage.delete(DEFAULT_KEY)
}
}

View file

@ -1,18 +0,0 @@
package com.tangem.tap.data
import com.google.firebase.messaging.FirebaseMessaging
import com.tangem.utils.notifications.PushNotificationsTokenProvider
import kotlinx.coroutines.tasks.await
import timber.log.Timber
import javax.inject.Inject
internal class FirebasePushNotificationsTokenProvider @Inject constructor() : PushNotificationsTokenProvider {
override suspend fun getToken(): String {
return try {
FirebaseMessaging.getInstance().token.await()
} catch (ex: Exception) {
Timber.e(ex)
""
}
}
}

View file

@ -0,0 +1,18 @@
package com.tangem.tap.di
import com.tangem.tap.common.buildconfig.AppConfigurationProviderImpl
import com.tangem.utils.buildConfig.AppConfigurationProvider
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 AppConfigurationModule {
@Binds
@Singleton
fun bindAppConfigurationProvider(impl: AppConfigurationProviderImpl): AppConfigurationProvider
}

View file

@ -1,18 +0,0 @@
package com.tangem.tap.di.data
import com.tangem.tap.data.FirebasePushNotificationsTokenProvider
import com.tangem.utils.notifications.PushNotificationsTokenProvider
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 PushNotificationsModule {
@Binds
@Singleton
fun bindPushNotificationsTokenProvider(impl: FirebasePushNotificationsTokenProvider): PushNotificationsTokenProvider
}

View file

@ -1,7 +1,9 @@
package com.tangem.tap.di.data
import com.tangem.datasource.local.visa.TangemPayStorage
import com.tangem.datasource.local.visa.VisaAuthTokenStorage
import com.tangem.datasource.local.visa.VisaOTPStorage
import com.tangem.tap.data.DefaultTangemPayStorage
import com.tangem.tap.data.DefaultVisaAuthTokenStorage
import com.tangem.tap.data.DefaultVisaOTPStorage
import dagger.Binds
@ -21,4 +23,8 @@ internal interface VisaStorageModule {
@Binds
@Singleton
fun bindVisaOTPStorage(impl: DefaultVisaOTPStorage): VisaOTPStorage
@Binds
@Singleton
fun bindTangemPayStorage(impl: DefaultTangemPayStorage): TangemPayStorage
}

View file

@ -1,7 +1,7 @@
package com.tangem.tap.di.domain
import android.content.Context
import com.tangem.domain.feedback.GetCardInfoUseCase
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.feedback.repository.FeedbackRepository
@ -18,8 +18,8 @@ internal object FeedbackDomainModule {
@Provides
@Singleton
fun provideGetCardInfoUseCase(feedbackRepository: FeedbackRepository): GetCardInfoUseCase {
return GetCardInfoUseCase(feedbackRepository = feedbackRepository)
fun provideGetCardInfoUseCase(feedbackRepository: FeedbackRepository): GetWalletMetaInfoUseCase {
return GetWalletMetaInfoUseCase(feedbackRepository = feedbackRepository)
}
@Provides

View file

@ -6,7 +6,6 @@ import com.tangem.domain.nft.repository.NFTRepository
import com.tangem.domain.quotes.single.SingleQuoteStatusFetcher
import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
import com.tangem.domain.tokens.TokensFeatureToggles
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -33,29 +32,21 @@ internal object NFTDomainModule {
@Provides
@Singleton
fun providesFetchNFTCollectionsUseCase(
currenciesRepository: CurrenciesRepository,
nftRepository: NFTRepository,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles,
): FetchNFTCollectionsUseCase = FetchNFTCollectionsUseCase(
currenciesRepository = currenciesRepository,
nftRepository = nftRepository,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles,
)
@Provides
@Singleton
fun providesRefreshAllNFTUseCase(
currenciesRepository: CurrenciesRepository,
nftRepository: NFTRepository,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles,
): RefreshAllNFTUseCase = RefreshAllNFTUseCase(
currenciesRepository = currenciesRepository,
nftRepository = nftRepository,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles,
)
@Provides
@ -127,16 +118,12 @@ internal object NFTDomainModule {
fun provideDisableWalletNFTUseCase(
walletsRepository: WalletsRepository,
nftRepository: NFTRepository,
currenciesRepository: CurrenciesRepository,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles,
): DisableWalletNFTUseCase {
return DisableWalletNFTUseCase(
walletsRepository = walletsRepository,
nftRepository = nftRepository,
currenciesRepository = currenciesRepository,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles,
)
}

View file

@ -120,6 +120,12 @@ internal object OnrampDomainModule {
return OnrampSaveTransactionUseCase(onrampTransactionRepository, onrampErrorResolver)
}
@Provides
@Singleton
fun provideOnrampSepaAvailableUseCase(onrampRepository: OnrampRepository): OnrampSepaAvailableUseCase {
return OnrampSepaAvailableUseCase(onrampRepository)
}
@Provides
@Singleton
fun provideOnrampUpdateTransactionStatusUseCase(

View file

@ -47,7 +47,6 @@ internal object TokensDomainModule {
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles,
stakingIdFactory: StakingIdFactory,
): AddCryptoCurrenciesUseCase {
return AddCryptoCurrenciesUseCase(
@ -56,7 +55,6 @@ internal object TokensDomainModule {
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
singleYieldBalanceFetcher = singleYieldBalanceFetcher,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles,
stakingIdFactory = stakingIdFactory,
)
}
@ -111,13 +109,11 @@ internal object TokensDomainModule {
currenciesRepository: CurrenciesRepository,
walletManagersFacade: WalletManagersFacade,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles,
): RemoveCurrencyUseCase {
return RemoveCurrencyUseCase(
currenciesRepository = currenciesRepository,
walletManagersFacade = walletManagersFacade,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles,
)
}
@ -156,7 +152,6 @@ internal object TokensDomainModule {
dispatchers: CoroutineDispatcherProvider,
baseCurrencyStatusOperations: BaseCurrencyStatusOperations,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles,
): GetCurrencyWarningsUseCase {
return GetCurrencyWarningsUseCase(
walletManagersFacade = walletManagersFacade,
@ -165,7 +160,6 @@ internal object TokensDomainModule {
currencyChecksRepository = currencyChecksRepository,
currencyStatusOperations = baseCurrencyStatusOperations,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles,
)
}
@ -177,7 +171,6 @@ internal object TokensDomainModule {
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles,
stakingIdFactory: StakingIdFactory,
): FetchCurrencyStatusUseCase {
return FetchCurrencyStatusUseCase(
@ -186,7 +179,6 @@ internal object TokensDomainModule {
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
singleYieldBalanceFetcher = singleYieldBalanceFetcher,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles,
stakingIdFactory = stakingIdFactory,
)
}
@ -214,9 +206,8 @@ internal object TokensDomainModule {
fun provideGetCryptoCurrencyUseCase(
currenciesRepository: CurrenciesRepository,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles,
): GetCryptoCurrencyUseCase {
return GetCryptoCurrencyUseCase(currenciesRepository, multiWalletCryptoCurrenciesSupplier, tokensFeatureToggles)
return GetCryptoCurrencyUseCase(currenciesRepository, multiWalletCryptoCurrenciesSupplier)
}
@Provides
@ -238,13 +229,11 @@ internal object TokensDomainModule {
fun provideApplyTokenListSortingUseCase(
currenciesRepository: CurrenciesRepository,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles,
dispatchers: CoroutineDispatcherProvider,
): ApplyTokenListSortingUseCase {
return ApplyTokenListSortingUseCase(
currenciesRepository = currenciesRepository,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles,
dispatchers = dispatchers,
)
}
@ -304,14 +293,10 @@ internal object TokensDomainModule {
@Provides
@Singleton
fun provideIsCryptoCurrencyCoinCouldHideUseCase(
currenciesRepository: CurrenciesRepository,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles,
): IsCryptoCurrencyCoinCouldHideUseCase {
return IsCryptoCurrencyCoinCouldHideUseCase(
currenciesRepository = currenciesRepository,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles,
)
}
@ -330,13 +315,11 @@ internal object TokensDomainModule {
fun provideGetBalanceNotEnoughForFeeWarningUseCase(
currenciesRepository: CurrenciesRepository,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles,
dispatchers: CoroutineDispatcherProvider,
): GetBalanceNotEnoughForFeeWarningUseCase {
return GetBalanceNotEnoughForFeeWarningUseCase(
currenciesRepository = currenciesRepository,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles,
dispatchers = dispatchers,
)
}
@ -377,16 +360,12 @@ internal object TokensDomainModule {
@Provides
@Singleton
fun provideRefreshMultiCurrencyWalletQuotesUseCase(
currenciesRepository: CurrenciesRepository,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles,
): RefreshMultiCurrencyWalletQuotesUseCase {
return RefreshMultiCurrencyWalletQuotesUseCase(
currenciesRepository = currenciesRepository,
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles,
)
}
@ -402,18 +381,13 @@ internal object TokensDomainModule {
@Provides
@Singleton
fun provideBaseCurrencyStatusOperations(
tokensFeatureToggles: TokensFeatureToggles,
currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository,
singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
singleQuoteStatusSupplier: SingleQuoteStatusSupplier,
singleYieldBalanceSupplier: SingleYieldBalanceSupplier,
multiYieldBalanceSupplier: MultiYieldBalanceSupplier,
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
stakingIdFactory: StakingIdFactory,
): BaseCurrencyStatusOperations {
@ -422,15 +396,10 @@ internal object TokensDomainModule {
quotesRepository = quotesRepository,
singleNetworkStatusSupplier = singleNetworkStatusSupplier,
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
singleNetworkStatusFetcher = singleNetworkStatusFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
singleQuoteStatusSupplier = singleQuoteStatusSupplier,
singleYieldBalanceSupplier = singleYieldBalanceSupplier,
multiYieldBalanceSupplier = multiYieldBalanceSupplier,
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles,
stakingIdFactory = stakingIdFactory,
)
}

View file

@ -6,8 +6,6 @@ import com.tangem.domain.demo.models.DemoConfig
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
import com.tangem.domain.tokens.TokensFeatureToggles
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.transaction.FeeRepository
import com.tangem.domain.transaction.TransactionRepository
import com.tangem.domain.transaction.WalletAddressServiceRepository
@ -63,18 +61,14 @@ internal object TransactionDomainModule {
fun provideAssociateAssetUseCase(
cardSdkConfigRepository: CardSdkConfigRepository,
walletManagersFacade: WalletManagersFacade,
currenciesRepository: CurrenciesRepository,
singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles,
): AssociateAssetUseCase {
return AssociateAssetUseCase(
cardSdkConfigRepository = cardSdkConfigRepository,
walletManagersFacade = walletManagersFacade,
currenciesRepository = currenciesRepository,
singleNetworkStatusSupplier = singleNetworkStatusSupplier,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles,
)
}

View file

@ -25,7 +25,7 @@ import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Suppress("TooManyFunctions")
@Suppress("TooManyFunctions", "LargeClass")
@Module
@InstallIn(SingletonComponent::class)
internal object WalletsDomainModule {
@ -352,4 +352,24 @@ internal object WalletsDomainModule {
walletsRepository = walletsRepository,
)
}
@Provides
@Singleton
fun providesIsUpgradeWalletNotificationEnabledUseCase(
walletsRepository: WalletsRepository,
): IsUpgradeWalletNotificationEnabledUseCase {
return IsUpgradeWalletNotificationEnabledUseCase(
walletsRepository = walletsRepository,
)
}
@Provides
@Singleton
fun providesDismissUpgradeWalletNotificationUseCase(
walletsRepository: WalletsRepository,
): DismissUpgradeWalletNotificationUseCase {
return DismissUpgradeWalletNotificationUseCase(
walletsRepository = walletsRepository,
)
}
}

View file

@ -1,5 +1,7 @@
package com.tangem.tap.di.hot
import com.tangem.data.wallets.hot.DefaultHotWalletAccessor
import com.tangem.domain.wallets.hot.HotWalletAccessor
import com.tangem.hot.sdk.TangemHotSdk
import com.tangem.tap.features.hot.TangemHotSDKProxy
import dagger.Binds
@ -15,4 +17,8 @@ internal interface TangemHotSdkModule {
@Binds
@Singleton
fun bindTangemHotSdk(proxy: TangemHotSDKProxy): TangemHotSdk
@Binds
@Singleton
fun bindHotWalletAccessor(default: DefaultHotWalletAccessor): HotWalletAccessor
}

View file

@ -265,7 +265,7 @@ internal class LegacyScanProcessor @Inject constructor(
onOk = { mainScope.launch { onSuccess() } },
onSupportClick = {
val cardInfo =
store.inject(DaggerGraphState::getCardInfoUseCase).invoke(scanResponse).getOrNull()
store.inject(DaggerGraphState::getWalletMetaInfoUseCase).invoke(scanResponse).getOrNull()
?: error("CardInfo must be not null")
scope.launch {

View file

@ -22,7 +22,7 @@ import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.visa.error.VisaActivationError
import com.tangem.domain.visa.model.*
import com.tangem.domain.visa.repository.VisaActivationRepository
import com.tangem.domain.visa.repository.VisaAuthRepository
import com.tangem.domain.visa.datasource.VisaAuthRemoteDataSource
import com.tangem.operations.GenerateOTPCommand
import com.tangem.operations.attestation.AttestCardKeyCommand
import com.tangem.operations.pins.SetUserCodeCommand
@ -46,7 +46,7 @@ class VisaCardActivationTask @AssistedInject constructor(
@Assisted private val coroutineScope: CoroutineScope,
private val otpStorage: VisaOTPStorage,
private val visaAuthTokenStorage: VisaAuthTokenStorage,
private val visaAuthRepository: VisaAuthRepository,
private val visaAuthRemoteDataSource: VisaAuthRemoteDataSource,
private val visaActivationRepositoryFactory: VisaActivationRepository.Factory,
) : CardSessionRunnable<VisaCardActivationResponse> {
@ -168,7 +168,7 @@ class VisaCardActivationTask @AssistedInject constructor(
signedChallenge: VisaAuthSignedChallenge,
cardWalletAddress: String,
): Either<TangemError, VisaDataToSignByCardWallet> = either {
val tokens = visaAuthRepository.getAccessTokens(signedChallenge)
val tokens = visaAuthRemoteDataSource.getAccessTokens(signedChallenge)
.getOrElse { raise(it.tangemError) }
visaAuthTokenStorage.store(cardId, tokens)

View file

@ -4,9 +4,5 @@ import com.tangem.core.configtoggle.feature.FeatureTogglesManager
import com.tangem.domain.tokens.TokensFeatureToggles
internal class DefaultTokensFeatureToggles(
private val featureTogglesManager: FeatureTogglesManager,
) : TokensFeatureToggles {
override val isWalletBalanceFetcherEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(name = "WALLET_BALANCE_FETCHER_ENABLED")
}
@Suppress("UnusedPrivateMember") private val featureTogglesManager: FeatureTogglesManager,
) : TokensFeatureToggles

View file

@ -62,7 +62,7 @@ internal class DefaultUserWalletsListRepository(
// If we don't save persistent information, we don't need to load user wallets
// and we should clear any existing data
clearPersistentData()
userWallets.value = emptyList()
updateWallets { emptyList() }
return
}
@ -118,7 +118,7 @@ internal class DefaultUserWalletsListRepository(
}
// update the userWallets state and add if it doesn't exist
userWallets.update { currentWallets ->
updateWallets { currentWallets ->
val wallets = currentWallets ?: emptyList()
if (wallets.any { it.walletId == userWallet.walletId }) {
wallets.map { if (it.walletId == userWallet.walletId) userWallet else it }
@ -248,7 +248,7 @@ internal class DefaultUserWalletsListRepository(
removePasswordAttempts(userWallet)
sensitiveInformationRepository.getAll(listOf(encryptionKey))
.doOnSuccess { sensitiveInfo -> userWallets.update { it?.updateWith(sensitiveInfo) } }
.doOnSuccess { sensitiveInfo -> updateWallets { it?.updateWith(sensitiveInfo) } }
.doOnFailure { error ->
raise(UnlockWalletError.UnableToUnlock)
}
@ -306,7 +306,7 @@ internal class DefaultUserWalletsListRepository(
sensitiveInformationRepository.getAll(allKeys)
.doOnSuccess { sensitiveInfo ->
userWallets.update { it?.updateWith(sensitiveInfo) }
updateWallets { it?.updateWith(sensitiveInfo) }
}
.doOnFailure { raise(UnlockWalletError.UnableToUnlock) }
}
@ -318,7 +318,7 @@ internal class DefaultUserWalletsListRepository(
raise(LockWalletsError.NothingToLock)
}
userWallets.update {
updateWallets {
it?.map {
if (it.walletId !in unsecuredWalletIds) {
it.lock()
@ -389,6 +389,15 @@ internal class DefaultUserWalletsListRepository(
return tangemSdkManagerProvider.invoke().canUseBiometry && useBiometricAuthentication
}
private fun updateWallets(block: (List<UserWallet>?) -> List<UserWallet>?) {
userWallets.update(block)
selectedUserWallet.update { currentSelected ->
if (currentSelected == null) return@update null
userWallets.value?.find { it.walletId == currentSelected.walletId }
}
}
/**
* Find the nearest available wallet that can be selected
*

View file

@ -15,7 +15,7 @@ import com.tangem.domain.visa.error.VisaApiError
import com.tangem.domain.visa.error.VisaCardScanError
import com.tangem.domain.visa.model.*
import com.tangem.domain.visa.repository.VisaActivationRepository
import com.tangem.domain.visa.repository.VisaAuthRepository
import com.tangem.domain.visa.datasource.VisaAuthRemoteDataSource
import com.tangem.operations.attestation.AttestCardKeyCommand
import com.tangem.operations.attestation.AttestCardKeyResponse
import com.tangem.operations.attestation.AttestWalletKeyResponse
@ -26,7 +26,7 @@ import javax.inject.Inject
import kotlin.coroutines.resume
internal class VisaCardScanHandler @Inject constructor(
private val visaAuthRepository: VisaAuthRepository,
private val visaAuthRemoteDataSource: VisaAuthRemoteDataSource,
private val visaActivationRepositoryFactory: VisaActivationRepository.Factory,
private val visaAuthTokenStorage: VisaAuthTokenStorage,
) {
@ -83,7 +83,7 @@ internal class VisaCardScanHandler @Inject constructor(
Timber.i("Requesting challenge for wallet authorization")
val challengeResponse = visaAuthRepository.getCardWalletAuthChallenge(
val challengeResponse = visaAuthRemoteDataSource.getCardWalletAuthChallenge(
cardId = card.cardId,
// This is the wallet public key, not the address and it's alright, as the API expects it in this format
cardWalletAddress = wallet.publicKey.toHexString(),
@ -122,7 +122,7 @@ internal class VisaCardScanHandler @Inject constructor(
cardWalletAddress: String,
signedChallenge: VisaAuthSignedChallenge,
): CompletionResult<VisaCardActivationStatus> {
val authorizationTokensResponse = visaAuthRepository.getAccessTokens(signedChallenge = signedChallenge)
val authorizationTokensResponse = visaAuthRemoteDataSource.getAccessTokens(signedChallenge = signedChallenge)
.getOrElse {
Timber.i("Failed to get Access token for Wallet public key authorization.")
return if (
@ -149,7 +149,7 @@ internal class VisaCardScanHandler @Inject constructor(
Timber.i("Requesting authorization challenge to sign")
val challengeResponse = visaAuthRepository.getCardAuthChallenge(
val challengeResponse = visaAuthRemoteDataSource.getCardAuthChallenge(
cardId = card.cardId,
cardPublicKey = card.cardPublicKey.toHexString(),
).getOrElse {
@ -174,7 +174,7 @@ internal class VisaCardScanHandler @Inject constructor(
}
}
val authorizationTokensResponse = visaAuthRepository.getAccessTokens(
val authorizationTokensResponse = visaAuthRemoteDataSource.getAccessTokens(
signedChallenge = challengeResponse.toSignedChallenge(
signedChallenge = attestCardKeyResponse.cardSignature.toHexString(),
salt = attestCardKeyResponse.salt.toHexString(),

View file

@ -47,6 +47,14 @@ class WalletConnectSdkHelper {
store.inject(DaggerGraphState::generalUserWalletsListManager)
}
private val userWalletsListRepository by lazy {
store.inject(DaggerGraphState::userWalletsListRepository)
}
private val hotWalletFeatureToggles by lazy {
store.inject(DaggerGraphState::hotWalletFeatureToggles)
}
@Suppress("MagicNumber")
suspend fun prepareTransactionData(data: EthTransactionData): WcTransactionData {
val transaction = data.transaction
@ -128,13 +136,13 @@ class WalletConnectSdkHelper {
)
}
fun isDemoCard(): Boolean {
val userWallet = userWalletsListManager.selectedUserWalletSync ?: return false
suspend fun isDemoCard(): Boolean {
val userWallet = getSelectedWallet() ?: return false
return userWallet is UserWallet.Cold && userWallet.scanResponse.isDemoCard()
}
private suspend fun getWalletManager(blockchain: Blockchain, derivationPath: String?): WalletManager? {
val userWallet = userWalletsListManager.selectedUserWalletSync ?: return null
val userWallet = getSelectedWallet() ?: return null
val walletManagerFacade = store.inject(DaggerGraphState::walletManagersFacade)
return walletManagerFacade.getOrCreateWalletManager(
userWalletId = userWallet.walletId,
@ -481,6 +489,14 @@ class WalletConnectSdkHelper {
}
}
private suspend fun getSelectedWallet(): UserWallet? {
return if (hotWalletFeatureToggles.isHotWalletEnabled) {
userWalletsListRepository.selectedUserWalletSync()
} else {
userWalletsListManager.selectedUserWalletSync
}
}
private fun getSolanaResultString(signedHash: ByteArray) = "{ signature: \"${signedHash.encodeBase58()}\" }"
/**

View file

@ -3,6 +3,7 @@ package com.tangem.tap.features.details.redux.walletconnect
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchainsdk.utils.toNetworkId
import com.tangem.common.routing.AppRoute
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.inject
@ -183,9 +184,19 @@ class WalletConnectMiddleware {
private suspend fun getWalletManagers(): List<WalletManager> {
val walletManagerFacade = store.inject(DaggerGraphState::walletManagersFacade)
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
val userWallet = userWalletsListManager.selectedUserWalletSync ?: return emptyList()
val userWallet = getSelectedWallet() ?: return emptyList()
return walletManagerFacade.getStoredWalletManagers(userWallet.walletId)
}
private suspend fun getSelectedWallet(): UserWallet? {
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
val userWalletsListRepository = store.inject(DaggerGraphState::userWalletsListRepository)
val hotWalletFeatureToggles = store.inject(DaggerGraphState::hotWalletFeatureToggles)
return if (hotWalletFeatureToggles.isHotWalletEnabled) {
userWalletsListRepository.selectedUserWalletSync()
} else {
userWalletsListManager.selectedUserWalletSync
}
}
}

View file

@ -18,6 +18,7 @@ import com.tangem.domain.wallets.legacy.asLockable
import com.tangem.domain.wallets.usecase.DeleteWalletUseCase
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.extensions.onUserWalletSelected
@ -50,6 +51,7 @@ internal class ResetCardModel @Inject constructor(
private val userWalletsListManager: UserWalletsListManager,
private val analyticsEventHandler: AnalyticsEventHandler,
private val cardSettingsInteractor: CardSettingsInteractor,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
) : Model() {
private val params = paramsContainer.require<ResetCardComponent.Params>()
@ -259,16 +261,20 @@ internal class ResetCardModel @Inject constructor(
private fun finishFullReset() {
cardSettingsInteractor.clear()
val newSelectedWallet = userWalletsListManager.selectedUserWalletSync
val newSelectedWallet = getSelectedWalletSyncUseCase.invoke().getOrNull()
if (newSelectedWallet != null) {
store.dispatchNavigationAction { popTo<AppRoute.Wallet>() }
} else {
val isLocked = runCatching { userWalletsListManager.asLockable()?.isLockedSync }.isSuccess
if (isLocked && userWalletsListManager.hasUserWallets) {
store.dispatchNavigationAction { popTo<AppRoute.Welcome>() }
} else {
if (hotWalletFeatureToggles.isHotWalletEnabled) {
store.dispatchNavigationAction { replaceAll(AppRoute.Home()) }
} else {
val isLocked = runCatching { userWalletsListManager.asLockable()?.isLockedSync }.isSuccess
if (isLocked && userWalletsListManager.hasUserWallets) {
store.dispatchNavigationAction { popTo<AppRoute.Welcome>() }
} else {
store.dispatchNavigationAction { replaceAll(AppRoute.Home()) }
}
}
}
}

View file

@ -24,6 +24,7 @@ import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.balancehiding.ListenToFlipsUseCase
import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase
import com.tangem.domain.common.LogConfig
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.notifications.GetApplicationIdUseCase
import com.tangem.domain.notifications.SendPushTokenUseCase
import com.tangem.domain.notifications.models.ApplicationId
@ -37,9 +38,9 @@ import com.tangem.domain.settings.DeleteDeprecatedLogsUseCase
import com.tangem.domain.settings.IncrementAppLaunchCounterUseCase
import com.tangem.domain.settings.usercountry.FetchUserCountryUseCase
import com.tangem.domain.staking.FetchStakingTokensUseCase
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.usecase.AssociateWalletsWithApplicationIdUseCase
import com.tangem.domain.wallets.usecase.GetSavedWalletsCountUseCase
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
import com.tangem.domain.wallets.usecase.UpdateRemoteWalletsInfoUseCase
import com.tangem.feature.swap.analytics.StoriesEvents
import com.tangem.tap.common.extensions.setContext
@ -69,7 +70,6 @@ internal class MainViewModel @Inject constructor(
deleteDeprecatedLogsUseCase: DeleteDeprecatedLogsUseCase,
private val incrementAppLaunchCounterUseCase: IncrementAppLaunchCounterUseCase,
private val blockchainSDKFactory: BlockchainSDKFactory,
private val userWalletsListManager: UserWalletsListManager,
private val dispatchers: CoroutineDispatcherProvider,
private val fetchStakingTokensUseCase: FetchStakingTokensUseCase,
private val fetchUserCountryUseCase: FetchUserCountryUseCase,
@ -90,6 +90,7 @@ internal class MainViewModel @Inject constructor(
private val multiQuoteUpdater: MultiQuoteUpdater,
private val appStateHolder: AppStateHolder,
private val environmentConfigStorage: EnvironmentConfigStorage,
private val getSelectedWalletUseCase: GetSelectedWalletUseCase,
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
) : ViewModel() {
@ -185,13 +186,16 @@ internal class MainViewModel @Inject constructor(
}
private fun prepareSelectedWalletFeedback() {
userWalletsListManager.selectedUserWallet
.distinctUntilChanged()
.onEach { userWallet ->
Analytics.setContext(userWallet)
getSelectedWalletUseCase.invoke()
.mapLeft { emptyFlow<UserWallet>() }
.onRight {
it.distinctUntilChanged()
.onEach { userWallet ->
Analytics.setContext(userWallet)
}
.flowOn(dispatchers.io)
.launchIn(viewModelScope)
}
.flowOn(dispatchers.io)
.launchIn(viewModelScope)
}
private suspend fun fetchStakingTokens() {
@ -214,7 +218,7 @@ internal class MainViewModel @Inject constructor(
apiKey = environmentConfig.moonPayApiKey,
secretKey = environmentConfig.moonPayApiSecretKey,
logEnabled = LogConfig.network.moonPayService,
userWalletProvider = { userWalletsListManager.selectedUserWalletSync },
userWalletProvider = { getSelectedWalletUseCase.sync().getOrNull() },
)
}

View file

@ -30,7 +30,7 @@ object WalletActivationErrorDialog {
val scanResponse = store.state.globalState.scanResponse
?: error("ScanResponse must be not null")
val cardInfo = store.inject(DaggerGraphState::getCardInfoUseCase).invoke(scanResponse).getOrNull()
val cardInfo = store.inject(DaggerGraphState::getWalletMetaInfoUseCase).invoke(scanResponse).getOrNull()
?: error("CardInfo must be not null")
scope.launch {

View file

@ -6,6 +6,7 @@ import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.blockchainsdk.utils.toBlockchain
import com.tangem.data.common.currency.CryptoCurrencyFactory
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.domain.model.Currency
import com.tangem.tap.proxy.redux.DaggerGraphState
@ -24,9 +25,7 @@ internal class CryptoCurrencyConverter(
cryptoCurrencyFactory.createCoin(
blockchain = value.blockchain,
extraDerivationPath = value.derivationPath,
userWallet = requireNotNull(
store.inject(DaggerGraphState::generalUserWalletsListManager).selectedUserWalletSync,
),
userWallet = getSelectedWallet(),
),
)
is Currency.Token -> requireNotNull(
@ -34,9 +33,7 @@ internal class CryptoCurrencyConverter(
sdkToken = value.token,
blockchain = value.blockchain,
extraDerivationPath = value.derivationPath,
userWallet = requireNotNull(
store.inject(DaggerGraphState::generalUserWalletsListManager).selectedUserWalletSync,
),
userWallet = getSelectedWallet(),
),
)
}
@ -63,4 +60,15 @@ internal class CryptoCurrencyConverter(
)
}
}
fun getSelectedWallet(): UserWallet {
val userWalletListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
val userWalletsListRepository = store.inject(DaggerGraphState::userWalletsListRepository)
val hotWalletFeatureToggles = store.inject(DaggerGraphState::hotWalletFeatureToggles)
return if (hotWalletFeatureToggles.isHotWalletEnabled) {
requireNotNull(userWalletsListRepository.selectedUserWallet.value)
} else {
requireNotNull(userWalletListManager.selectedUserWalletSync)
}
}
}

View file

@ -5,7 +5,7 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
import com.tangem.lib.crypto.UserWalletManager
import com.tangem.lib.crypto.models.ProxyAmount
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -15,13 +15,13 @@ import java.math.BigDecimal
class UserWalletManagerImpl(
private val walletManagersFacade: WalletManagersFacade,
private val userWalletsListManager: UserWalletsListManager,
private val getSelectedWalletUseCase: GetSelectedWalletUseCase,
private val dispatchers: CoroutineDispatcherProvider,
) : UserWalletManager {
override fun getWalletId(): String {
val selectedUserWallet = requireNotNull(
userWalletsListManager.selectedUserWalletSync,
getSelectedWalletUseCase.sync().getOrNull(),
) { "selectedUserWallet shouldn't be null" }
return selectedUserWallet.walletId.stringValue
}
@ -62,7 +62,7 @@ class UserWalletManagerImpl(
@Throws(IllegalArgumentException::class)
private suspend fun getActualWalletManager(blockchain: Blockchain, derivationPath: String?): WalletManager {
val selectedUserWallet = requireNotNull(
userWalletsListManager.selectedUserWalletSync,
getSelectedWalletUseCase.sync().getOrNull(),
) { "userWallet or userWalletsListManager is null" }
val walletManager = withContext(dispatchers.io) {
walletManagersFacade.getOrCreateWalletManager(

View file

@ -1,7 +1,7 @@
package com.tangem.tap.proxy.di
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
import com.tangem.lib.crypto.UserWalletManager
import com.tangem.tap.proxy.AppStateHolder
import com.tangem.tap.proxy.UserWalletManagerImpl
@ -26,12 +26,12 @@ internal object ProxyModule {
@Singleton
fun provideUserWalletManager(
walletManagersFacade: WalletManagersFacade,
userWalletsListManager: UserWalletsListManager,
getSelectedWalletUseCase: GetSelectedWalletUseCase,
dispatchers: CoroutineDispatcherProvider,
): UserWalletManager {
return UserWalletManagerImpl(
walletManagersFacade = walletManagersFacade,
userWalletsListManager = userWalletsListManager,
getSelectedWalletUseCase = getSelectedWalletUseCase,
dispatchers = dispatchers,
)
}

View file

@ -22,7 +22,7 @@ import com.tangem.domain.card.ScanCardUseCase
import com.tangem.domain.card.repository.CardRepository
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.core.wallets.UserWalletsListRepository
import com.tangem.domain.feedback.GetCardInfoUseCase
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
@ -63,7 +63,7 @@ data class DaggerGraphState(
val settingsRepository: SettingsRepository? = null,
val blockchainSDKFactory: BlockchainSDKFactory? = null,
val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase? = null,
val getCardInfoUseCase: GetCardInfoUseCase? = null,
val getWalletMetaInfoUseCase: GetWalletMetaInfoUseCase? = null,
val issuersConfigStorage: IssuersConfigStorage? = null,
val urlOpener: UrlOpener? = null,
val shareManager: ShareManager? = null,

View file

@ -17,6 +17,7 @@ import com.tangem.features.disclaimer.api.components.DisclaimerComponent
import com.tangem.features.home.api.HomeComponent
import com.tangem.features.hotwallet.AddExistingWalletComponent
import com.tangem.features.hotwallet.CreateMobileWalletComponent
import com.tangem.features.hotwallet.UpgradeWalletComponent
import com.tangem.features.hotwallet.WalletActivationComponent
import com.tangem.features.hotwallet.CreateWalletBackupComponent
import com.tangem.features.hotwallet.UpdateAccessCodeComponent
@ -103,6 +104,7 @@ internal class ChildFactory @Inject constructor(
private val chooseManagedTokensComponentFactory: ChooseManagedTokensComponent.Factory,
private val createWalletSelectionComponentFactory: CreateWalletSelectionComponent.Factory,
private val createMobileWalletComponentFactory: CreateMobileWalletComponent.Factory,
private val upgradeWalletComponentFactory: UpgradeWalletComponent.Factory,
private val addExistingWalletComponentFactory: AddExistingWalletComponent.Factory,
private val walletActivationComponentFactory: WalletActivationComponent.Factory,
private val createWalletBackupComponentFactory: CreateWalletBackupComponent.Factory,
@ -205,6 +207,7 @@ internal class ChildFactory @Inject constructor(
userWalletId = route.userWalletId,
cryptoCurrency = route.currency,
source = route.source,
launchSepa = route.launchSepa,
),
componentFactory = onrampComponentFactory,
)
@ -486,6 +489,15 @@ internal class ChildFactory @Inject constructor(
componentFactory = createMobileWalletComponentFactory,
)
}
is AppRoute.UpgradeWallet -> {
createComponentChild(
context = context,
params = UpgradeWalletComponent.Params(
userWalletId = route.userWalletId,
),
componentFactory = upgradeWalletComponentFactory,
)
}
is AppRoute.AddExistingWallet -> {
createComponentChild(
context = context,