Updated on 2026-08-14
This commit is contained in:
commit
a51233abaf
79 changed files with 1041 additions and 412 deletions
|
|
@ -3,7 +3,6 @@ package com.tangem.tap
|
|||
import androidx.hilt.work.HiltWorkerFactory
|
||||
import com.tangem.TangemSdkLogger
|
||||
import com.tangem.blockchainsdk.BlockchainSDKFactory
|
||||
import com.tangem.data.card.TransactionSignerFactory
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.analytics.filter.OneTimeEventFilter
|
||||
|
|
@ -15,6 +14,7 @@ import com.tangem.core.navigation.settings.SettingsManager
|
|||
import com.tangem.core.navigation.share.ShareManager
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.core.ui.clipboard.ClipboardManager
|
||||
import com.tangem.data.card.TransactionSignerFactory
|
||||
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
|
||||
import com.tangem.datasource.local.config.issuers.IssuersConfigStorage
|
||||
|
|
@ -32,13 +32,13 @@ import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
|
|||
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
|
||||
import com.tangem.domain.onboarding.repository.OnboardingRepository
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
||||
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
||||
import com.tangem.features.onramp.OnrampFeatureToggles
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.tap.common.analytics.handlers.BlockchainExceptionHandler
|
||||
import com.tangem.tap.common.log.TangemAppLoggerInitializer
|
||||
import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles
|
||||
|
|
@ -94,8 +94,6 @@ interface ApplicationEntryPoint {
|
|||
|
||||
fun getSaveTwinsOnboardingShownUseCase(): SaveTwinsOnboardingShownUseCase
|
||||
|
||||
fun getWalletNameGenerateUseCase(): GenerateWalletNameUseCase
|
||||
|
||||
fun getCardRepository(): CardRepository
|
||||
|
||||
fun getTangemSdkLogger(): TangemSdkLogger
|
||||
|
|
@ -118,8 +116,6 @@ interface ApplicationEntryPoint {
|
|||
|
||||
fun getTransactionSignerFactory(): TransactionSignerFactory
|
||||
|
||||
fun getGetUserCountryCodeUseCase(): GetUserCountryUseCase
|
||||
|
||||
fun getOnrampFeatureToggles(): OnrampFeatureToggles
|
||||
|
||||
fun getOnboardingV2FeatureToggles(): OnboardingV2FeatureToggles
|
||||
|
|
@ -142,4 +138,8 @@ interface ApplicationEntryPoint {
|
|||
fun getUiMessageSender(): UiMessageSender
|
||||
|
||||
fun getWorkerFactory(): HiltWorkerFactory
|
||||
|
||||
fun getOnlineCardVerifier(): OnlineCardVerifier
|
||||
|
||||
fun getUserWalletBuilderFactory(): UserWalletBuilder.Factory
|
||||
}
|
||||
|
|
@ -46,13 +46,13 @@ import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
|
|||
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
|
||||
import com.tangem.domain.onboarding.repository.OnboardingRepository
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
||||
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
||||
import com.tangem.features.onramp.OnrampFeatureToggles
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.tap.common.analytics.AnalyticsFactory
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
|
||||
import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeAnalyticsHandler
|
||||
|
|
@ -146,9 +146,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
|
|||
private val saveTwinsOnboardingShownUseCase: SaveTwinsOnboardingShownUseCase
|
||||
get() = entryPoint.getSaveTwinsOnboardingShownUseCase()
|
||||
|
||||
private val generateWalletNameUseCase: GenerateWalletNameUseCase
|
||||
get() = entryPoint.getWalletNameGenerateUseCase()
|
||||
|
||||
private val cardRepository: CardRepository
|
||||
get() = entryPoint.getCardRepository()
|
||||
|
||||
|
|
@ -182,9 +179,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
|
|||
private val transactionSignerFactory: TransactionSignerFactory
|
||||
get() = entryPoint.getTransactionSignerFactory()
|
||||
|
||||
private val getUserCountryUseCase: GetUserCountryUseCase
|
||||
get() = entryPoint.getGetUserCountryCodeUseCase()
|
||||
|
||||
private val onrampFeatureToggles: OnrampFeatureToggles
|
||||
get() = entryPoint.getOnrampFeatureToggles()
|
||||
|
||||
|
|
@ -220,6 +214,11 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
|
|||
.setWorkerFactory(workerFactory)
|
||||
.build()
|
||||
|
||||
private val onlineCardVerifier: OnlineCardVerifier
|
||||
get() = entryPoint.getOnlineCardVerifier()
|
||||
|
||||
private val userWalletBuilderFactory: UserWalletBuilder.Factory
|
||||
get() = entryPoint.getUserWalletBuilderFactory()
|
||||
// endregion
|
||||
|
||||
override fun onCreate() {
|
||||
|
|
@ -301,7 +300,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
|
|||
generalUserWalletsListManager = generalUserWalletsListManager,
|
||||
wasTwinsOnboardingShownUseCase = wasTwinsOnboardingShownUseCase,
|
||||
saveTwinsOnboardingShownUseCase = saveTwinsOnboardingShownUseCase,
|
||||
generateWalletNameUseCase = generateWalletNameUseCase,
|
||||
cardRepository = cardRepository,
|
||||
settingsRepository = settingsRepository,
|
||||
blockchainSDKFactory = blockchainSDKFactory,
|
||||
|
|
@ -312,7 +310,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
|
|||
shareManager = shareManager,
|
||||
appRouter = appRouter,
|
||||
transactionSignerFactory = transactionSignerFactory,
|
||||
getUserCountryUseCase = getUserCountryUseCase,
|
||||
onrampFeatureToggles = onrampFeatureToggles,
|
||||
environmentConfigStorage = environmentConfigStorage,
|
||||
onboardingV2FeatureToggles = onboardingV2FeatureToggles,
|
||||
|
|
@ -322,6 +319,8 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
|
|||
clipboardManager = clipboardManager,
|
||||
settingsManager = settingsManager,
|
||||
uiMessageSender = uiMessageSender,
|
||||
onlineCardVerifier = onlineCardVerifier,
|
||||
userWalletBuilderFactory = userWalletBuilderFactory,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.domain.nft.FetchNFTCollectionsUseCase
|
||||
import com.tangem.domain.nft.GetNFTCollectionsUseCase
|
||||
import com.tangem.domain.nft.repository.NFTRepository
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object NFTDomainModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesGetNFTCollectionsUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
nftRepository: NFTRepository,
|
||||
): GetNFTCollectionsUseCase {
|
||||
return GetNFTCollectionsUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
nftRepository = nftRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesFetchNFTCollectionsUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
nftRepository: NFTRepository,
|
||||
): FetchNFTCollectionsUseCase {
|
||||
return FetchNFTCollectionsUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
nftRepository = nftRepository,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,10 @@ import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
|||
import com.tangem.domain.wallets.repository.WalletNamesMigrationRepository
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.domain.wallets.usecase.*
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.IsWalletNFTEnabledSyncUseCase
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletNameMigrationUseCase
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.features.nft.NFTFeatureToggles
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -18,6 +21,7 @@ import dagger.hilt.InstallIn
|
|||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object WalletsDomainModule {
|
||||
|
|
@ -165,4 +169,22 @@ internal object WalletsDomainModule {
|
|||
fun providesSeedPhraseNotificationUseCase(walletsRepository: WalletsRepository): SeedPhraseNotificationUseCase {
|
||||
return SeedPhraseNotificationUseCase(walletsRepository = walletsRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetCardImageUseCase(onlineCardVerifier: OnlineCardVerifier): GetCardImageUseCase {
|
||||
return GetCardImageUseCase(verifier = onlineCardVerifier)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesIsWalletNFTEnabledSyncUseCase(
|
||||
walletsRepository: WalletsRepository,
|
||||
nftFeatureToggles: NFTFeatureToggles,
|
||||
): IsWalletNFTEnabledSyncUseCase {
|
||||
return IsWalletNFTEnabledSyncUseCase(
|
||||
walletsRepository = walletsRepository,
|
||||
nftFeatureToggles = nftFeatureToggles,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -6,8 +6,8 @@ import com.tangem.domain.common.TwinCardNumber
|
|||
import com.tangem.domain.common.getTwinCardNumber
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.wallets.models.Artwork
|
||||
import com.tangem.operations.attestation.CardVerifyAndGetInfo
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.operations.attestation.api.models.CardVerifyAndGetInfo
|
||||
|
||||
fun CardDTO.signedHashesCount(): Int {
|
||||
return wallets.sumOf { it.totalSignedHashes ?: 0 }
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ import com.tangem.domain.models.scan.ScanResponse
|
|||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.tokens.TokensAction
|
||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
||||
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
|
||||
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
|
||||
import com.tangem.tap.common.analytics.events.IntroductionProcess
|
||||
|
|
@ -44,12 +43,12 @@ import javax.inject.Inject
|
|||
internal class HomeModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val scanCardProcessor: ScanCardProcessor,
|
||||
private val generateWalletNameUseCase: GenerateWalletNameUseCase,
|
||||
private val saveWalletUseCase: SaveWalletUseCase,
|
||||
private val cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val urlOpener: UrlOpener,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val userWalletBuilderFactory: UserWalletBuilder.Factory,
|
||||
) : Model() {
|
||||
|
||||
private val tangemErrorHandler = TangemTangemErrorsHandler(store)
|
||||
|
|
@ -100,10 +99,7 @@ internal class HomeModel @Inject constructor(
|
|||
}
|
||||
|
||||
private suspend fun proceedWithScanResponse(scanResponse: ScanResponse) {
|
||||
val userWallet = UserWalletBuilder(
|
||||
scanResponse = scanResponse,
|
||||
generateWalletNameUseCase = generateWalletNameUseCase,
|
||||
).build()
|
||||
val userWallet = userWalletBuilderFactory.create(scanResponse = scanResponse).build()
|
||||
|
||||
if (userWallet == null) {
|
||||
Timber.e("User wallet not created")
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import com.tangem.core.analytics.models.AnalyticsParam
|
|||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
|
||||
import com.tangem.tap.common.analytics.events.IntroductionProcess
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
|
|
@ -91,8 +90,9 @@ private suspend fun readCard() {
|
|||
}
|
||||
|
||||
private fun proceedWithScanResponse(scanResponse: ScanResponse) = scope.launch {
|
||||
val walletNameGenerateUseCase = store.inject(DaggerGraphState::generateWalletNameUseCase)
|
||||
val userWallet = UserWalletBuilder(scanResponse, walletNameGenerateUseCase).build().guard {
|
||||
val userWalletBuilder = store.inject(DaggerGraphState::userWalletBuilderFactory).create(scanResponse)
|
||||
|
||||
val userWallet = userWalletBuilder.build().guard {
|
||||
Timber.e("User wallet not created")
|
||||
return@launch
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import com.tangem.domain.models.scan.CardDTO
|
|||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.visa.model.VisaCardActivationStatus
|
||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
|
||||
|
|
@ -250,8 +249,9 @@ object OnboardingHelper {
|
|||
hasBackupError: Boolean,
|
||||
alreadyCreatedWallet: UserWallet? = null,
|
||||
) {
|
||||
val walletNameGenerateUseCase = store.inject(DaggerGraphState::generateWalletNameUseCase)
|
||||
val userWallet = alreadyCreatedWallet ?: UserWalletBuilder(scanResponse, walletNameGenerateUseCase)
|
||||
val userWalletBuilderFactory = store.inject(DaggerGraphState::userWalletBuilderFactory)
|
||||
|
||||
val userWallet = alreadyCreatedWallet ?: userWalletBuilderFactory.create(scanResponse)
|
||||
.hasBackupError(hasBackupError)
|
||||
.backupCardsIds(backupCardsIds?.toSet())
|
||||
.build()
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ import com.tangem.common.extensions.isZero
|
|||
import com.tangem.common.services.Result
|
||||
import com.tangem.domain.card.repository.CardRepository
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.operations.attestation.CardVerifyAndGetInfo
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.operations.attestation.api.models.CardVerifyAndGetInfo
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.extensions.safeUpdate
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import com.tangem.domain.models.scan.CardDTO
|
|||
import com.tangem.domain.models.scan.CardDTO.Companion.RING_BATCH_IDS
|
||||
import com.tangem.domain.models.scan.CardDTO.Companion.RING_BATCH_PREFIX
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.models.Artwork
|
||||
|
|
@ -782,9 +781,10 @@ private suspend fun updateWallet(
|
|||
}
|
||||
|
||||
private suspend fun createUserWallet(scanResponse: ScanResponse, backupState: BackupState): UserWallet {
|
||||
val walletNameGenerateUseCase = store.inject(DaggerGraphState::generateWalletNameUseCase)
|
||||
val userWalletBuilder = store.inject(DaggerGraphState::userWalletBuilderFactory).create(scanResponse)
|
||||
|
||||
return requireNotNull(
|
||||
value = UserWalletBuilder(scanResponse, walletNameGenerateUseCase)
|
||||
value = userWalletBuilder
|
||||
.backupCardsIds(backupState.backupCardIds.toSet())
|
||||
.hasBackupError(backupState.hasBackupError)
|
||||
.build(),
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import com.tangem.common.extensions.guard
|
|||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
|
||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
|
|
@ -70,8 +69,10 @@ internal class SaveWalletMiddleware {
|
|||
scope.launch {
|
||||
val backupInfo = state.backupInfo ?: error("Backup info is null")
|
||||
|
||||
val walletNameGenerateUseCase = store.inject(DaggerGraphState::generateWalletNameUseCase)
|
||||
val userWallet = UserWalletBuilder(backupInfo.scanResponse, walletNameGenerateUseCase)
|
||||
val userWalletBuilder = store.inject(DaggerGraphState::userWalletBuilderFactory)
|
||||
.create(scanResponse = backupInfo.scanResponse)
|
||||
|
||||
val userWallet = userWalletBuilder
|
||||
.backupCardsIds(state.backupInfo.backupCardsIds)
|
||||
.hasBackupError(hasBackupError)
|
||||
.build()
|
||||
|
|
|
|||
|
|
@ -11,10 +11,8 @@ import com.tangem.common.routing.utils.popTo
|
|||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager.Lockable.UnlockType
|
||||
import com.tangem.domain.wallets.legacy.unlockIfLockable
|
||||
import com.tangem.tap.backupService
|
||||
|
|
@ -118,9 +116,9 @@ internal class WelcomeMiddleware {
|
|||
)
|
||||
|
||||
scanCardInternal { scanResponse ->
|
||||
val walletNameGenerateUseCase = store.inject(DaggerGraphState::generateWalletNameUseCase)
|
||||
val userWallet = UserWalletBuilder(scanResponse, walletNameGenerateUseCase).build()
|
||||
?: return@scanCardInternal
|
||||
val userWalletBuilder = store.inject(DaggerGraphState::userWalletBuilderFactory).create(scanResponse)
|
||||
|
||||
val userWallet = userWalletBuilder.build() ?: return@scanCardInternal
|
||||
|
||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
userWalletsListManager.save(userWallet, canOverride = true)
|
||||
|
|
|
|||
|
|
@ -26,13 +26,13 @@ import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
|
|||
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
|
||||
import com.tangem.domain.onboarding.repository.OnboardingRepository
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
||||
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
||||
import com.tangem.features.onramp.OnrampFeatureToggles
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles
|
||||
import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
|
||||
|
|
@ -56,7 +56,6 @@ data class DaggerGraphState(
|
|||
val generalUserWalletsListManager: UserWalletsListManager? = null,
|
||||
val wasTwinsOnboardingShownUseCase: WasTwinsOnboardingShownUseCase? = null,
|
||||
val saveTwinsOnboardingShownUseCase: SaveTwinsOnboardingShownUseCase? = null,
|
||||
val generateWalletNameUseCase: GenerateWalletNameUseCase? = null,
|
||||
val cardRepository: CardRepository? = null,
|
||||
val settingsRepository: SettingsRepository? = null,
|
||||
val blockchainSDKFactory: BlockchainSDKFactory? = null,
|
||||
|
|
@ -67,7 +66,6 @@ data class DaggerGraphState(
|
|||
val shareManager: ShareManager? = null,
|
||||
val appRouter: AppRouter? = null,
|
||||
val transactionSignerFactory: TransactionSignerFactory? = null,
|
||||
val getUserCountryUseCase: GetUserCountryUseCase? = null,
|
||||
val onrampFeatureToggles: OnrampFeatureToggles? = null,
|
||||
val environmentConfigStorage: EnvironmentConfigStorage? = null,
|
||||
val onboardingV2FeatureToggles: OnboardingV2FeatureToggles? = null,
|
||||
|
|
@ -77,4 +75,6 @@ data class DaggerGraphState(
|
|||
val clipboardManager: ClipboardManager? = null,
|
||||
val settingsManager: SettingsManager? = null,
|
||||
val uiMessageSender: UiMessageSender? = null,
|
||||
val onlineCardVerifier: OnlineCardVerifier? = null,
|
||||
val userWalletBuilderFactory: UserWalletBuilder.Factory? = null,
|
||||
) : StateType
|
||||
|
|
@ -66,5 +66,9 @@
|
|||
{
|
||||
"name": "NOTE_REFACTORING_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "NEW_ATTESTATION_ENABLED",
|
||||
"version": "undefined"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ internal class DefaultNFTPersistenceStore(
|
|||
private val pricesPersistenceStore: DataStore<Map<NFTAsset.Identifier, NFTAsset.SalePrice>>,
|
||||
) : NFTPersistenceStore {
|
||||
|
||||
override fun getCollections(): Flow<List<NFTCollection>> = collectionsPersistenceStore.data
|
||||
override fun getCollections(): Flow<List<NFTCollection>?> = collectionsPersistenceStore.data
|
||||
|
||||
override suspend fun getCollectionsSync(): List<NFTCollection>? = collectionsPersistenceStore
|
||||
.data
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import com.tangem.blockchain.nft.models.NFTCollection
|
|||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface NFTPersistenceStore {
|
||||
fun getCollections(): Flow<List<NFTCollection>>
|
||||
fun getCollections(): Flow<List<NFTCollection>?>
|
||||
|
||||
suspend fun getCollectionsSync(): List<NFTCollection>?
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.datasource.local.nft
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.core.DataStoreFactory
|
||||
import androidx.datastore.dataStoreFile
|
||||
import com.squareup.moshi.Moshi
|
||||
|
|
@ -11,10 +12,12 @@ import com.tangem.datasource.utils.MoshiDataStoreSerializer
|
|||
import com.tangem.datasource.utils.listTypes
|
||||
import com.tangem.datasource.utils.mapWithCustomKeyTypes
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import java.lang.reflect.ParameterizedType
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
|
@ -25,41 +28,51 @@ class NFTPersistenceStoreFactory @Inject constructor(
|
|||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
fun provide(network: Network): NFTPersistenceStore {
|
||||
val networkStringIdentifier = listOfNotNull(
|
||||
network.id.value,
|
||||
network.derivationPath.value,
|
||||
).joinToString("_") {
|
||||
// remove all non-alphanumeric characters
|
||||
it
|
||||
.toCharArray()
|
||||
.filter(Char::isLetterOrDigit)
|
||||
.joinToString("")
|
||||
.lowercase()
|
||||
}
|
||||
fun provide(userWalletId: UserWalletId, network: Network): NFTPersistenceStore {
|
||||
// simplify network identifier so that is correct for a file name
|
||||
// e.g. eth_m4460000 or theopennetwork_m446070
|
||||
val networkStringId =
|
||||
network.id.formatted() + network.derivationPath.formatted()?.let { "_$it" }.orEmpty()
|
||||
// simplify user wallet id so that is correct for a file name
|
||||
// e.g. 9a1a178f951a7115555568c09ebad8a882f3d96de25429f0017fe570931e208a
|
||||
val userWalletStringId = userWalletId.formatted()
|
||||
return DefaultNFTPersistenceStore(
|
||||
collectionsPersistenceStore = DataStoreFactory.create(
|
||||
serializer = MoshiDataStoreSerializer(
|
||||
moshi = moshi,
|
||||
types = listTypes<NFTCollection>(),
|
||||
defaultValue = emptyList(),
|
||||
),
|
||||
produceFile = {
|
||||
context.dataStoreFile(fileName = "nft_${networkStringIdentifier}_collections")
|
||||
},
|
||||
scope = CoroutineScope(context = dispatchers.io + SupervisorJob()),
|
||||
collectionsPersistenceStore = createPersistenceStore(
|
||||
// result file name example: nft_9a1a178f951a7115555568c09ebad8a882f3d96de25429f0017fe570931e208a_eth_m4460000_collections
|
||||
// result file name example: nft_9a1a178f951a7115555568c09ebad8a882f3d96de25429f0017fe570931e208a_theopennetwork_m446070_collections
|
||||
fileName = "nft_${userWalletStringId}_${networkStringId}_collections",
|
||||
types = listTypes<NFTCollection>(),
|
||||
defaultValue = emptyList(),
|
||||
),
|
||||
pricesPersistenceStore = DataStoreFactory.create(
|
||||
serializer = MoshiDataStoreSerializer(
|
||||
moshi = moshi,
|
||||
types = mapWithCustomKeyTypes<NFTAsset.Identifier, NFTAsset.SalePrice>(),
|
||||
defaultValue = emptyMap(),
|
||||
),
|
||||
produceFile = {
|
||||
context.dataStoreFile(fileName = "nft_${networkStringIdentifier}_prices")
|
||||
},
|
||||
scope = CoroutineScope(context = dispatchers.io + SupervisorJob()),
|
||||
pricesPersistenceStore = createPersistenceStore(
|
||||
// result file name example: nft_9a1a178f951a7115555568c09ebad8a882f3d96de25429f0017fe570931e208a_eth_m4460000_prices
|
||||
// result file name example: nft_9a1a178f951a7115555568c09ebad8a882f3d96de25429f0017fe570931e208a_theopennetwork_m446070_prices
|
||||
fileName = "nft_${userWalletStringId}_${networkStringId}_prices",
|
||||
types = mapWithCustomKeyTypes<NFTAsset.Identifier, NFTAsset.SalePrice>(),
|
||||
defaultValue = emptyMap(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun <T> createPersistenceStore(fileName: String, types: ParameterizedType, defaultValue: T): DataStore<T> =
|
||||
DataStoreFactory.create(
|
||||
serializer = MoshiDataStoreSerializer(
|
||||
moshi = moshi,
|
||||
types = types,
|
||||
defaultValue = defaultValue,
|
||||
),
|
||||
produceFile = { context.dataStoreFile(fileName = fileName) },
|
||||
scope = CoroutineScope(context = dispatchers.io + SupervisorJob()),
|
||||
)
|
||||
|
||||
private fun Network.ID.formatted(): String = value
|
||||
.filter(Char::isLetterOrDigit)
|
||||
.lowercase()
|
||||
|
||||
private fun Network.DerivationPath.formatted(): String? = value
|
||||
?.filter(Char::isLetterOrDigit)
|
||||
?.lowercase()
|
||||
|
||||
private fun UserWalletId.formatted(): String = stringValue
|
||||
.lowercase()
|
||||
}
|
||||
|
|
@ -7,14 +7,11 @@ import com.tangem.domain.tokens.model.Network
|
|||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.blockchain.nft.models.NFTAsset as SdkNFTAsset
|
||||
|
||||
class NFTSdkAssetConverter(
|
||||
private val nftSdkAssetIdentifierConverter: NFTSdkAssetIdentifierConverter,
|
||||
private val nftSdkCollectionIdentifierConverter: NFTSdkCollectionIdentifierConverter,
|
||||
) : Converter<Pair<Network, SdkNFTAsset>, NFTAsset> {
|
||||
object NFTSdkAssetConverter : Converter<Pair<Network, SdkNFTAsset>, NFTAsset> {
|
||||
override fun convert(value: Pair<Network, SdkNFTAsset>): NFTAsset {
|
||||
val (network, asset) = value
|
||||
val assetId = nftSdkAssetIdentifierConverter.convert(asset.identifier)
|
||||
val collectionId = nftSdkCollectionIdentifierConverter.convert(asset.collectionIdentifier)
|
||||
val assetId = NFTSdkAssetIdentifierConverter.convert(asset.identifier)
|
||||
val collectionId = NFTSdkCollectionIdentifierConverter.convert(asset.collectionIdentifier)
|
||||
return NFTAsset.Value(
|
||||
id = assetId,
|
||||
collectionId = collectionId,
|
||||
|
|
|
|||
|
|
@ -6,13 +6,10 @@ import com.tangem.domain.tokens.model.Network
|
|||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.blockchain.nft.models.NFTCollection as SdkNFTCollection
|
||||
|
||||
class NFTSdkCollectionConverter(
|
||||
private val nftSdkCollectionIdentifierConverter: NFTSdkCollectionIdentifierConverter,
|
||||
private val nftSdkAssetConverter: NFTSdkAssetConverter,
|
||||
) : Converter<Pair<Network, SdkNFTCollection>, NFTCollection> {
|
||||
object NFTSdkCollectionConverter : Converter<Pair<Network, SdkNFTCollection>, NFTCollection> {
|
||||
override fun convert(value: Pair<Network, SdkNFTCollection>): NFTCollection {
|
||||
val (network, collection) = value
|
||||
val collectionId = nftSdkCollectionIdentifierConverter.convert(collection.identifier)
|
||||
val collectionId = NFTSdkCollectionIdentifierConverter.convert(collection.identifier)
|
||||
return NFTCollection(
|
||||
id = collectionId,
|
||||
network = network,
|
||||
|
|
@ -22,7 +19,7 @@ class NFTSdkCollectionConverter(
|
|||
count = collection.count,
|
||||
assets = collection.assets
|
||||
.map { asset ->
|
||||
nftSdkAssetConverter.convert(network to asset)
|
||||
NFTSdkAssetConverter.convert(network to asset)
|
||||
}
|
||||
.filter {
|
||||
it.id !is NFTAsset.Identifier.Unknown
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
package com.tangem.data.nft
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.datasource.local.nft.NFTPersistenceStore
|
||||
import com.tangem.datasource.local.nft.NFTPersistenceStoreFactory
|
||||
import com.tangem.datasource.local.nft.NFTRuntimeStore
|
||||
import com.tangem.datasource.local.nft.NFTRuntimeStoreFactory
|
||||
import com.tangem.datasource.local.nft.converter.NFTSdkAssetConverter
|
||||
import com.tangem.datasource.local.nft.converter.NFTSdkAssetIdentifierConverter
|
||||
import com.tangem.datasource.local.nft.converter.NFTSdkCollectionConverter
|
||||
import com.tangem.datasource.local.nft.converter.NFTSdkCollectionIdentifierConverter
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.nft.models.NFTCollection
|
||||
import com.tangem.domain.nft.models.NFTCollections
|
||||
|
|
@ -20,8 +20,10 @@ import com.tangem.domain.wallets.models.UserWalletId
|
|||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveIn
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.joinAll
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
import com.tangem.blockchain.nft.models.NFTCollection as SdkNFTCollection
|
||||
|
||||
|
|
@ -34,82 +36,82 @@ internal class DefaultNFTRepository @Inject constructor(
|
|||
|
||||
private val jobs = mutableMapOf<Network, JobHolder>()
|
||||
|
||||
private val nftRuntimeStores = mutableMapOf<Network, NFTRuntimeStore>()
|
||||
private val nftPersistenceStores = mutableMapOf<Network, NFTPersistenceStore>()
|
||||
|
||||
private val nftSdkAssetConverter = NFTSdkAssetConverter(
|
||||
nftSdkAssetIdentifierConverter = NFTSdkAssetIdentifierConverter,
|
||||
nftSdkCollectionIdentifierConverter = NFTSdkCollectionIdentifierConverter,
|
||||
)
|
||||
private val collectionConverter = NFTSdkCollectionConverter(
|
||||
nftSdkCollectionIdentifierConverter = NFTSdkCollectionIdentifierConverter,
|
||||
nftSdkAssetConverter = nftSdkAssetConverter,
|
||||
)
|
||||
private val nftRuntimeStores = mutableMapOf<String, NFTRuntimeStore>()
|
||||
private val nftPersistenceStores = mutableMapOf<String, NFTPersistenceStore>()
|
||||
|
||||
override fun observeCollections(userWalletId: UserWalletId, networks: List<Network>): Flow<List<NFTCollections>> =
|
||||
flow {
|
||||
emitAll(
|
||||
combine(
|
||||
networks.map { getNFTRuntimeStore(it).getCollections() },
|
||||
) { it.asList() },
|
||||
)
|
||||
}.onStart {
|
||||
refreshCollections(userWalletId, networks)
|
||||
}
|
||||
flow { emitAll(observeCollectionsInternal(userWalletId, networks)) }
|
||||
|
||||
private suspend fun observeCollectionsInternal(
|
||||
userWalletId: UserWalletId,
|
||||
networks: List<Network>,
|
||||
): Flow<List<NFTCollections>> = combine(
|
||||
networks.map { observeCollectionsInternal(userWalletId, it) },
|
||||
) { it.asList() }
|
||||
|
||||
private suspend fun observeCollectionsInternal(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
): Flow<NFTCollections> = if (network.canHandleNFTs()) {
|
||||
getNFTRuntimeStore(userWalletId, network).getCollections()
|
||||
} else {
|
||||
flowOf(NFTCollections.empty(network))
|
||||
}
|
||||
|
||||
override suspend fun refreshCollections(userWalletId: UserWalletId, networks: List<Network>) = coroutineScope {
|
||||
networks.map { network ->
|
||||
launch(dispatchers.io) {
|
||||
Either.catch {
|
||||
expireCollections(network)
|
||||
walletManagersFacade.getNFTCollections(userWalletId, network)
|
||||
}.onLeft {
|
||||
saveFailedStateInRuntime(
|
||||
network = network,
|
||||
error = it,
|
||||
)
|
||||
}.onRight {
|
||||
val mergedCollections = it.mergeWithStoredAssets(network)
|
||||
networks.mapNotNull { network ->
|
||||
if (network.canHandleNFTs()) {
|
||||
launch(dispatchers.io) {
|
||||
Either.catch {
|
||||
expireCollections(userWalletId, network)
|
||||
walletManagersFacade.getNFTCollections(userWalletId, network)
|
||||
}.onLeft {
|
||||
saveFailedStateInRuntime(
|
||||
userWalletId = userWalletId,
|
||||
network = network,
|
||||
error = it,
|
||||
)
|
||||
}.onRight {
|
||||
val mergedCollections = it.mergeWithStoredAssets(userWalletId, network)
|
||||
|
||||
saveCollectionsInRuntime(
|
||||
network = network,
|
||||
collections = mergedCollections,
|
||||
)
|
||||
saveCollectionsInPersistence(
|
||||
network = network,
|
||||
collections = mergedCollections,
|
||||
)
|
||||
}
|
||||
}.saveIn(getJobHolder(network))
|
||||
saveCollectionsInRuntime(
|
||||
userWalletId = userWalletId,
|
||||
network = network,
|
||||
collections = mergedCollections,
|
||||
)
|
||||
saveCollectionsInPersistence(
|
||||
userWalletId = userWalletId,
|
||||
network = network,
|
||||
collections = mergedCollections,
|
||||
)
|
||||
}
|
||||
}.saveIn(getJobHolder(network))
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}.joinAll()
|
||||
}
|
||||
|
||||
private suspend fun expireCollections(network: Network) {
|
||||
val runtimeStore = getNFTRuntimeStore(network)
|
||||
|
||||
private suspend fun expireCollections(userWalletId: UserWalletId, network: Network) {
|
||||
val runtimeStore = getNFTRuntimeStore(userWalletId, network)
|
||||
val expiredCollections = runtimeStore
|
||||
.getCollectionsSync()
|
||||
.let { collections ->
|
||||
collections.copy(
|
||||
content = when (val content = collections.content) {
|
||||
is NFTCollections.Content.Collections -> content.copy(
|
||||
source = StatusSource.CACHE,
|
||||
)
|
||||
is NFTCollections.Content.Error -> content
|
||||
},
|
||||
)
|
||||
}
|
||||
.changeStatusSource(StatusSource.CACHE)
|
||||
runtimeStore.saveCollections(expiredCollections)
|
||||
}
|
||||
|
||||
private suspend fun saveCollectionsInRuntime(network: Network, collections: List<SdkNFTCollection>) {
|
||||
getNFTRuntimeStore(network).saveCollections(
|
||||
private suspend fun saveCollectionsInRuntime(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
collections: List<SdkNFTCollection>,
|
||||
) {
|
||||
getNFTRuntimeStore(userWalletId, network).saveCollections(
|
||||
NFTCollections(
|
||||
network = network,
|
||||
content = NFTCollections.Content.Collections(
|
||||
collections = collections
|
||||
.map { collection ->
|
||||
collectionConverter.convert(network to collection)
|
||||
NFTSdkCollectionConverter.convert(network to collection)
|
||||
}
|
||||
.filter {
|
||||
it.id !is NFTCollection.Identifier.Unknown
|
||||
|
|
@ -120,19 +122,30 @@ internal class DefaultNFTRepository @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private suspend fun saveFailedStateInRuntime(network: Network, error: Throwable) {
|
||||
getNFTRuntimeStore(network).saveCollections(
|
||||
NFTCollections(
|
||||
network = network,
|
||||
content = NFTCollections.Content.Error(
|
||||
error = error,
|
||||
),
|
||||
),
|
||||
)
|
||||
private suspend fun saveFailedStateInRuntime(userWalletId: UserWalletId, network: Network, error: Throwable) {
|
||||
getNFTRuntimeStore(userWalletId, network).let { store ->
|
||||
val storedCollections = store.getCollectionsSync()
|
||||
val content = storedCollections.content
|
||||
val updatedCollections = if (content is NFTCollections.Content.Collections && content.collections != null) {
|
||||
// if there is any cached collections in store, then mark them as not actual and emit anyway
|
||||
storedCollections.changeStatusSource(StatusSource.ONLY_CACHE)
|
||||
} else {
|
||||
// otherwise, just emit an error
|
||||
NFTCollections(
|
||||
network = network,
|
||||
content = NFTCollections.Content.Error(error),
|
||||
)
|
||||
}
|
||||
store.saveCollections(updatedCollections)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun saveCollectionsInPersistence(network: Network, collections: List<SdkNFTCollection>) {
|
||||
getNFTPersistenceStore(network).saveCollections(collections)
|
||||
private suspend fun saveCollectionsInPersistence(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
collections: List<SdkNFTCollection>,
|
||||
) {
|
||||
getNFTPersistenceStore(userWalletId, network).saveCollections(collections)
|
||||
}
|
||||
|
||||
private fun getJobHolder(network: Network): JobHolder = jobs[network] ?: run {
|
||||
|
|
@ -141,65 +154,85 @@ internal class DefaultNFTRepository @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getNFTPersistenceStore(network: Network): NFTPersistenceStore = nftPersistenceStores[network] ?: run {
|
||||
nftPersistenceStoreFactory.provide(network).also {
|
||||
nftPersistenceStores[network] = it
|
||||
private fun getNFTPersistenceStore(userWalletId: UserWalletId, network: Network): NFTPersistenceStore {
|
||||
val storeId = (userWalletId to network).formatted()
|
||||
return nftPersistenceStores[storeId] ?: run {
|
||||
nftPersistenceStoreFactory.provide(userWalletId, network).also {
|
||||
nftPersistenceStores[storeId] = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getNFTRuntimeStore(network: Network): NFTRuntimeStore = nftRuntimeStores[network] ?: run {
|
||||
nftRuntimeStoreFactory.provide(network).also {
|
||||
nftRuntimeStores[network] = it
|
||||
val storedCollections = getStoredCollections(network)
|
||||
val storedPrices = getStoredPrices(network)
|
||||
it.initialize(
|
||||
collections = storedCollections,
|
||||
prices = storedPrices,
|
||||
)
|
||||
private suspend fun getNFTRuntimeStore(userWalletId: UserWalletId, network: Network): NFTRuntimeStore {
|
||||
val storeId = (userWalletId to network).formatted()
|
||||
return nftRuntimeStores[storeId] ?: run {
|
||||
nftRuntimeStoreFactory.provide(network).also {
|
||||
nftRuntimeStores[storeId] = it
|
||||
val storedCollections = getStoredCollections(userWalletId, network)
|
||||
val storedPrices = getStoredPrices(userWalletId, network)
|
||||
it.initialize(
|
||||
collections = storedCollections,
|
||||
prices = storedPrices,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getStoredCollections(network: Network) = getNFTPersistenceStore(network)
|
||||
.getCollectionsSync()
|
||||
.let {
|
||||
NFTCollections(
|
||||
network = network,
|
||||
content = NFTCollections.Content.Collections(
|
||||
collections = it
|
||||
?.map { collection ->
|
||||
collectionConverter.convert(network to collection)
|
||||
}
|
||||
?.filter {
|
||||
it.id !is NFTCollection.Identifier.Unknown
|
||||
},
|
||||
source = StatusSource.CACHE,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun getStoredPrices(network: Network) = getNFTPersistenceStore(network)
|
||||
.getSalePricesSync()
|
||||
.orEmpty()
|
||||
.let { prices ->
|
||||
prices
|
||||
.mapKeys {
|
||||
val (assetId, _) = it
|
||||
NFTSdkAssetIdentifierConverter.convert(assetId)
|
||||
}
|
||||
.mapValues {
|
||||
val (assetId, price) = it
|
||||
NFTSalePrice.Value(
|
||||
assetId = assetId,
|
||||
value = price.value,
|
||||
symbol = price.symbol,
|
||||
private suspend fun getStoredCollections(userWalletId: UserWalletId, network: Network) =
|
||||
getNFTPersistenceStore(userWalletId, network)
|
||||
.getCollectionsSync()
|
||||
.let {
|
||||
NFTCollections(
|
||||
network = network,
|
||||
content = NFTCollections.Content.Collections(
|
||||
collections = it
|
||||
?.map { collection ->
|
||||
NFTSdkCollectionConverter.convert(network to collection)
|
||||
}
|
||||
?.filter {
|
||||
it.id !is NFTCollection.Identifier.Unknown
|
||||
},
|
||||
source = StatusSource.CACHE,
|
||||
)
|
||||
}
|
||||
}
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun List<SdkNFTCollection>.mergeWithStoredAssets(network: Network): List<SdkNFTCollection> {
|
||||
private suspend fun getStoredPrices(userWalletId: UserWalletId, network: Network) =
|
||||
getNFTPersistenceStore(userWalletId, network)
|
||||
.getSalePricesSync()
|
||||
.orEmpty()
|
||||
.let { prices ->
|
||||
prices
|
||||
.mapKeys {
|
||||
val (assetId, _) = it
|
||||
NFTSdkAssetIdentifierConverter.convert(assetId)
|
||||
}
|
||||
.mapValues {
|
||||
val (assetId, price) = it
|
||||
NFTSalePrice.Value(
|
||||
assetId = assetId,
|
||||
value = price.value,
|
||||
symbol = price.symbol,
|
||||
source = StatusSource.CACHE,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun NFTCollections.changeStatusSource(source: StatusSource) = copy(
|
||||
content = when (val content = content) {
|
||||
is NFTCollections.Content.Collections -> content.copy(
|
||||
source = source,
|
||||
)
|
||||
is NFTCollections.Content.Error -> content
|
||||
},
|
||||
)
|
||||
|
||||
private suspend fun List<SdkNFTCollection>.mergeWithStoredAssets(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
): List<SdkNFTCollection> {
|
||||
val storedCollections =
|
||||
getNFTPersistenceStore(network)
|
||||
getNFTPersistenceStore(userWalletId, network)
|
||||
.getCollectionsSync()
|
||||
.orEmpty()
|
||||
.associateBy(SdkNFTCollection::identifier)
|
||||
|
|
@ -224,4 +257,11 @@ internal class DefaultNFTRepository @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Pair<UserWalletId, Network>.formatted(): String {
|
||||
val (walletId, network) = this
|
||||
return walletId.stringValue + "_" + network.id.value + "_" + network.derivationPath.value
|
||||
}
|
||||
|
||||
private fun Network.canHandleNFTs(): Boolean = Blockchain.fromNetworkId(backendId)?.canHandleNFTs() == true
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@ dependencies {
|
|||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.appCurrency.models)
|
||||
implementation(projects.domain.tokens.models)
|
||||
|
||||
/** Project - Utils */
|
||||
implementation(projects.core.utils)
|
||||
|
|
|
|||
|
|
@ -43,11 +43,9 @@ internal class DefaultVisaRepository @Inject constructor(
|
|||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val visaApiRequestMaker: VisaApiRequestMaker,
|
||||
private val visaApi: TangemVisaApi,
|
||||
private val visaCurrencyFactory: VisaCurrencyFactory,
|
||||
) : VisaRepository {
|
||||
|
||||
private val currencyFactory by lazy(mode = LazyThreadSafetyMode.NONE) {
|
||||
VisaCurrencyFactory()
|
||||
}
|
||||
private val txDetailsFactory by lazy(mode = LazyThreadSafetyMode.NONE) {
|
||||
VisaTxDetailsFactory()
|
||||
}
|
||||
|
|
@ -79,20 +77,22 @@ internal class DefaultVisaRepository @Inject constructor(
|
|||
|
||||
private suspend fun fetchVisaCurrency(userWalletId: UserWalletId, address: String) {
|
||||
val contractInfoProvider = visaLibLoader.getOrCreateProvider()
|
||||
val paymentAccountAddress = getPaymentAccountAddress(userWalletId)
|
||||
val userWallet = findVisaUserWallet(userWalletId)
|
||||
|
||||
parZip(
|
||||
dispatchers.io,
|
||||
{
|
||||
contractInfoProvider.getContractInfo(
|
||||
walletAddress = address,
|
||||
paymentAccountAddress = getPaymentAccountAddress(userWalletId),
|
||||
paymentAccountAddress = paymentAccountAddress,
|
||||
)
|
||||
},
|
||||
{ getFiatRate() },
|
||||
{ contractInfo, fiatRate ->
|
||||
fetchedCurrencies.update { value ->
|
||||
value.apply {
|
||||
put(address, currencyFactory.create(contractInfo, fiatRate))
|
||||
put(address, visaCurrencyFactory.create(userWallet, contractInfo, fiatRate))
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -157,7 +157,7 @@ internal class DefaultVisaRepository @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
// TODO select correct account when multiple accounts are available (will be implemented when backend is ready)
|
||||
// There will be only one payment account (backend has filtered for us), so we can take the first one
|
||||
customerInfo.paymentAccounts.firstOrNull()?.paymentAccountAddress
|
||||
}.getOrNull()
|
||||
|
||||
|
|
@ -187,14 +187,14 @@ internal class DefaultVisaRepository @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun getFiatRate(): BigDecimal? {
|
||||
private suspend fun getFiatRate(): BigDecimal {
|
||||
val fiatCurrencyId = VisaConstants.fiatCurrency.code.lowercase()
|
||||
val quotes = tangemTechApi.getQuotes(
|
||||
currencyId = fiatCurrencyId,
|
||||
coinIds = VisaConstants.TOKEN_ID,
|
||||
).getOrThrow()
|
||||
|
||||
return quotes.quotes[VisaConstants.TOKEN_ID]?.price
|
||||
return quotes.quotes[VisaConstants.TOKEN_ID]?.price ?: error("No price found")
|
||||
}
|
||||
|
||||
private fun makeWalletAddresses(userWallet: UserWallet): Set<Address> {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,28 @@
|
|||
package com.tangem.data.visa.utils
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.data.common.currency.CryptoCurrencyFactory
|
||||
import com.tangem.data.common.currency.getNetwork
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.NetworkAddress
|
||||
import com.tangem.domain.visa.model.VisaCurrency
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.lib.visa.model.VisaContractInfo
|
||||
import org.joda.time.DateTime
|
||||
import org.joda.time.DateTimeZone
|
||||
import org.joda.time.Instant
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class VisaCurrencyFactory {
|
||||
internal class VisaCurrencyFactory @Inject constructor(
|
||||
private val cryptoCurrencyFactory: CryptoCurrencyFactory,
|
||||
private val excludedBlockchains: ExcludedBlockchains,
|
||||
) {
|
||||
|
||||
fun create(contractInfo: VisaContractInfo, fiatRate: BigDecimal?): VisaCurrency {
|
||||
fun create(userWallet: UserWallet, contractInfo: VisaContractInfo, fiatRate: BigDecimal): VisaCurrency {
|
||||
val now = Instant.now()
|
||||
val currentLimit = if (contractInfo.limitsChangeDate > now) {
|
||||
contractInfo.oldLimits
|
||||
|
|
@ -19,11 +31,30 @@ internal class VisaCurrencyFactory {
|
|||
}
|
||||
val remainingOtpLimit = getRemainingOtp(currentLimit, now)
|
||||
|
||||
val currencyNetwork = getNetwork(
|
||||
blockchain = Blockchain.Polygon,
|
||||
extraDerivationPath = null,
|
||||
derivationStyleProvider = userWallet.scanResponse.derivationStyleProvider,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
canHandleTokens = true,
|
||||
) ?: error("Unable to create network for Visa currency")
|
||||
|
||||
val cryptoCurrency = cryptoCurrencyFactory.createToken(
|
||||
network = currencyNetwork,
|
||||
rawId = CryptoCurrency.RawID(VisaConstants.TOKEN_ID),
|
||||
name = contractInfo.token.name,
|
||||
symbol = contractInfo.token.symbol,
|
||||
decimals = contractInfo.token.decimals,
|
||||
contractAddress = contractInfo.token.address,
|
||||
)
|
||||
|
||||
return VisaCurrency(
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
symbol = contractInfo.token.symbol,
|
||||
networkName = VisaConstants.NETWORK_NAME,
|
||||
decimals = contractInfo.token.decimals,
|
||||
fiatRate = fiatRate,
|
||||
priceChange = BigDecimal.ZERO, // [Second Visa Iteration] pass to create() params if needed
|
||||
fiatCurrency = VisaConstants.fiatCurrency,
|
||||
balances = with(contractInfo) {
|
||||
VisaCurrency.Balances(
|
||||
|
|
@ -40,6 +71,12 @@ internal class VisaCurrencyFactory {
|
|||
singleTransaction = currentLimit.singleTransactionLimit,
|
||||
expirationDate = getLimitsExpirationDate(currentLimit, now),
|
||||
),
|
||||
paymentAccountAddress = NetworkAddress.Single(
|
||||
NetworkAddress.Address(
|
||||
value = contractInfo.paymentAccountAddress,
|
||||
type = NetworkAddress.Address.Type.Primary,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -208,6 +208,10 @@ internal class DefaultWalletsRepository(
|
|||
.getObjectMap<Boolean>(PreferencesKeys.WALLETS_NFT_ENABLED_STATES_KEY)
|
||||
.map { it[userWalletId.stringValue] == true }
|
||||
|
||||
override fun nftEnabledStatuses(): Flow<Map<UserWalletId, Boolean>> = appPreferencesStore
|
||||
.getObjectMap<Boolean>(PreferencesKeys.WALLETS_NFT_ENABLED_STATES_KEY)
|
||||
.map { it.mapKeys { UserWalletId(it.key) } }
|
||||
|
||||
override suspend fun enableNFT(userWalletId: UserWalletId) {
|
||||
appPreferencesStore.editData {
|
||||
it.setObjectMap(
|
||||
|
|
|
|||
|
|
@ -17,4 +17,14 @@ data class NFTCollections(
|
|||
val error: Throwable,
|
||||
) : Content()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun empty(network: Network) = NFTCollections(
|
||||
network = network,
|
||||
content = Content.Collections(
|
||||
collections = null,
|
||||
source = StatusSource.ACTUAL,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.domain.nft
|
||||
|
||||
import com.tangem.domain.nft.repository.NFTRepository
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
class FetchNFTCollectionsUseCase(
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val nftRepository: NFTRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(userWalletId: UserWalletId) {
|
||||
val currencies = currenciesRepository.getMultiCurrencyWalletCurrenciesSync(userWalletId)
|
||||
nftRepository.refreshCollections(userWalletId, currencies.map { it.network }.distinct())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.domain.nft
|
||||
|
||||
import com.tangem.domain.nft.models.NFTCollections
|
||||
import com.tangem.domain.nft.repository.NFTRepository
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
|
||||
class GetNFTCollectionsUseCase(
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val nftRepository: NFTRepository,
|
||||
) {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun launch(userWalletId: UserWalletId): Flow<List<NFTCollections>> = currenciesRepository
|
||||
.getWalletCurrenciesUpdates(userWalletId)
|
||||
.flatMapLatest {
|
||||
val networks = it
|
||||
.map { cryptoCurrency -> cryptoCurrency.network }
|
||||
.distinct()
|
||||
nftRepository.observeCollections(userWalletId, networks)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +1,22 @@
|
|||
package com.tangem.domain.visa.model
|
||||
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.NetworkAddress
|
||||
import org.joda.time.DateTime
|
||||
import java.math.BigDecimal
|
||||
|
||||
data class VisaCurrency(
|
||||
val cryptoCurrency: CryptoCurrency,
|
||||
val networkName: String,
|
||||
val symbol: String,
|
||||
val decimals: Int,
|
||||
val fiatRate: BigDecimal?,
|
||||
val fiatRate: BigDecimal,
|
||||
val priceChange: BigDecimal,
|
||||
val fiatCurrency: AppCurrency,
|
||||
val balances: Balances,
|
||||
val limits: Limits,
|
||||
val paymentAccountAddress: NetworkAddress,
|
||||
) {
|
||||
|
||||
data class Balances(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
alias(deps.plugins.hilt.android)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
|
|
@ -19,6 +21,7 @@ dependencies {
|
|||
api(projects.domain.core)
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
implementation(projects.libs.tangemSdkApi)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.tokens)
|
||||
implementation(projects.domain.tokens.models)
|
||||
|
|
@ -29,4 +32,9 @@ dependencies {
|
|||
implementation(tangemDeps.blockchain) // android-library
|
||||
implementation(tangemDeps.card.core)
|
||||
// endregion
|
||||
|
||||
// region DI
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
// end
|
||||
}
|
||||
|
|
@ -3,14 +3,17 @@ package com.tangem.domain.wallets.builder
|
|||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.usecase.GetCardImageUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetCardImageUseCase
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
class UserWalletBuilder(
|
||||
private val scanResponse: ScanResponse,
|
||||
class UserWalletBuilder @AssistedInject constructor(
|
||||
@Assisted private val scanResponse: ScanResponse,
|
||||
private val generateWalletNameUseCase: GenerateWalletNameUseCase,
|
||||
private val getCardImageUseCase: GetCardImageUseCase = GetCardImageUseCase(),
|
||||
private val getCardImageUseCase: GetCardImageUseCase,
|
||||
) {
|
||||
private var backupCardsIds: Set<String> = emptySet()
|
||||
private var hasBackupError: Boolean = false
|
||||
|
|
@ -56,4 +59,10 @@ class UserWalletBuilder(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
|
||||
fun create(scanResponse: ScanResponse): UserWalletBuilder
|
||||
}
|
||||
}
|
||||
|
|
@ -32,6 +32,8 @@ interface WalletsRepository {
|
|||
|
||||
fun nftEnabledStatus(userWalletId: UserWalletId): Flow<Boolean>
|
||||
|
||||
fun nftEnabledStatuses(): Flow<Map<UserWalletId, Boolean>>
|
||||
|
||||
suspend fun enableNFT(userWalletId: UserWalletId)
|
||||
|
||||
suspend fun disableNFT(userWalletId: UserWalletId)
|
||||
|
|
|
|||
|
|
@ -6,16 +6,15 @@ import com.tangem.domain.common.TwinCardNumber
|
|||
import com.tangem.domain.common.TwinsHelper
|
||||
import com.tangem.domain.wallets.models.Artwork
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.operations.attestation.TangemApi
|
||||
|
||||
/**
|
||||
* Use case for getting card image url
|
||||
*
|
||||
* @property verifier REST API
|
||||
* @property verifier online card verifier
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class GetCardImageUseCase(private val verifier: OnlineCardVerifier = OnlineCardVerifier()) {
|
||||
class GetCardImageUseCase(private val verifier: OnlineCardVerifier) {
|
||||
|
||||
/**
|
||||
* Get card image url
|
||||
|
|
@ -30,7 +29,11 @@ class GetCardImageUseCase(private val verifier: OnlineCardVerifier = OnlineCardV
|
|||
if (artworkId.isNullOrEmpty()) {
|
||||
getFallbackArtworkUrl(cardId)
|
||||
} else {
|
||||
getUrlForArtwork(cardId, cardPublicKey.toHexString(), artworkId)
|
||||
OnlineCardVerifier.getUrlForArtwork(
|
||||
cardId = cardId,
|
||||
cardPublicKey = cardPublicKey.toHexString(),
|
||||
artworkId = artworkId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -51,9 +54,4 @@ class GetCardImageUseCase(private val verifier: OnlineCardVerifier = OnlineCardV
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getUrlForArtwork(cardId: String, cardPublicKeyHex: String, artworkId: String): String {
|
||||
return TangemApi.Companion.BaseUrl.VERIFY.url + TangemApi.ARTWORK +
|
||||
"?artworkId=$artworkId&CID=$cardId&publicKey=$cardPublicKeyHex"
|
||||
}
|
||||
}
|
||||
|
|
@ -23,7 +23,6 @@ import com.tangem.domain.redux.ReduxStateHolder
|
|||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
import com.tangem.domain.wallets.models.SaveWalletError
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
||||
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsSyncUseCase
|
||||
import com.tangem.features.details.impl.R
|
||||
|
|
@ -38,7 +37,7 @@ import kotlin.coroutines.resume
|
|||
internal class UserWalletSaver @Inject constructor(
|
||||
private val scanCardProcessor: ScanCardProcessor,
|
||||
private val saveWalletUseCase: SaveWalletUseCase,
|
||||
private val generateWalletNameUseCase: GenerateWalletNameUseCase,
|
||||
private val userWalletBuilderFactory: UserWalletBuilder.Factory,
|
||||
private val shouldSaveUserWalletsSyncUseCase: ShouldSaveUserWalletsSyncUseCase,
|
||||
private val reduxStateHolder: ReduxStateHolder,
|
||||
private val messageSender: UiMessageSender,
|
||||
|
|
@ -56,17 +55,20 @@ internal class UserWalletSaver @Inject constructor(
|
|||
}
|
||||
},
|
||||
ifRight = { scanResponse ->
|
||||
recover(block = {
|
||||
scanResponse ?: return
|
||||
val userWallet = createUserWallet(scanResponse)
|
||||
saveWallet(userWallet)
|
||||
}, recover = {
|
||||
recover(
|
||||
block = {
|
||||
scanResponse ?: return
|
||||
val userWallet = createUserWallet(scanResponse)
|
||||
saveWallet(userWallet)
|
||||
},
|
||||
recover = {
|
||||
val message = it.message
|
||||
|
||||
if (!message.isNullOrEmpty()) {
|
||||
messageSender.send(SnackbarMessage(message))
|
||||
}
|
||||
},)
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -110,7 +112,7 @@ internal class UserWalletSaver @Inject constructor(
|
|||
}
|
||||
|
||||
private suspend fun Raise<Error>.createUserWallet(response: ScanResponse): UserWallet {
|
||||
val userWallet = UserWalletBuilder(response, generateWalletNameUseCase).build()
|
||||
val userWallet = userWalletBuilderFactory.create(scanResponse = response).build()
|
||||
|
||||
return ensureNotNull(userWallet) { Error.Unknown }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,9 @@ dependencies {
|
|||
implementation(projects.core.navigation)
|
||||
implementation(projects.core.datasource)
|
||||
|
||||
/** Domain modules */
|
||||
implementation(projects.domain.nft.models)
|
||||
|
||||
/** Common */
|
||||
implementation(projects.common.ui)
|
||||
implementation(projects.common.routing)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import com.tangem.domain.common.TapWorkarounds.canSkipBackup
|
|||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
||||
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.analytics.OnboardingEvent
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams
|
||||
|
|
@ -25,7 +24,7 @@ import javax.inject.Inject
|
|||
internal class Wallet1ChooseOptionModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
paramsContainer: ParamsContainer,
|
||||
private val generateWalletNameUseCase: GenerateWalletNameUseCase,
|
||||
private val userWalletBuilderFactory: UserWalletBuilder.Factory,
|
||||
private val cardRepository: CardRepository,
|
||||
private val saveWalletUseCase: SaveWalletUseCase,
|
||||
private val analyticsHandler: AnalyticsEventHandler,
|
||||
|
|
@ -67,7 +66,7 @@ internal class Wallet1ChooseOptionModel @Inject constructor(
|
|||
|
||||
private suspend fun createUserWallet(scanResponse: ScanResponse): UserWallet {
|
||||
return requireNotNull(
|
||||
value = UserWalletBuilder(scanResponse, generateWalletNameUseCase).build(),
|
||||
value = userWalletBuilderFactory.create(scanResponse = scanResponse).build(),
|
||||
lazyMessage = { "User wallet not created" },
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import com.tangem.domain.feedback.models.FeedbackEmailType
|
|||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
||||
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
|
||||
import com.tangem.features.onboarding.v2.impl.R
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.analytics.OnboardingEvent
|
||||
|
|
@ -43,7 +42,7 @@ internal class MultiWalletCreateWalletModel @Inject constructor(
|
|||
private val getCardInfoUseCase: GetCardInfoUseCase,
|
||||
private val cardRepository: CardRepository,
|
||||
private val analyticsHandler: AnalyticsEventHandler,
|
||||
private val generateWalletNameUseCase: GenerateWalletNameUseCase,
|
||||
private val userWalletBuilderFactory: UserWalletBuilder.Factory,
|
||||
private val saveWalletUseCase: SaveWalletUseCase,
|
||||
) : Model() {
|
||||
|
||||
|
|
@ -146,7 +145,7 @@ internal class MultiWalletCreateWalletModel @Inject constructor(
|
|||
|
||||
private suspend fun createUserWallet(scanResponse: ScanResponse): UserWallet {
|
||||
return requireNotNull(
|
||||
value = UserWalletBuilder(scanResponse, generateWalletNameUseCase).build(),
|
||||
value = userWalletBuilderFactory.create(scanResponse = scanResponse).build(),
|
||||
lazyMessage = { "User wallet not created" },
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import com.tangem.domain.wallets.builder.UserWalletBuilder
|
|||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
||||
import com.tangem.features.onboarding.v2.common.ui.CantLeaveBackupDialog
|
||||
import com.tangem.features.onboarding.v2.impl.R
|
||||
import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent
|
||||
|
|
@ -49,7 +48,7 @@ internal class MultiWalletFinalizeModel @Inject constructor(
|
|||
private val tangemSdkManager: TangemSdkManager,
|
||||
private val getCardInfoUseCase: GetCardInfoUseCase,
|
||||
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
|
||||
private val generateWalletNameUseCase: GenerateWalletNameUseCase,
|
||||
private val userWalletBuilderFactory: UserWalletBuilder.Factory,
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
private val cardRepository: CardRepository,
|
||||
private val onboardingRepository: OnboardingRepository,
|
||||
|
|
@ -279,7 +278,7 @@ internal class MultiWalletFinalizeModel @Inject constructor(
|
|||
|
||||
private suspend fun createUserWallet(scanResponse: ScanResponse): UserWallet {
|
||||
return requireNotNull(
|
||||
value = UserWalletBuilder(scanResponse, generateWalletNameUseCase)
|
||||
value = userWalletBuilderFactory.create(scanResponse = scanResponse)
|
||||
.backupCardsIds(backupCardIds.toSet())
|
||||
.hasBackupError(walletHasBackupError)
|
||||
.build(),
|
||||
|
|
|
|||
|
|
@ -18,21 +18,25 @@ import com.tangem.features.onboarding.v2.multiwallet.impl.ui.state.OnboardingMul
|
|||
import com.tangem.operations.backup.BackupService
|
||||
import com.tangem.sdk.api.BackupServiceHolder
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@ModelScoped
|
||||
internal class OnboardingMultiWalletModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
analyticsHandler: AnalyticsEventHandler,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val router: Router,
|
||||
private val analyticsHandler: AnalyticsEventHandler,
|
||||
private val backupServiceHolder: BackupServiceHolder,
|
||||
private val onboardingRepository: OnboardingRepository,
|
||||
private val getCardImageUseCase: GetCardImageUseCase,
|
||||
) : Model() {
|
||||
private val params = paramsContainer.require<OnboardingMultiWalletComponent.Params>()
|
||||
private val getCardImageUseCase = GetCardImageUseCase()
|
||||
private val _uiState = MutableStateFlow(OnboardingMultiWalletUM())
|
||||
|
||||
val state = MutableStateFlow(
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import com.tangem.domain.visa.repository.VisaAuthRepository
|
|||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.inprogress.OnboardingVisaInProgressComponent.Config
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.inprogress.OnboardingVisaInProgressComponent.Params
|
||||
import com.tangem.features.onboarding.v2.visa.impl.route.OnboardingVisaRoute
|
||||
|
|
@ -35,7 +34,7 @@ internal class OnboardingVisaInProgressModel @Inject constructor(
|
|||
private val visaAuthRepository: VisaAuthRepository,
|
||||
private val visaAuthTokenStorage: VisaAuthTokenStorage,
|
||||
private val otpStorage: VisaOTPStorage,
|
||||
private val generateWalletNameUseCase: GenerateWalletNameUseCase,
|
||||
private val userWalletBuilderFactory: UserWalletBuilder.Factory,
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
) : Model() {
|
||||
|
||||
|
|
@ -110,9 +109,8 @@ internal class OnboardingVisaInProgressModel @Inject constructor(
|
|||
val newActivationStatus = VisaCardActivationStatus.Activated(visaAuthTokens = authTokens)
|
||||
|
||||
requireNotNull(
|
||||
value = UserWalletBuilder(
|
||||
scanResponse.copy(visaCardActivationStatus = newActivationStatus),
|
||||
generateWalletNameUseCase,
|
||||
value = userWalletBuilderFactory.create(
|
||||
scanResponse = scanResponse.copy(visaCardActivationStatus = newActivationStatus),
|
||||
).build(),
|
||||
lazyMessage = { "User wallet not created" },
|
||||
)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.tangem.common.extensions.toHexString
|
|||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.datasource.local.visa.VisaAuthTokenStorage
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.visa.SetVisaPinCodeUseCase
|
||||
|
|
@ -13,7 +14,6 @@ import com.tangem.domain.visa.model.VisaCardId
|
|||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.pincode.OnboardingVisaPinCodeComponent
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.pincode.ui.state.OnboardingVisaPinCodeUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -31,7 +31,7 @@ import javax.inject.Inject
|
|||
internal class OnboardingVisaPinCodeModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val generateWalletNameUseCase: GenerateWalletNameUseCase,
|
||||
private val userWalletBuilderFactory: UserWalletBuilder.Factory,
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
private val authTokenStorage: VisaAuthTokenStorage,
|
||||
private val otpStorage: VisaAuthTokenStorage,
|
||||
|
|
@ -57,24 +57,28 @@ internal class OnboardingVisaPinCodeModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun onPinCodeChange(pin: String) {
|
||||
if (pin.all { it.isDigit() }) {
|
||||
if (PinCodeValidation.validateAllDigits(pin)) {
|
||||
val isError = PinCodeValidation.validateLength(pin) && PinCodeValidation.validate(pin).not()
|
||||
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
pinCode = pin,
|
||||
submitButtonEnabled = checkPinCode(pin),
|
||||
submitButtonEnabled = PinCodeValidation.validate(pin),
|
||||
error = if (isError) stringReference("Invalid PIN: avoid sequences or repeats") else null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSubmitClick() {
|
||||
if (checkPinCode(_uiState.value.pinCode).not()) return
|
||||
val pinCode = _uiState.value.pinCode
|
||||
if (PinCodeValidation.validate(pinCode).not()) return
|
||||
|
||||
modelScope.launch {
|
||||
loading(true)
|
||||
|
||||
setVisaPinCodeUseCase(
|
||||
pinCode = _uiState.value.pinCode,
|
||||
pinCode = pinCode,
|
||||
visaCardId = visaCardId,
|
||||
activationOrderId = params.activationOrderInfo.orderId,
|
||||
).onLeft {
|
||||
|
|
@ -88,10 +92,6 @@ internal class OnboardingVisaPinCodeModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun checkPinCode(pin: String): Boolean {
|
||||
return pin.length == PIN_CODE_LENGTH
|
||||
}
|
||||
|
||||
private fun loading(state: Boolean) {
|
||||
_uiState.update { it.copy(submitButtonLoading = state) }
|
||||
}
|
||||
|
|
@ -111,9 +111,8 @@ internal class OnboardingVisaPinCodeModel @Inject constructor(
|
|||
)
|
||||
|
||||
requireNotNull(
|
||||
value = UserWalletBuilder(
|
||||
scanResponse.copy(visaCardActivationStatus = newActivationStatus),
|
||||
generateWalletNameUseCase,
|
||||
value = userWalletBuilderFactory.create(
|
||||
scanResponse = scanResponse.copy(visaCardActivationStatus = newActivationStatus),
|
||||
).build(),
|
||||
lazyMessage = { "User wallet not created" },
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.features.onboarding.v2.visa.impl.child.pincode.model
|
||||
|
||||
internal object PinCodeValidation {
|
||||
|
||||
private const val PIN_CODE_LENGTH = 4
|
||||
|
||||
fun validate(pinCode: String): Boolean {
|
||||
return sequenceOf(
|
||||
::validateAllDigits,
|
||||
::validateLength,
|
||||
::validateNoRepeatedDigits,
|
||||
::validateNoConsecutiveDigits,
|
||||
).all { it(pinCode) }
|
||||
}
|
||||
|
||||
fun validateAllDigits(pinCode: String): Boolean {
|
||||
return pinCode.all { it.isDigit() }
|
||||
}
|
||||
|
||||
fun validateLength(pinCode: String): Boolean {
|
||||
return pinCode.length == PIN_CODE_LENGTH
|
||||
}
|
||||
|
||||
private fun validateNoRepeatedDigits(pinCode: String): Boolean {
|
||||
return pinCode.toSet().size == PIN_CODE_LENGTH
|
||||
}
|
||||
|
||||
private fun validateNoConsecutiveDigits(pinCode: String): Boolean {
|
||||
return pinCode.windowed(2).all { it[0] + 1 == it[1] }.not()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.onboarding.v2.visa.impl.child.pincode.ui
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
|
|
@ -31,7 +32,10 @@ import com.tangem.common.ui.navigationButtons.NavigationButton
|
|||
import com.tangem.common.ui.navigationButtons.NavigationPrimaryButton
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.components.SpacerH16
|
||||
import com.tangem.core.ui.components.SpacerH4
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.pincode.ui.state.OnboardingVisaPinCodeUM
|
||||
|
|
@ -91,13 +95,29 @@ internal fun OnboardingVisaPinCode(state: OnboardingVisaPinCodeUM, modifier: Mod
|
|||
@Composable
|
||||
private fun PinCodeSection(state: OnboardingVisaPinCodeUM, modifier: Modifier = Modifier) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
PinCode(
|
||||
modifier = modifier,
|
||||
value = state.pinCode,
|
||||
onValueChange = state.onPinCodeChange,
|
||||
focusRequester = focusRequester,
|
||||
)
|
||||
|
||||
PinCode(
|
||||
modifier = modifier,
|
||||
value = state.pinCode,
|
||||
onValueChange = state.onPinCodeChange,
|
||||
focusRequester = focusRequester,
|
||||
)
|
||||
AnimatedVisibility(
|
||||
visible = state.error != null,
|
||||
) {
|
||||
val error = remember(this) { requireNotNull(state.error) }
|
||||
Column {
|
||||
SpacerH4()
|
||||
Text(
|
||||
text = error.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.warning,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
delay(timeMillis = 300)
|
||||
|
|
@ -134,9 +154,11 @@ private fun PinCode(
|
|||
keyboardType = KeyboardType.Number,
|
||||
imeAction = ImeAction.Done,
|
||||
),
|
||||
keyboardActions = KeyboardActions(onDone = {
|
||||
keyboardController?.hide()
|
||||
},),
|
||||
keyboardActions = KeyboardActions(
|
||||
onDone = {
|
||||
keyboardController?.hide()
|
||||
},
|
||||
),
|
||||
modifier = Modifier
|
||||
.alpha(alpha = 0.01f)
|
||||
.focusRequester(focusRequester),
|
||||
|
|
@ -194,6 +216,7 @@ private fun Preview() {
|
|||
OnboardingVisaPinCode(
|
||||
state = OnboardingVisaPinCodeUM(
|
||||
pinCode = "1234",
|
||||
error = stringReference("PIN Code error"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
package com.tangem.features.onboarding.v2.visa.impl.child.pincode.ui.state
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
||||
internal data class OnboardingVisaPinCodeUM(
|
||||
val pinCode: String = "",
|
||||
val error: TextReference? = null,
|
||||
val onPinCodeChange: (String) -> Unit = {},
|
||||
val submitButtonLoading: Boolean = false,
|
||||
val submitButtonEnabled: Boolean = true,
|
||||
|
|
|
|||
|
|
@ -90,6 +90,8 @@ dependencies {
|
|||
implementation(projects.domain.onramp)
|
||||
implementation(projects.domain.promo)
|
||||
implementation(projects.domain.promo.models)
|
||||
implementation(projects.domain.nft)
|
||||
implementation(projects.domain.nft.models)
|
||||
|
||||
//TODO: Create api/impl modules for onboarding [REDACTED_JIRA]
|
||||
implementation(projects.features.onboarding)
|
||||
|
|
@ -108,6 +110,7 @@ dependencies {
|
|||
implementation(projects.features.wallet.api)
|
||||
implementation(projects.features.walletSettings.api)
|
||||
implementation(projects.features.biometry.api)
|
||||
implementation(projects.features.nft.api)
|
||||
|
||||
/** Common modules */
|
||||
implementation(projects.common)
|
||||
|
|
|
|||
|
|
@ -10,9 +10,11 @@ import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
|
|||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.nft.FetchNFTCollectionsUseCase
|
||||
import com.tangem.domain.settings.*
|
||||
import com.tangem.domain.tokens.RefreshMultiCurrencyWalletQuotesUseCase
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
|
|
@ -41,6 +43,7 @@ import com.tangem.features.pushnotifications.api.utils.getPushPermissionOrNull
|
|||
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.coroutines.*
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -76,6 +79,8 @@ internal class WalletModel @Inject constructor(
|
|||
private val walletFeatureToggles: WalletFeatureToggles,
|
||||
private val biometryFeatureToggles: BiometryFeatureToggles,
|
||||
private val analyticsEventsHandler: AnalyticsEventHandler,
|
||||
private val fetchNFTCollectionsUseCase: FetchNFTCollectionsUseCase,
|
||||
private val walletsRepository: WalletsRepository,
|
||||
val screenLifecycleProvider: ScreenLifecycleProvider,
|
||||
val innerWalletRouter: InnerWalletRouter,
|
||||
) : Model() {
|
||||
|
|
@ -86,6 +91,7 @@ internal class WalletModel @Inject constructor(
|
|||
private val walletsUpdateJobHolder = JobHolder()
|
||||
private val refreshWalletJobHolder = JobHolder()
|
||||
private val expressStatusJobHolder = JobHolder()
|
||||
private val walletsNFTsUpdateJobHolder = JobHolder()
|
||||
private var needToRefreshWallet = false
|
||||
|
||||
private var expressTxStatusTaskScheduler = SingleTaskScheduler<Unit>()
|
||||
|
|
@ -103,6 +109,7 @@ internal class WalletModel @Inject constructor(
|
|||
subscribeToScreenBackgroundState()
|
||||
subscribeOnPushNotificationsPermission()
|
||||
subscribeOnExpressTransactionsUpdates()
|
||||
subscribeOnNFTUpdates()
|
||||
|
||||
clickIntents.initialize(innerWalletRouter, modelScope)
|
||||
}
|
||||
|
|
@ -160,7 +167,10 @@ internal class WalletModel @Inject constructor(
|
|||
.conflate()
|
||||
.distinctUntilChanged()
|
||||
.map {
|
||||
walletsUpdateActionResolver.resolve(wallets = it, currentState = stateHolder.value)
|
||||
walletsUpdateActionResolver.resolve(
|
||||
wallets = it,
|
||||
currentState = stateHolder.value,
|
||||
)
|
||||
}
|
||||
.onEach(::updateWallets)
|
||||
.flowOn(dispatchers.main)
|
||||
|
|
@ -256,6 +266,29 @@ internal class WalletModel @Inject constructor(
|
|||
}.saveIn(expressStatusJobHolder)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private fun subscribeOnNFTUpdates() {
|
||||
getWalletsUseCase()
|
||||
.conflate()
|
||||
.distinctUntilChanged()
|
||||
.flatMapLatest { wallets ->
|
||||
wallets
|
||||
.map { wallet ->
|
||||
walletsRepository.nftEnabledStatus(wallet.walletId)
|
||||
.distinctUntilChanged()
|
||||
.onEach { nftEnabled ->
|
||||
if (nftEnabled) {
|
||||
fetchNFTCollectionsUseCase.invoke(wallet.walletId)
|
||||
}
|
||||
}
|
||||
}
|
||||
.merge()
|
||||
}
|
||||
.flowOn(dispatchers.main)
|
||||
.launchIn(modelScope)
|
||||
.saveIn(walletsNFTsUpdateJobHolder)
|
||||
}
|
||||
|
||||
private fun needToRefreshTimer() {
|
||||
modelScope.launch {
|
||||
delay(REFRESH_WALLET_BACKGROUND_TIMER_MILLIS)
|
||||
|
|
|
|||
|
|
@ -231,7 +231,10 @@ internal class WalletsUpdateActionResolver @Inject constructor(
|
|||
* @property prevWalletId previous selected wallet id
|
||||
* @property selectedWallet selected wallet
|
||||
*/
|
||||
data class ReinitializeWallet(val prevWalletId: UserWalletId, val selectedWallet: UserWallet) : Action() {
|
||||
data class ReinitializeWallet(
|
||||
val prevWalletId: UserWalletId,
|
||||
val selectedWallet: UserWallet,
|
||||
) : Action() {
|
||||
|
||||
override fun toString(): String {
|
||||
return "ReinitializeWallet(prevWalletId = $prevWalletId, selectedWallet = ${selectedWallet.walletId})"
|
||||
|
|
|
|||
|
|
@ -2,29 +2,19 @@ package com.tangem.feature.wallet.child.wallet.model.intents
|
|||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.components.bottomsheets.tokenreceive.TokenReceiveBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.tokenreceive.mapToAddressModels
|
||||
import com.tangem.domain.tokens.GetCryptoCurrencyStatusSyncUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.visa.GetVisaCurrencyUseCase
|
||||
import com.tangem.domain.visa.GetVisaTxDetailsUseCase
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.BalancesAndLimitsBottomSheetConverter
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.VisaTxDetailsBottomSheetConverter
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface VisaWalletIntents {
|
||||
|
||||
fun onDepositClick()
|
||||
|
||||
fun onBalancesAndLimitsClick()
|
||||
|
||||
fun onVisaTransactionClick(id: String)
|
||||
|
|
@ -36,7 +26,6 @@ internal interface VisaWalletIntents {
|
|||
internal class VisaWalletIntentsImplementor @Inject constructor(
|
||||
private val stateController: WalletStateController,
|
||||
private val eventSender: WalletEventSender,
|
||||
private val getCurrencyStatusUseCase: GetCryptoCurrencyStatusSyncUseCase,
|
||||
private val getVisaCurrencyUseCase: GetVisaCurrencyUseCase,
|
||||
private val getVisaTxDetailsUseCase: GetVisaTxDetailsUseCase,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
|
|
@ -46,38 +35,6 @@ internal class VisaWalletIntentsImplementor @Inject constructor(
|
|||
BalancesAndLimitsBottomSheetConverter(eventSender)
|
||||
}
|
||||
|
||||
override fun onDepositClick() {
|
||||
val userWalletId = stateController.getSelectedWalletId()
|
||||
|
||||
modelScope.launch(dispatchers.main) {
|
||||
val currencyStatus = getPrimaryCurrencyStatus(userWalletId) ?: return@launch
|
||||
|
||||
createReceiveBottomSheetContent(currencyStatus)?.let { content ->
|
||||
stateController.showBottomSheet(content)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createReceiveBottomSheetContent(currencyStatus: CryptoCurrencyStatus): TangemBottomSheetConfigContent? {
|
||||
val currency = currencyStatus.currency
|
||||
val addresses = currencyStatus.value.networkAddress?.availableAddresses
|
||||
|
||||
if (addresses == null) {
|
||||
Timber.e("Addresses should not be null")
|
||||
return null
|
||||
}
|
||||
|
||||
return TokenReceiveBottomSheetConfig(
|
||||
name = currency.name,
|
||||
symbol = currency.symbol,
|
||||
network = currency.network.name,
|
||||
addresses = addresses.mapToAddressModels(currency).toImmutableList(),
|
||||
showMemoDisclaimer = currency.network.transactionExtrasType != Network.TransactionExtrasType.NONE,
|
||||
onCopyClick = { /* no-op */ },
|
||||
onShareClick = { /* no-op */ },
|
||||
)
|
||||
}
|
||||
|
||||
override fun onBalancesAndLimitsClick() {
|
||||
modelScope.launch(dispatchers.main) {
|
||||
val userWalletId = stateController.getSelectedWalletId()
|
||||
|
|
@ -95,14 +52,6 @@ internal class VisaWalletIntentsImplementor @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun getPrimaryCurrencyStatus(userWalletId: UserWalletId): CryptoCurrencyStatus? {
|
||||
return getCurrencyStatusUseCase(userWalletId)
|
||||
.getOrElse {
|
||||
Timber.e("Failed to get primary currency $it")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onVisaTransactionClick(id: String) {
|
||||
modelScope.launch(dispatchers.main) {
|
||||
val userWalletId = stateController.getSelectedWalletId()
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
|||
import com.tangem.domain.appcurrency.extenstions.unwrap
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.exchange.RampStateManager
|
||||
import com.tangem.domain.nft.FetchNFTCollectionsUseCase
|
||||
import com.tangem.domain.onramp.FetchHotCryptoUseCase
|
||||
import com.tangem.domain.settings.NeverToShowWalletsScrollPreview
|
||||
import com.tangem.domain.tokens.FetchCardTokenListUseCase
|
||||
|
|
@ -14,6 +15,7 @@ import com.tangem.domain.tokens.FetchTokenListUseCase.RefreshMode
|
|||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.domain.wallets.usecase.SelectWalletUseCase
|
||||
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.IsWalletNFTEnabledSyncUseCase
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.unwrap
|
||||
import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
||||
|
|
@ -50,6 +52,8 @@ internal class WalletClickIntents @Inject constructor(
|
|||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val onrampFeatureToggles: OnrampFeatureToggles,
|
||||
private val fetchHotCryptoUseCase: FetchHotCryptoUseCase,
|
||||
private val fetchNFTCollectionsUseCase: FetchNFTCollectionsUseCase,
|
||||
private val isWalletNFTEnabledSyncUseCase: IsWalletNFTEnabledSyncUseCase,
|
||||
) : BaseWalletClickIntents(),
|
||||
WalletCardClickIntents by walletCardClickIntentsImplementor,
|
||||
WalletWarningsClickIntents by warningsClickIntentsImplementer,
|
||||
|
|
@ -140,6 +144,10 @@ internal class WalletClickIntents @Inject constructor(
|
|||
if (onrampFeatureToggles.isHotTokensEnabled) {
|
||||
async { fetchHotCryptoUseCase() }.let(::add)
|
||||
}
|
||||
|
||||
if (isWalletNFTEnabledSyncUseCase.invoke(userWallet.walletId)) {
|
||||
async { fetchNFTCollectionsUseCase(userWalletId = userWallet.walletId) }.let(::add)
|
||||
}
|
||||
}
|
||||
.awaitAll()
|
||||
|
||||
|
|
|
|||
|
|
@ -120,6 +120,12 @@ internal object WalletScreenPreviewData {
|
|||
),
|
||||
bottomSheetConfig = null,
|
||||
tokensListState = textContentTokensState,
|
||||
nftState = WalletNFTItemUM.Content(
|
||||
previews = persistentListOf(WalletNFTItemUM.Content.CollectionPreview.Image("img1")),
|
||||
collectionsCount = 1,
|
||||
assetsCount = 3,
|
||||
isFlickering = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.domain
|
||||
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.features.nft.NFTFeatureToggles
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
|
||||
class IsWalletNFTEnabledSyncUseCase(
|
||||
private val walletsRepository: WalletsRepository,
|
||||
private val nftFeatureToggles: NFTFeatureToggles,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(userWalletId: UserWalletId): Boolean = if (nftFeatureToggles.isNFTEnabled) {
|
||||
walletsRepository
|
||||
.nftEnabledStatuses()
|
||||
.firstOrNull()
|
||||
?.let { it[userWalletId] }
|
||||
?: false
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,6 @@ import com.tangem.common.core.TangemSdkError
|
|||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.domain.card.ScanCardProcessor
|
||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
|
||||
import javax.inject.Inject
|
||||
|
|
@ -16,7 +15,7 @@ import javax.inject.Inject
|
|||
internal class ScanCardToUnlockWalletClickHandler @Inject constructor(
|
||||
private val scanCardProcessor: ScanCardProcessor,
|
||||
private val saveWalletUseCase: SaveWalletUseCase,
|
||||
private val generateWalletNameUseCase: GenerateWalletNameUseCase,
|
||||
private val userWalletBuilderFactory: UserWalletBuilder.Factory,
|
||||
) {
|
||||
|
||||
private var scanFailsCounter = 0
|
||||
|
|
@ -34,7 +33,7 @@ internal class ScanCardToUnlockWalletClickHandler @Inject constructor(
|
|||
scanFailsCounter = 0
|
||||
|
||||
// If card's public key is null then user wallet will be null
|
||||
val scannedWallet = UserWalletBuilder(result.data, generateWalletNameUseCase).build()
|
||||
val scannedWallet = userWalletBuilderFactory.create(scanResponse = result.data).build()
|
||||
|
||||
ensure(walletId == scannedWallet?.walletId) {
|
||||
ScanCardToUnlockWalletError.WrongCardIsScanned
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ internal enum class Wallet2CobrandImage(
|
|||
BitcoinGold(
|
||||
cards2ResId = R.drawable.ill_bitcoingold_card2_120_106,
|
||||
cards3ResId = R.drawable.ill_bitcoingold_card3_120_106,
|
||||
batchIds = setOf("AF71"),
|
||||
batchIds = setOf("AF71", "AF990016", "AF990009"),
|
||||
),
|
||||
|
||||
BitcoinPizzaDay(
|
||||
|
|
|
|||
|
|
@ -3,10 +3,12 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
|
|||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.deeplink.DeepLinksRegistry
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.nft.GetNFTCollectionsUseCase
|
||||
import com.tangem.domain.promo.GetStoryContentUseCase
|
||||
import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
|
||||
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
|
||||
|
|
@ -21,6 +23,7 @@ import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletActi
|
|||
import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletTokenListSubscriber
|
||||
import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletWarningsSubscriber
|
||||
import com.tangem.feature.wallet.presentation.wallet.subscribers.WalletSubscriber
|
||||
import com.tangem.features.nft.NFTFeatureToggles
|
||||
import com.tangem.features.swap.SwapFeatureToggles
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
|
|
@ -34,6 +37,7 @@ internal class MultiWalletContentLoader(
|
|||
private val walletWarningsSingleEventSender: WalletWarningsSingleEventSender,
|
||||
private val walletWithFundsChecker: WalletWithFundsChecker,
|
||||
private val tokenListStore: MultiWalletTokenListStore,
|
||||
private val getNFTCollectionsUseCase: GetNFTCollectionsUseCase,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase,
|
||||
private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory,
|
||||
|
|
@ -42,6 +46,8 @@ internal class MultiWalletContentLoader(
|
|||
private val getStoryContentUseCase: GetStoryContentUseCase,
|
||||
private val swapFeatureToggles: SwapFeatureToggles,
|
||||
private val deepLinksRegistry: DeepLinksRegistry,
|
||||
private val nftFeatureToggles: NFTFeatureToggles,
|
||||
private val walletsRepository: WalletsRepository,
|
||||
) : WalletContentLoader(id = userWallet.walletId) {
|
||||
|
||||
override fun create(): List<WalletSubscriber> {
|
||||
|
|
@ -58,6 +64,15 @@ internal class MultiWalletContentLoader(
|
|||
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
|
||||
deepLinksRegistry = deepLinksRegistry,
|
||||
).let(::add)
|
||||
if (nftFeatureToggles.isNFTEnabled) {
|
||||
WalletNFTListSubscriber(
|
||||
userWallet = userWallet,
|
||||
getNFTCollectionsUseCase = getNFTCollectionsUseCase,
|
||||
stateHolder = stateHolder,
|
||||
walletsRepository = walletsRepository,
|
||||
clickIntents = clickIntents,
|
||||
).let(::add)
|
||||
}
|
||||
MultiWalletWarningsSubscriber(
|
||||
userWallet = userWallet,
|
||||
stateHolder = stateHolder,
|
||||
|
|
|
|||
|
|
@ -3,10 +3,12 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
|
|||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.deeplink.DeepLinksRegistry
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.nft.GetNFTCollectionsUseCase
|
||||
import com.tangem.domain.promo.GetStoryContentUseCase
|
||||
import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
|
||||
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
|
||||
|
|
@ -16,6 +18,7 @@ import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarnin
|
|||
import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
||||
import com.tangem.features.nft.NFTFeatureToggles
|
||||
import com.tangem.features.swap.SwapFeatureToggles
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -36,6 +39,9 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
|
|||
private val getStoryContentUseCase: GetStoryContentUseCase,
|
||||
private val swapFeatureToggles: SwapFeatureToggles,
|
||||
private val deepLinksRegistry: DeepLinksRegistry,
|
||||
private val nftFeatureToggles: NFTFeatureToggles,
|
||||
private val walletsRepository: WalletsRepository,
|
||||
private val getNFTCollectionsUseCase: GetNFTCollectionsUseCase,
|
||||
) {
|
||||
|
||||
fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): WalletContentLoader {
|
||||
|
|
@ -56,6 +62,9 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
|
|||
shouldSaveUserWalletsUseCase = shouldSaveUserWalletsUseCase,
|
||||
swapFeatureToggles = swapFeatureToggles,
|
||||
deepLinksRegistry = deepLinksRegistry,
|
||||
nftFeatureToggles = nftFeatureToggles,
|
||||
walletsRepository = walletsRepository,
|
||||
getNFTCollectionsUseCase = getNFTCollectionsUseCase,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.tangem.feature.wallet.presentation.wallet.state
|
|||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.event.consumedEvent
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.*
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.NOT_INITIALIZED_WALLET_INDEX
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.model
|
||||
|
||||
internal data class DepositButtonState(
|
||||
val isEnabled: Boolean,
|
||||
val onClick: () -> Unit,
|
||||
)
|
||||
|
|
@ -22,6 +22,7 @@ internal sealed interface WalletState : WalletStateHolder {
|
|||
sealed class MultiCurrency : WalletState {
|
||||
|
||||
abstract val tokensListState: WalletTokensListState
|
||||
abstract val nftState: WalletNFTItemUM
|
||||
|
||||
data class Content(
|
||||
override val pullToRefreshConfig: PullToRefreshConfig,
|
||||
|
|
@ -30,6 +31,7 @@ internal sealed interface WalletState : WalletStateHolder {
|
|||
override val warnings: ImmutableList<WalletNotification>,
|
||||
override val bottomSheetConfig: TangemBottomSheetConfig?,
|
||||
override val tokensListState: WalletTokensListState,
|
||||
override val nftState: WalletNFTItemUM,
|
||||
) : MultiCurrency()
|
||||
|
||||
data class Locked(
|
||||
|
|
@ -46,6 +48,7 @@ internal sealed interface WalletState : WalletStateHolder {
|
|||
) {
|
||||
|
||||
override val tokensListState = WalletTokensListState.ContentState.Locked
|
||||
override val nftState: WalletNFTItemUM = WalletNFTItemUM.Hidden
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -96,7 +99,6 @@ internal sealed interface WalletState : WalletStateHolder {
|
|||
override val bottomSheetConfig: TangemBottomSheetConfig?,
|
||||
override val balancesAndLimitBlockState: BalancesAndLimitsBlockState,
|
||||
override val txHistoryState: TxHistoryState,
|
||||
val depositButtonState: DepositButtonState,
|
||||
) : Visa()
|
||||
|
||||
data class Locked(
|
||||
|
|
|
|||
|
|
@ -38,7 +38,9 @@ internal class InitializeWalletsTransformer(
|
|||
if (userWallet.isLocked) {
|
||||
createLockedState(userWallet)
|
||||
} else {
|
||||
walletLoadingStateFactory.create(userWallet)
|
||||
walletLoadingStateFactory.create(
|
||||
userWallet = userWallet,
|
||||
)
|
||||
}
|
||||
}
|
||||
.toImmutableList(),
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers
|
|||
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletLoadingStateFactory
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
|
|
@ -38,7 +38,11 @@ internal class ReinitializeWalletTransformer(
|
|||
return prevState.copy(
|
||||
wallets = prevState.wallets
|
||||
.filterNot { it.walletCardState.id == prevWalletId }
|
||||
.plus(element = walletLoadingStateFactory.create(newUserWallet))
|
||||
.plus(
|
||||
element = walletLoadingStateFactory.create(
|
||||
userWallet = newUserWallet,
|
||||
),
|
||||
)
|
||||
.toImmutableList(),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.transformers
|
||||
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNFTItemUM
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
|
||||
|
||||
internal class RemoveNFTCollectionsTransformer(
|
||||
userWalletId: UserWalletId,
|
||||
) : WalletStateTransformer(userWalletId) {
|
||||
|
||||
override fun transform(prevState: WalletState): WalletState = when (prevState) {
|
||||
is WalletState.MultiCurrency.Content -> prevState.copy(
|
||||
nftState = WalletNFTItemUM.Hidden,
|
||||
)
|
||||
is WalletState.SingleCurrency.Content,
|
||||
is WalletState.Visa.Content,
|
||||
is WalletState.MultiCurrency.Locked,
|
||||
is WalletState.SingleCurrency.Locked,
|
||||
is WalletState.Visa.Locked,
|
||||
is WalletState.Visa.AccessTokenLocked,
|
||||
-> prevState
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.transformers
|
||||
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.nft.models.NFTCollections
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNFTItemUM
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
internal class SetNFTCollectionsTransformer(
|
||||
userWalletId: UserWalletId,
|
||||
private val nftCollections: List<NFTCollections>,
|
||||
) : WalletStateTransformer(userWalletId) {
|
||||
|
||||
override fun transform(prevState: WalletState): WalletState = when (prevState) {
|
||||
is WalletState.MultiCurrency.Content -> prevState.copy(
|
||||
nftState = when {
|
||||
allCollectionsFailed() -> WalletNFTItemUM.Failed
|
||||
anyCollectionFailed() && allLoadedCollectionsEmpty() -> WalletNFTItemUM.Failed
|
||||
allCollectionsLoaded() && allCollectionsEmpty() -> WalletNFTItemUM.Empty
|
||||
!allCollectionsLoaded() && allCollectionsEmpty() -> WalletNFTItemUM.Loading
|
||||
else -> createContentNFTItemUM()
|
||||
},
|
||||
)
|
||||
is WalletState.SingleCurrency.Content,
|
||||
is WalletState.Visa.Content,
|
||||
is WalletState.MultiCurrency.Locked,
|
||||
is WalletState.SingleCurrency.Locked,
|
||||
is WalletState.Visa.Locked,
|
||||
is WalletState.Visa.AccessTokenLocked,
|
||||
-> prevState
|
||||
}
|
||||
|
||||
private fun createContentNFTItemUM(): WalletNFTItemUM.Content {
|
||||
val collectionsContent = nftCollections
|
||||
.map { it.content }
|
||||
.filterIsInstance<NFTCollections.Content.Collections>()
|
||||
|
||||
val isFlickering = collectionsContent
|
||||
.map { it.source }
|
||||
.any { it == StatusSource.CACHE }
|
||||
|
||||
val collections = collectionsContent
|
||||
.mapNotNull { it.collections }
|
||||
.flatten()
|
||||
|
||||
return WalletNFTItemUM.Content(
|
||||
previews = if (collections.size > NFT_COLLECTIONS_MAX_PREVIEWS_COUNT) {
|
||||
collections
|
||||
.take(NFT_COLLECTIONS_MAX_PREVIEWS_COUNT - 1)
|
||||
.map { WalletNFTItemUM.Content.CollectionPreview.Image(it.logoUrl.orEmpty()) }
|
||||
.plus(WalletNFTItemUM.Content.CollectionPreview.More)
|
||||
.toPersistentList()
|
||||
} else {
|
||||
collections
|
||||
.take(NFT_COLLECTIONS_MAX_PREVIEWS_COUNT)
|
||||
.map { WalletNFTItemUM.Content.CollectionPreview.Image(it.logoUrl.orEmpty()) }
|
||||
.toPersistentList()
|
||||
},
|
||||
collectionsCount = collections.size,
|
||||
assetsCount = collections
|
||||
.sumOf { it.count },
|
||||
isFlickering = isFlickering,
|
||||
)
|
||||
}
|
||||
|
||||
private fun allCollectionsFailed() = nftCollections.all {
|
||||
it.content is NFTCollections.Content.Error
|
||||
}
|
||||
|
||||
private fun anyCollectionFailed() = nftCollections.any {
|
||||
it.content is NFTCollections.Content.Error
|
||||
}
|
||||
|
||||
private fun allLoadedCollectionsEmpty() = nftCollections
|
||||
.map { it.content }
|
||||
.filterIsInstance<NFTCollections.Content.Collections>()
|
||||
.all { it.collections.isNullOrEmpty() }
|
||||
|
||||
private fun allCollectionsLoaded() = nftCollections.all {
|
||||
val content = it.content
|
||||
content is NFTCollections.Content.Collections &&
|
||||
content.source != StatusSource.CACHE
|
||||
}
|
||||
|
||||
private fun allCollectionsEmpty() = nftCollections.all {
|
||||
val content = it.content
|
||||
content is NFTCollections.Content.Collections &&
|
||||
content.collections.isNullOrEmpty()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val NFT_COLLECTIONS_MAX_PREVIEWS_COUNT = 4
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers
|
|||
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.BalancesAndLimitsBlockState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.DepositButtonState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.*
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.mutate
|
||||
|
|
@ -29,8 +28,8 @@ internal class SetRefreshStateTransformer(
|
|||
}
|
||||
is WalletState.Visa.Content -> {
|
||||
prevState.copy(
|
||||
buttons = prevState.buttons.toUpdatedState(),
|
||||
pullToRefreshConfig = prevState.pullToRefreshConfig.toUpdatedState(isRefreshing),
|
||||
depositButtonState = prevState.depositButtonState.toUpdatedState(isRefreshing),
|
||||
balancesAndLimitBlockState = prevState.balancesAndLimitBlockState.toUpdatedState(isRefreshing),
|
||||
)
|
||||
}
|
||||
|
|
@ -75,10 +74,6 @@ internal class SetRefreshStateTransformer(
|
|||
}
|
||||
}
|
||||
|
||||
private fun DepositButtonState.toUpdatedState(isRefreshing: Boolean): DepositButtonState {
|
||||
return copy(isEnabled = !isRefreshing)
|
||||
}
|
||||
|
||||
private fun BalancesAndLimitsBlockState.toUpdatedState(isRefreshing: Boolean): BalancesAndLimitsBlockState {
|
||||
return when (this) {
|
||||
is BalancesAndLimitsBlockState.Content -> copy(isEnabled = !isRefreshing)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import com.tangem.core.ui.format.bigdecimal.crypto
|
|||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.domain.common.util.getCardsCount
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.visa.exception.RefreshTokenExpiredException
|
||||
import com.tangem.domain.visa.model.VisaCurrency
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
|
|
@ -15,11 +16,14 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAdditiona
|
|||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletManageButton
|
||||
import com.tangem.utils.extensions.isZero
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import org.joda.time.DateTime
|
||||
import org.joda.time.Days
|
||||
|
||||
internal class SetBalancesAndLimitsTransformer(
|
||||
internal class SetVisaInfoTransformer(
|
||||
private val userWallet: UserWallet,
|
||||
private val maybeVisaCurrency: Either<Throwable, VisaCurrency>,
|
||||
private val clickIntents: WalletClickIntents,
|
||||
|
|
@ -35,15 +39,15 @@ internal class SetBalancesAndLimitsTransformer(
|
|||
}
|
||||
|
||||
return prevState.copy(
|
||||
buttons = createVisaButtonsDimmed(),
|
||||
walletCardState = getErrorWalletCardState(prevState.walletCardState),
|
||||
depositButtonState = prevState.depositButtonState.copy(isEnabled = false),
|
||||
balancesAndLimitBlockState = BalancesAndLimitsBlockState.Error,
|
||||
)
|
||||
}
|
||||
|
||||
return prevState.copy(
|
||||
buttons = createVisaButtons(visaCurrency = visaCurrency),
|
||||
walletCardState = getContentWalletCardState(prevState.walletCardState, visaCurrency),
|
||||
depositButtonState = prevState.depositButtonState.copy(isEnabled = true),
|
||||
balancesAndLimitBlockState = getContentBlockState(visaCurrency),
|
||||
)
|
||||
}
|
||||
|
|
@ -115,4 +119,43 @@ internal class SetBalancesAndLimitsTransformer(
|
|||
onUnlockVisaAccessNotificationClick = clickIntents::onUnlockVisaAccessClick,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createVisaButtonsDimmed(): PersistentList<WalletManageButton> {
|
||||
return persistentListOf(
|
||||
WalletManageButton.Receive(enabled = true, dimContent = true, onClick = {}, onLongClick = null),
|
||||
WalletManageButton.Buy(enabled = true, dimContent = true, onClick = {}),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createVisaButtons(visaCurrency: VisaCurrency): PersistentList<WalletManageButton> {
|
||||
// [Second Visa Iteration] Make VisaCurrency contain CryptoCurrencyStatus
|
||||
val cryptoCurrencyStatus = CryptoCurrencyStatus(
|
||||
currency = visaCurrency.cryptoCurrency,
|
||||
value = CryptoCurrencyStatus.Loaded(
|
||||
amount = visaCurrency.balances.available,
|
||||
fiatAmount = visaCurrency.balances.available.multiply(visaCurrency.fiatRate),
|
||||
fiatRate = visaCurrency.fiatRate,
|
||||
priceChange = visaCurrency.priceChange,
|
||||
yieldBalance = null,
|
||||
hasCurrentNetworkTransactions = false,
|
||||
pendingTransactions = emptySet(),
|
||||
networkAddress = visaCurrency.paymentAccountAddress,
|
||||
sources = CryptoCurrencyStatus.Sources(),
|
||||
),
|
||||
)
|
||||
|
||||
return return persistentListOf(
|
||||
WalletManageButton.Receive(
|
||||
enabled = true,
|
||||
dimContent = false,
|
||||
onClick = { clickIntents.onReceiveClick(cryptoCurrencyStatus = cryptoCurrencyStatus) },
|
||||
onLongClick = { clickIntents.onCopyAddressLongClick(cryptoCurrencyStatus = cryptoCurrencyStatus) },
|
||||
),
|
||||
WalletManageButton.Buy(
|
||||
enabled = true,
|
||||
dimContent = false,
|
||||
onClick = { clickIntents.onMultiWalletBuyClick(userWalletId = userWallet.walletId) },
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -46,7 +46,9 @@ internal class UnlockWalletTransformer(
|
|||
is WalletState.MultiCurrency.Locked,
|
||||
is WalletState.SingleCurrency.Locked,
|
||||
is WalletState.Visa.Locked,
|
||||
-> walletLoadingStateFactory.create(userWallet = unlockedWallet)
|
||||
-> walletLoadingStateFactory.create(
|
||||
userWallet = unlockedWallet,
|
||||
)
|
||||
is WalletState.MultiCurrency.Content,
|
||||
is WalletState.SingleCurrency.Content,
|
||||
is WalletState.Visa.Content,
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.utils
|
||||
|
||||
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
|
||||
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
|
||||
import com.tangem.core.ui.components.transactions.state.TxHistoryState
|
||||
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.*
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
|
@ -41,6 +41,7 @@ internal class WalletLoadingStateFactory(
|
|||
warnings = persistentListOf(),
|
||||
bottomSheetConfig = null,
|
||||
tokensListState = WalletTokensListState.ContentState.Loading,
|
||||
nftState = WalletNFTItemUM.Hidden,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -67,7 +68,7 @@ internal class WalletLoadingStateFactory(
|
|||
return WalletState.Visa.Content(
|
||||
pullToRefreshConfig = createPullToRefreshConfig(),
|
||||
walletCardState = userWallet.toLoadingWalletCardState(),
|
||||
buttons = createMultiWalletActions(userWallet),
|
||||
buttons = createVisaDimmedButtons(),
|
||||
warnings = persistentListOf(),
|
||||
bottomSheetConfig = null,
|
||||
balancesAndLimitBlockState = BalancesAndLimitsBlockState.Loading,
|
||||
|
|
@ -76,12 +77,14 @@ internal class WalletLoadingStateFactory(
|
|||
value = TxHistoryState.getDefaultLoadingTransactions(clickIntents::onExploreClick),
|
||||
),
|
||||
),
|
||||
depositButtonState = DepositButtonState(isEnabled = false, clickIntents::onDepositClick),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createPullToRefreshConfig(): PullToRefreshConfig {
|
||||
return PullToRefreshConfig(onRefresh = { clickIntents.onRefreshSwipe(it.value) }, isRefreshing = false)
|
||||
return PullToRefreshConfig(
|
||||
onRefresh = { clickIntents.onRefreshSwipe(it.value) },
|
||||
isRefreshing = false,
|
||||
)
|
||||
}
|
||||
|
||||
private fun UserWallet.toLoadingWalletCardState(): WalletCardState {
|
||||
|
|
@ -117,6 +120,13 @@ internal class WalletLoadingStateFactory(
|
|||
)
|
||||
}
|
||||
|
||||
private fun createVisaDimmedButtons(): PersistentList<WalletManageButton> {
|
||||
return persistentListOf(
|
||||
WalletManageButton.Receive(enabled = true, dimContent = true, onClick = {}, onLongClick = null),
|
||||
WalletManageButton.Buy(enabled = true, dimContent = true, onClick = {}),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createDimmedButtons(): PersistentList<WalletManageButton> {
|
||||
return persistentListOf(
|
||||
WalletManageButton.Receive(enabled = true, dimContent = true, onClick = {}, onLongClick = null),
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import com.tangem.domain.visa.model.VisaTxHistoryItem
|
|||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetBalancesAndLimitsTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetVisaInfoTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTxHistoryCountTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTxHistoryItemsErrorTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTxHistoryItemsTransformer
|
||||
|
|
@ -62,7 +62,7 @@ internal class VisaWalletSubscriber(
|
|||
|
||||
private fun setLoadedCurrencyState(maybeCurrency: Either<Throwable, VisaCurrency>) {
|
||||
stateController.update(
|
||||
SetBalancesAndLimitsTransformer(
|
||||
SetVisaInfoTransformer(
|
||||
userWallet = userWallet,
|
||||
maybeVisaCurrency = maybeCurrency,
|
||||
clickIntents = clickIntents,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.subscribers
|
||||
|
||||
import com.tangem.domain.nft.GetNFTCollectionsUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.RemoveNFTCollectionsTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetNFTCollectionsTransformer
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
internal class WalletNFTListSubscriber(
|
||||
private val userWallet: UserWallet,
|
||||
private val stateHolder: WalletStateController,
|
||||
private val walletsRepository: WalletsRepository,
|
||||
private val getNFTCollectionsUseCase: GetNFTCollectionsUseCase,
|
||||
clickIntents: WalletClickIntents,
|
||||
) : WalletSubscriber() {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override fun create(coroutineScope: CoroutineScope): Flow<*> = walletsRepository
|
||||
.nftEnabledStatus(userWallet.walletId)
|
||||
.distinctUntilChanged()
|
||||
.flatMapLatest { nftEnabled ->
|
||||
// if NFT is enabled for this wallet, then start observing changes from store and apply transformer if need
|
||||
if (nftEnabled) {
|
||||
getNFTCollectionsUseCase
|
||||
.launch(userWallet.walletId)
|
||||
.shareIn(
|
||||
scope = coroutineScope,
|
||||
started = SharingStarted.WhileSubscribed(),
|
||||
replay = 1,
|
||||
)
|
||||
.onEach {
|
||||
stateHolder.update(
|
||||
SetNFTCollectionsTransformer(userWallet.walletId, it),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// otherwise, hide NFT from wallet
|
||||
stateHolder.update(
|
||||
RemoveNFTCollectionsTransformer(userWallet.walletId),
|
||||
)
|
||||
emptyFlow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -80,12 +80,12 @@ import com.tangem.feature.wallet.presentation.wallet.ui.components.TokenActionsB
|
|||
import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletsList
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.common.*
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.common.actions
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency.nftCollections
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency.organizeTokensButton
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.marketPriceBlock
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.BalancesAndLimitsBottomSheet
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.VisaTxDetailsBottomSheet
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.balancesAndLimitsBlock
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.depositButton
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.utils.changeWalletAnimator
|
||||
import com.tangem.features.markets.entry.BottomSheetState
|
||||
import com.tangem.features.markets.entry.MarketsEntryComponent
|
||||
|
|
@ -230,11 +230,6 @@ private fun WalletContent(
|
|||
}
|
||||
|
||||
(selectedWallet as? WalletState.Visa.Content)?.let {
|
||||
depositButton(
|
||||
modifier = itemModifier.fillMaxWidth(),
|
||||
state = it.depositButtonState,
|
||||
)
|
||||
|
||||
balancesAndLimitsBlock(
|
||||
modifier = itemModifier,
|
||||
state = it.balancesAndLimitBlockState,
|
||||
|
|
@ -248,6 +243,8 @@ private fun WalletContent(
|
|||
modifier = movableItemModifier,
|
||||
)
|
||||
|
||||
nftCollections(state = selectedWallet, itemModifier = itemModifier)
|
||||
|
||||
organizeTokens(state = selectedWallet, itemModifier = itemModifier)
|
||||
}
|
||||
|
||||
|
|
@ -706,6 +703,16 @@ internal fun LazyListScope.organizeTokens(state: WalletState, itemModifier: Modi
|
|||
}
|
||||
}
|
||||
|
||||
internal fun LazyListScope.nftCollections(state: WalletState, itemModifier: Modifier) {
|
||||
(state as? WalletState.MultiCurrency)?.let {
|
||||
nftCollections(
|
||||
modifier = itemModifier,
|
||||
state = it.nftState,
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ShowBottomSheet(bottomSheetConfig: TangemBottomSheetConfig?) {
|
||||
if (bottomSheetConfig != null) {
|
||||
|
|
|
|||
|
|
@ -131,7 +131,20 @@ private fun WalletNFTItemFailed(modifier: Modifier = Modifier) {
|
|||
RowContentContainer(
|
||||
modifier = modifier,
|
||||
icon = {
|
||||
CollectionsPreviewsPlaceholder()
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size36)
|
||||
.clip(RoundedCornerShape(TangemTheme.dimens.radius8))
|
||||
.background(TangemTheme.colors.field.primary),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size20),
|
||||
painter = painterResource(R.drawable.ic_error_sync_24),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
},
|
||||
text = {
|
||||
Text(
|
||||
|
|
@ -158,7 +171,12 @@ private fun WalletNFTItemLoading(modifier: Modifier = Modifier) {
|
|||
RowContentContainer(
|
||||
modifier = modifier,
|
||||
icon = {
|
||||
CollectionsPreviewsPlaceholder()
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size36)
|
||||
.clip(RoundedCornerShape(TangemTheme.dimens.radius8))
|
||||
.background(TangemTheme.colors.field.primary),
|
||||
)
|
||||
},
|
||||
text = {
|
||||
TextShimmer(
|
||||
|
|
@ -178,16 +196,6 @@ private fun WalletNFTItemLoading(modifier: Modifier = Modifier) {
|
|||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CollectionsPreviewsPlaceholder(modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.size(TangemTheme.dimens.size36)
|
||||
.clip(RoundedCornerShape(TangemTheme.dimens.radius8))
|
||||
.background(TangemTheme.colors.field.primary),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Suppress("MagicNumber")
|
||||
private fun BoxScope.CollectionsPreviews(previews: ImmutableList<CollectionPreview>, modifier: Modifier = Modifier) {
|
||||
|
|
@ -207,7 +215,7 @@ private fun BoxScope.CollectionsPreviews(previews: ImmutableList<CollectionPrevi
|
|||
is CollectionPreview.Image -> {
|
||||
SubcomposeAsyncImage(
|
||||
modifier = modifier,
|
||||
model = s,
|
||||
model = s.url,
|
||||
loading = {
|
||||
RectangleShimmer(radius = 0.dp)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency
|
||||
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNFTItemUM
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletNFTItem
|
||||
|
||||
private const val NFT_COLLECTIONS_CONTENT_TYPE = "NFTCollections"
|
||||
|
||||
internal fun LazyListScope.nftCollections(state: WalletNFTItemUM, onClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
item(key = NFT_COLLECTIONS_CONTENT_TYPE, contentType = NFT_COLLECTIONS_CONTENT_TYPE) {
|
||||
WalletNFTItem(
|
||||
modifier = modifier,
|
||||
state = state,
|
||||
onClick = onClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.ui.components.visa
|
||||
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.ui.components.PrimaryButtonIconStart
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.DepositButtonState
|
||||
|
||||
private const val DEPOSIT_BUTTON_CONTENT_TYPE = "DepositButton"
|
||||
|
||||
internal fun LazyListScope.depositButton(state: DepositButtonState, modifier: Modifier = Modifier) {
|
||||
item(key = DEPOSIT_BUTTON_CONTENT_TYPE, contentType = DEPOSIT_BUTTON_CONTENT_TYPE) {
|
||||
PrimaryButtonIconStart(
|
||||
modifier = modifier,
|
||||
text = "Deposit",
|
||||
iconResId = R.drawable.ic_arrow_down_24,
|
||||
onClick = state.onClick,
|
||||
enabled = state.isEnabled,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
[versions]
|
||||
tangemBlockchainSdk = "develop-977"
|
||||
tangemBlockchainSdk = "develop-984"
|
||||
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
|
||||
tangemCardSdk = "develop-441"
|
||||
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ dependencies {
|
|||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.visa.models)
|
||||
|
||||
implementation(projects.core.configToggles)
|
||||
implementation(projects.core.res)
|
||||
|
||||
/** Tangem libraries */
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.sdk.api.di
|
||||
|
||||
import com.tangem.sdk.api.featuretoggles.CardSdkFeatureToggles
|
||||
import com.tangem.sdk.api.featuretoggles.DefaultCardSdkFeatureToggles
|
||||
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 CardSdkFeatureTogglesModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindCardSdkFeatureToggles(impl: DefaultCardSdkFeatureToggles): CardSdkFeatureToggles
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.sdk.api.di
|
||||
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object CardSdkModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideOnlineCardVerifier(): OnlineCardVerifier = OnlineCardVerifier()
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.sdk.api.featuretoggles
|
||||
|
||||
/**
|
||||
* Card SDK feature toggles
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface CardSdkFeatureToggles {
|
||||
|
||||
val isNewAttestationEnabled: Boolean
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.sdk.api.featuretoggles
|
||||
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultCardSdkFeatureToggles @Inject constructor(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : CardSdkFeatureToggles {
|
||||
|
||||
override val isNewAttestationEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(name = "NEW_ATTESTATION_ENABLED")
|
||||
}
|
||||
|
|
@ -81,7 +81,14 @@ internal class DefaultVisaContractInfoProvider(
|
|||
{ fetchBalances(paymentAccount, paymentToken) },
|
||||
{ fetchLimits(paymentAccount, paymentToken, walletAddress) },
|
||||
{ token, balances, (oldLimit, newLimit, changeDate) ->
|
||||
VisaContractInfo(token, balances, oldLimit, newLimit, changeDate)
|
||||
VisaContractInfo(
|
||||
token = token,
|
||||
balances = balances,
|
||||
oldLimits = oldLimit,
|
||||
newLimits = newLimit,
|
||||
paymentAccountAddress = paymentAccount.contractAddress,
|
||||
limitsChangeDate = changeDate,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ data class VisaContractInfo(
|
|||
val balances: Balances,
|
||||
val oldLimits: Limits,
|
||||
val newLimits: Limits,
|
||||
val paymentAccountAddress: String,
|
||||
val limitsChangeDate: Instant,
|
||||
) {
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue