diff --git a/app/src/main/java/com/tangem/tap/di/domain/ManageTokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/ManageTokensDomainModule.kt index b55ef9a8f7..e32ac8b237 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/ManageTokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/ManageTokensDomainModule.kt @@ -10,7 +10,6 @@ import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher import com.tangem.domain.staking.repositories.StakingRepository import com.tangem.domain.tokens.TokensFeatureToggles import com.tangem.domain.tokens.repository.CurrenciesRepository -import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -70,7 +69,6 @@ internal object ManageTokensDomainModule { customTokensRepository: CustomTokensRepository, walletManagersFacade: WalletManagersFacade, currenciesRepository: CurrenciesRepository, - networksRepository: NetworksRepository, derivationsRepository: DerivationsRepository, stakingRepository: StakingRepository, quotesRepository: QuotesRepository, @@ -83,7 +81,6 @@ internal object ManageTokensDomainModule { customTokensRepository = customTokensRepository, walletManagersFacade = walletManagersFacade, currenciesRepository = currenciesRepository, - networksRepository = networksRepository, derivationsRepository = derivationsRepository, stakingRepository = stakingRepository, quotesRepository = quotesRepository, diff --git a/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt index 7666729a36..9338ab2914 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt @@ -11,7 +11,6 @@ import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher import com.tangem.domain.staking.repositories.StakingRepository import com.tangem.domain.tokens.TokensFeatureToggles import com.tangem.domain.tokens.repository.CurrenciesRepository -import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.wallets.legacy.UserWalletsListManager import dagger.Module @@ -70,7 +69,6 @@ object MarketsDomainModule { derivationsRepository: DerivationsRepository, marketsTokenRepository: MarketsTokenRepository, currenciesRepository: CurrenciesRepository, - networksRepository: NetworksRepository, stakingRepository: StakingRepository, quotesRepository: QuotesRepository, multiNetworkStatusFetcher: MultiNetworkStatusFetcher, @@ -82,7 +80,6 @@ object MarketsDomainModule { derivationsRepository = derivationsRepository, marketsTokenRepository = marketsTokenRepository, currenciesRepository = currenciesRepository, - networksRepository = networksRepository, stakingRepository = stakingRepository, quotesRepository = quotesRepository, multiNetworkStatusFetcher = multiNetworkStatusFetcher, diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index ff23a2c9e2..d6e0a771ad 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -37,7 +37,6 @@ internal object TokensDomainModule { @Singleton fun provideAddCryptoCurrenciesUseCase( currenciesRepository: CurrenciesRepository, - networksRepository: NetworksRepository, stakingRepository: StakingRepository, quotesRepository: QuotesRepository, multiNetworkStatusFetcher: MultiNetworkStatusFetcher, @@ -47,7 +46,6 @@ internal object TokensDomainModule { ): AddCryptoCurrenciesUseCase { return AddCryptoCurrenciesUseCase( currenciesRepository = currenciesRepository, - networksRepository = networksRepository, stakingRepository = stakingRepository, quotesRepository = quotesRepository, multiNetworkStatusFetcher = multiNetworkStatusFetcher, @@ -62,7 +60,6 @@ internal object TokensDomainModule { fun provideFetchTokenListUseCase( currenciesRepository: CurrenciesRepository, quotesRepository: QuotesRepository, - networksRepository: NetworksRepository, stakingRepository: StakingRepository, multiNetworkStatusFetcher: MultiNetworkStatusFetcher, multiQuoteFetcher: MultiQuoteFetcher, @@ -71,7 +68,6 @@ internal object TokensDomainModule { ): FetchTokenListUseCase { return FetchTokenListUseCase( currenciesRepository = currenciesRepository, - networksRepository = networksRepository, quotesRepository = quotesRepository, stakingRepository = stakingRepository, multiNetworkStatusFetcher = multiNetworkStatusFetcher, @@ -121,8 +117,8 @@ internal object TokensDomainModule { fun provideGetCurrencyUseCase( baseCurrencyStatusOperations: BaseCurrencyStatusOperations, dispatchers: CoroutineDispatcherProvider, - ): GetCurrencyStatusUpdatesUseCase { - return GetCurrencyStatusUpdatesUseCase( + ): GetSingleCryptoCurrencyStatusUseCase { + return GetSingleCryptoCurrencyStatusUseCase( currencyStatusOperations = baseCurrencyStatusOperations, dispatchers = dispatchers, ) @@ -162,24 +158,11 @@ internal object TokensDomainModule { ) } - @Provides - @Singleton - fun provideGetPrimaryCurrencyUseCase( - currencyStatusOperations: BaseCurrencyStatusOperations, - dispatchers: CoroutineDispatcherProvider, - ): GetPrimaryCurrencyStatusUpdatesUseCase { - return GetPrimaryCurrencyStatusUpdatesUseCase( - currencyStatusOperations = currencyStatusOperations, - dispatchers = dispatchers, - ) - } - @Provides @Singleton fun provideFetchCurrencyStatusUseCase( currenciesRepository: CurrenciesRepository, quotesRepository: QuotesRepository, - networksRepository: NetworksRepository, stakingRepository: StakingRepository, singleNetworkStatusFetcher: SingleNetworkStatusFetcher, multiQuoteFetcher: MultiQuoteFetcher, @@ -188,7 +171,6 @@ internal object TokensDomainModule { ): FetchCurrencyStatusUseCase { return FetchCurrencyStatusUseCase( currenciesRepository = currenciesRepository, - networksRepository = networksRepository, quotesRepository = quotesRepository, stakingRepository = stakingRepository, singleNetworkStatusFetcher = singleNetworkStatusFetcher, @@ -203,7 +185,6 @@ internal object TokensDomainModule { fun provideFetchCardTokenListUseCase( currenciesRepository: CurrenciesRepository, quotesRepository: QuotesRepository, - networksRepository: NetworksRepository, stakingRepository: StakingRepository, multiNetworkStatusFetcher: MultiNetworkStatusFetcher, multiQuoteFetcher: MultiQuoteFetcher, @@ -212,7 +193,6 @@ internal object TokensDomainModule { ): FetchCardTokenListUseCase { return FetchCardTokenListUseCase( currenciesRepository = currenciesRepository, - networksRepository = networksRepository, quotesRepository = quotesRepository, stakingRepository = stakingRepository, multiNetworkStatusFetcher = multiNetworkStatusFetcher, @@ -222,14 +202,6 @@ internal object TokensDomainModule { ) } - @Provides - @Singleton - fun providesGetCryptoCurrencyStatusSyncUseCase( - currencyStatusOperations: BaseCurrencyStatusOperations, - ): GetCryptoCurrencyStatusSyncUseCase { - return GetCryptoCurrencyStatusSyncUseCase(currencyStatusOperations) - } - @Provides @Singleton fun provideGetCryptoCurrencyUseCase(currenciesRepository: CurrenciesRepository): GetCryptoCurrencyUseCase { @@ -328,14 +300,10 @@ internal object TokensDomainModule { @Provides @Singleton fun provideUpdateDelayedCurrencyStatusUseCase( - networksRepository: NetworksRepository, singleNetworkStatusFetcher: SingleNetworkStatusFetcher, - tokensFeatureToggles: TokensFeatureToggles, ): UpdateDelayedNetworkStatusUseCase { return UpdateDelayedNetworkStatusUseCase( - networksRepository = networksRepository, singleNetworkStatusFetcher = singleNetworkStatusFetcher, - tokensFeatureToggles = tokensFeatureToggles, ) } @@ -413,7 +381,6 @@ internal object TokensDomainModule { currenciesRepository: CurrenciesRepository, quotesRepository: QuotesRepository, quotesRepositoryV2: QuotesRepositoryV2, - networksRepository: NetworksRepository, stakingRepository: StakingRepository, singleNetworkStatusSupplier: SingleNetworkStatusSupplier, multiNetworkStatusSupplier: MultiNetworkStatusSupplier, @@ -427,7 +394,6 @@ internal object TokensDomainModule { currenciesRepository = currenciesRepository, quotesRepository = quotesRepository, quotesRepositoryV2 = quotesRepositoryV2, - networksRepository = networksRepository, stakingRepository = stakingRepository, singleNetworkStatusSupplier = singleNetworkStatusSupplier, multiNetworkStatusSupplier = multiNetworkStatusSupplier, @@ -447,7 +413,6 @@ internal object TokensDomainModule { currenciesRepository: CurrenciesRepository, quotesRepository: QuotesRepository, quotesRepositoryV2: QuotesRepositoryV2, - networksRepository: NetworksRepository, stakingRepository: StakingRepository, singleNetworkStatusSupplier: SingleNetworkStatusSupplier, multiNetworkStatusSupplier: MultiNetworkStatusSupplier, @@ -461,7 +426,6 @@ internal object TokensDomainModule { currenciesRepository = currenciesRepository, quotesRepository = quotesRepository, quotesRepositoryV2 = quotesRepositoryV2, - networksRepository = networksRepository, stakingRepository = stakingRepository, singleNetworkStatusSupplier = singleNetworkStatusSupplier, multiNetworkStatusSupplier = multiNetworkStatusSupplier, diff --git a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt index b0b9e8f8ad..ea48365cb7 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt @@ -4,9 +4,7 @@ import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.demo.DemoConfig import com.tangem.domain.networks.single.SingleNetworkStatusFetcher import com.tangem.domain.networks.single.SingleNetworkStatusSupplier -import com.tangem.domain.tokens.TokensFeatureToggles import com.tangem.domain.tokens.repository.CurrenciesRepository -import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.transaction.FeeRepository import com.tangem.domain.transaction.TransactionRepository import com.tangem.domain.transaction.usecase.* @@ -43,7 +41,6 @@ internal object TransactionDomainModule { transactionRepository: TransactionRepository, walletManagersFacade: WalletManagersFacade, singleNetworkStatusFetcher: SingleNetworkStatusFetcher, - tokensFeatureToggles: TokensFeatureToggles, ): SendTransactionUseCase { return SendTransactionUseCase( demoConfig = DemoConfig(), @@ -51,7 +48,6 @@ internal object TransactionDomainModule { transactionRepository = transactionRepository, walletManagersFacade = walletManagersFacade, singleNetworkStatusFetcher = singleNetworkStatusFetcher, - tokensFeatureToggles = tokensFeatureToggles, ) } @@ -61,17 +57,13 @@ internal object TransactionDomainModule { cardSdkConfigRepository: CardSdkConfigRepository, walletManagersFacade: WalletManagersFacade, currenciesRepository: CurrenciesRepository, - networksRepository: NetworksRepository, singleNetworkStatusSupplier: SingleNetworkStatusSupplier, - tokensFeatureToggles: TokensFeatureToggles, ): AssociateAssetUseCase { return AssociateAssetUseCase( cardSdkConfigRepository = cardSdkConfigRepository, walletManagersFacade = walletManagersFacade, currenciesRepository = currenciesRepository, - networksRepository = networksRepository, singleNetworkStatusSupplier = singleNetworkStatusSupplier, - tokensFeatureToggles = tokensFeatureToggles, ) } diff --git a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt index 4096521b75..b153b0e0de 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt @@ -6,6 +6,8 @@ import com.tangem.domain.transaction.usecase.ParseSharedAddressUseCase import com.tangem.domain.transaction.usecase.ValidateWalletAddressUseCase import com.tangem.domain.transaction.usecase.ValidateWalletMemoUseCase import com.tangem.domain.walletmanager.WalletManagersFacade +import com.tangem.domain.wallets.delegate.DefaultUserWalletsSyncDelegate +import com.tangem.domain.wallets.delegate.UserWalletsSyncDelegate import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.repository.WalletNamesMigrationRepository import com.tangem.domain.wallets.repository.WalletsRepository @@ -28,6 +30,17 @@ import javax.inject.Singleton @InstallIn(SingletonComponent::class) internal object WalletsDomainModule { + @Provides + fun providesUserWalletsSyncDelegate( + userWalletsListManager: UserWalletsListManager, + dispatchers: CoroutineDispatcherProvider, + ): UserWalletsSyncDelegate { + return DefaultUserWalletsSyncDelegate( + userWalletsListManager = userWalletsListManager, + dispatchers = dispatchers, + ) + } + @Provides @Singleton fun providesGetWalletsUseCase(userWalletsListManager: UserWalletsListManager): GetWalletsUseCase { @@ -102,10 +115,13 @@ internal object WalletsDomainModule { @Provides @Singleton fun providesRenameWalletUseCase( - userWalletsListManager: UserWalletsListManager, - dispatchers: CoroutineDispatcherProvider, + walletsRepository: WalletsRepository, + userWalletsSyncDelegate: UserWalletsSyncDelegate, ): RenameWalletUseCase { - return RenameWalletUseCase(userWalletsListManager = userWalletsListManager, dispatchers = dispatchers) + return RenameWalletUseCase( + walletsRepository = walletsRepository, + userWalletsSyncDelegate = userWalletsSyncDelegate, + ) } @Provides @@ -197,4 +213,16 @@ internal object WalletsDomainModule { nftFeatureToggles = nftFeatureToggles, ) } + + @Provides + @Singleton + fun providesUpdateRemoteWalletsInfoUseCase( + walletsRepository: WalletsRepository, + userWalletsSyncDelegate: UserWalletsSyncDelegate, + ): UpdateRemoteWalletsInfoUseCase { + return UpdateRemoteWalletsInfoUseCase( + walletsRepository = walletsRepository, + userWalletsSyncDelegate = userWalletsSyncDelegate, + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/DefaultTokensFeatureToggles.kt b/app/src/main/java/com/tangem/tap/domain/tokens/DefaultTokensFeatureToggles.kt index adc8e80741..bfb76fbf06 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/DefaultTokensFeatureToggles.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/DefaultTokensFeatureToggles.kt @@ -7,9 +7,6 @@ internal class DefaultTokensFeatureToggles( private val featureTogglesManager: FeatureTogglesManager, ) : TokensFeatureToggles { - override val isNetworksLoadingRefactoringEnabled: Boolean - get() = featureTogglesManager.isFeatureEnabled(name = "NETWORKS_LOADING_REFACTORING_ENABLED") - override val isQuotesLoadingRefactoringEnabled: Boolean get() = featureTogglesManager.isFeatureEnabled(name = "QUOTES_LOADING_REFACTORING_ENABLED") diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultLegacyWalletConnectRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultLegacyWalletConnectRepository.kt index 689db8c1c0..0ac30a5cb0 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultLegacyWalletConnectRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultLegacyWalletConnectRepository.kt @@ -13,6 +13,7 @@ import com.tangem.domain.walletconnect.WcPairService import com.tangem.domain.walletconnect.model.WcPairRequest import com.tangem.domain.walletconnect.model.legacy.Account import com.tangem.domain.walletconnect.usecase.initialize.WcInitializeUseCase +import com.tangem.domain.wallets.models.UserWalletId import com.tangem.features.walletconnect.components.WalletConnectFeatureToggles import com.tangem.tap.common.analytics.events.WalletConnect import com.tangem.tap.domain.walletconnect2.app.TangemWcBlockchainHelper @@ -64,14 +65,18 @@ internal class DefaultLegacyWalletConnectRepositoryFacade constructor( if (isNewWc) stub.updateSessions() else legacy.updateSessions() } - override fun pair(uri: String, source: SourceType) { + override fun pair(userWalletId: UserWalletId, uri: String, source: SourceType) { val src = when (source) { SourceType.QR -> WcPairRequest.Source.QR SourceType.DEEPLINK -> WcPairRequest.Source.DEEPLINK SourceType.CLIPBOARD -> WcPairRequest.Source.CLIPBOARD SourceType.ETC -> WcPairRequest.Source.ETC } - if (isNewWc) wcPairService.pair(WcPairRequest(uri, src)) else legacy.pair(uri, source) + if (isNewWc) { + wcPairService.pair(WcPairRequest(uri = uri, source = src, userWalletId = userWalletId)) + } else { + legacy.pair(userWalletId = userWalletId, uri = uri, source = source) + } } override fun disconnect(topic: String) { @@ -110,7 +115,7 @@ internal class LegacyWalletConnectRepositoryStub : LegacyWalletConnectRepository override fun updateSessions() = Unit - override fun pair(uri: String, source: SourceType) = Unit + override fun pair(userWalletId: UserWalletId, uri: String, source: SourceType) = Unit override fun disconnect(topic: String) = Unit @@ -405,7 +410,7 @@ internal class DefaultLegacyWalletConnectRepository( this.userNamespaces = userNamespaces } - override fun pair(uri: String, source: SourceType) { + override fun pair(userWalletId: UserWalletId, uri: String, source: SourceType) { analyticsHandler.send(WalletConnect.NewSessionInitiated(source = source)) WalletKit.pair( params = Wallet.Params.Pair(uri), diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/LegacyWalletConnectRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/LegacyWalletConnectRepository.kt index 59d5954e00..c0e22a3b7d 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/LegacyWalletConnectRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/LegacyWalletConnectRepository.kt @@ -1,6 +1,7 @@ package com.tangem.tap.domain.walletconnect2.domain import com.tangem.domain.walletconnect.model.legacy.Account +import com.tangem.domain.wallets.models.UserWalletId import com.tangem.tap.domain.walletconnect2.domain.models.* import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction.OpenSession.SourceType import kotlinx.coroutines.flow.Flow @@ -19,7 +20,7 @@ interface LegacyWalletConnectRepository { fun updateSessions() - fun pair(uri: String, source: SourceType) + fun pair(userWalletId: UserWalletId, uri: String, source: SourceType) fun disconnect(topic: String) diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt index d8d5a989ae..92418004d0 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt @@ -12,6 +12,7 @@ import com.tangem.domain.walletconnect.model.legacy.WalletConnectSessionsReposit import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.filterNotNull @@ -139,7 +140,11 @@ class WalletConnectInteractor( if (deeplinkStack.empty()) return val lastDeeplink = deeplinkStack.pop() val action = WalletConnectAction - .OpenSession(lastDeeplink, WalletConnectAction.OpenSession.SourceType.DEEPLINK) + .OpenSession( + wcUri = lastDeeplink, + source = WalletConnectAction.OpenSession.SourceType.DEEPLINK, + userWalletId = UserWalletId(userWalletId), + ) store.dispatchOnMain(action) }.onFailure { Timber.e("WC deeplink handling failed. $it") @@ -393,7 +398,11 @@ class WalletConnectInteractor( } if (isWalletConnectReadyForDeepLinks) { - val action = WalletConnectAction.OpenSession(deeplink, WalletConnectAction.OpenSession.SourceType.DEEPLINK) + val action = WalletConnectAction.OpenSession( + wcUri = deeplink, + source = WalletConnectAction.OpenSession.SourceType.DEEPLINK, + userWalletId = UserWalletId(userWalletId), + ) store.dispatchOnMain(action) } else { deeplinkStack.push(deeplink) diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt index 7391c77f5b..4288f711b9 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.details.redux.walletconnect +import com.tangem.domain.wallets.models.UserWalletId import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents @@ -14,6 +15,7 @@ sealed class WalletConnectAction : Action { data class OpenSession( val wcUri: String, val source: SourceType, + val userWalletId: UserWalletId, ) : WalletConnectAction() { enum class SourceType { QR, DEEPLINK, CLIPBOARD, ETC } } diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt index 6bbd548de4..7439241a50 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt @@ -63,7 +63,11 @@ class WalletConnectMiddleware { is WalletConnectAction.OpenSession -> { val index = action.wcUri.indexOf("@") when (action.wcUri[index + 1]) { - '2' -> walletConnectRepository.pair(uri = action.wcUri, source = action.source) + '2' -> walletConnectRepository.pair( + uri = action.wcUri, + source = action.source, + userWalletId = action.userWalletId, + ) '1' -> { store.dispatchOnMain(WalletConnectAction.UnsupportedDappRequest) store.dispatchOnMain( diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/DefaultWalletConnectComponent.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/DefaultWalletConnectComponent.kt index 73a0989519..0974e0db1b 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/DefaultWalletConnectComponent.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/DefaultWalletConnectComponent.kt @@ -22,10 +22,10 @@ import org.rekotlin.StoreSubscriber @Suppress("UnusedPrivateMember") internal class DefaultWalletConnectComponent @AssistedInject constructor( @Assisted appComponentContext: AppComponentContext, - @Assisted params: Unit, + @Assisted params: WalletConnectComponent.Params, ) : WalletConnectComponent, AppComponentContext by appComponentContext, StoreSubscriber { - private val model: WalletConnectModel = getOrCreateModel() + private val model: WalletConnectModel = getOrCreateModel(params) private var screenState: MutableState = mutableStateOf(model.updateState(store.state.walletConnectState)) @@ -65,6 +65,9 @@ internal class DefaultWalletConnectComponent @AssistedInject constructor( @AssistedFactory interface Factory : WalletConnectComponent.Factory { - override fun create(context: AppComponentContext, params: Unit): DefaultWalletConnectComponent + override fun create( + context: AppComponentContext, + params: WalletConnectComponent.Params, + ): DefaultWalletConnectComponent } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectModel.kt index 21deae59a7..762b7afb71 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectModel.kt @@ -4,10 +4,12 @@ import androidx.compose.runtime.Stable import arrow.core.getOrElse import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer import com.tangem.domain.qrscanning.models.SourceType import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState +import com.tangem.tap.features.details.ui.walletconnect.api.WalletConnectComponent import com.tangem.tap.store import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.collections.immutable.toImmutableList @@ -22,13 +24,22 @@ import javax.inject.Inject internal class WalletConnectModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, private val listenToQrScanningUseCase: ListenToQrScanningUseCase, + paramsContainer: ParamsContainer, ) : Model() { + private val params = paramsContainer.require() + init { modelScope.launch { listenToQrScanningUseCase(SourceType.WALLET_CONNECT) .getOrElse { emptyFlow() } - .map { WalletConnectAction.OpenSession(it, WalletConnectAction.OpenSession.SourceType.QR) } + .map { + WalletConnectAction.OpenSession( + wcUri = it, + source = WalletConnectAction.OpenSession.SourceType.QR, + userWalletId = params.userWalletId, + ) + } .collect { store.dispatch(it) } } } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/api/WalletConnectComponent.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/api/WalletConnectComponent.kt index 4e1f4ab3fb..6b9d8c4330 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/api/WalletConnectComponent.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/api/WalletConnectComponent.kt @@ -2,7 +2,9 @@ package com.tangem.tap.features.details.ui.walletconnect.api import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.domain.wallets.models.UserWalletId interface WalletConnectComponent : ComposableContentComponent { - interface Factory : ComponentFactory + data class Params(val userWalletId: UserWalletId) + interface Factory : ComponentFactory } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/auth/DefaultAuthProvider.kt b/app/src/main/java/com/tangem/tap/network/auth/DefaultAuthProvider.kt index c39150c2c2..be40dc6f87 100644 --- a/app/src/main/java/com/tangem/tap/network/auth/DefaultAuthProvider.kt +++ b/app/src/main/java/com/tangem/tap/network/auth/DefaultAuthProvider.kt @@ -13,4 +13,10 @@ internal class DefaultAuthProvider(private val userWalletsListManager: UserWalle override fun getCardId(): String { return userWalletsListManager.selectedUserWalletSync?.scanResponse?.card?.cardId ?: "" } + + override fun getCardsPublicKeys(): Map { + return userWalletsListManager.userWalletsSync.associate { + it.scanResponse.card.cardId to it.scanResponse.card.cardPublicKey.toHexString() + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index 0f2b7d5303..f1cd71bbe3 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -291,13 +291,13 @@ internal class ChildFactory @Inject constructor( if (walletConnectFeatureToggles.isRedesignedWalletConnectEnabled) { createComponentChild( context = context, - params = Unit, + params = RedesignedWalletConnectComponent.Params(route.userWalletId), componentFactory = redesignedWalletConnectComponentFactory, ) } else { createComponentChild( context = context, - params = Unit, + params = WalletConnectComponent.Params(route.userWalletId), componentFactory = walletConnectComponentFactory, ) } diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index 0a280474be..af27366ceb 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -122,7 +122,7 @@ sealed class AppRoute(val path: String) : Route { } @Serializable - data object WalletConnectSessions : AppRoute(path = "/wallet_connect_sessions") + data class WalletConnectSessions(val userWalletId: UserWalletId) : AppRoute(path = "/wallet_connect_sessions") @Serializable data class QrScanning(val source: Source) : AppRoute(path = "/$source/qr_scanning${source.path}") { diff --git a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json index 4a9a86f746..6a3b525e20 100644 --- a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json @@ -51,10 +51,6 @@ "name": "WALLET_CONNECT_REDESIGN_ENABLED", "version": "undefined" }, - { - "name": "NETWORKS_LOADING_REFACTORING_ENABLED", - "version": "5.23.0" - }, { "name": "QUOTES_LOADING_REFACTORING_ENABLED", "version": "5.24.0" diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/AuthProvider.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/AuthProvider.kt index a11b7bf574..7945fc59eb 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/AuthProvider.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/AuthProvider.kt @@ -11,4 +11,9 @@ interface AuthProvider { fun getCardPublicKey(): String fun getCardId(): String + + /** + * Returns map where keys(cardId) associated with cardPublicKey + */ + fun getCardsPublicKeys(): Map } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt index 7b474dd442..d4db8f1fa1 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt @@ -172,6 +172,9 @@ interface TangemTechApi { @GET("user-wallets/wallets/{wallet_id}") suspend fun getWalletById(@Path("wallet_id") walletId: String): ApiResponse + + @GET("user-wallets/wallets/by-app/{app_id}") + suspend fun getWallets(@Path("app_id") appId: String): ApiResponse> // endregion companion object { diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/CardInfoBody.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/CardInfoBody.kt new file mode 100644 index 0000000000..80454a733b --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/CardInfoBody.kt @@ -0,0 +1,10 @@ +package com.tangem.datasource.api.tangemTech.models + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class CardInfoBody( + @Json(name = "card_id") val cardId: String, + @Json(name = "card_public_key") val cardPublicKey: String, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/WalletIdBody.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/WalletIdBody.kt index dbadddd6fc..3fd5ff6196 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/WalletIdBody.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/WalletIdBody.kt @@ -6,4 +6,6 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class WalletIdBody( @Json(name = "id") val walletId: String, + @Json(name = "name") val name: String, + @Json(name = "cards") val cards: List, ) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/local/LocalTokenModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/local/LocalTokenModule.kt new file mode 100644 index 0000000000..1165379d12 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/di/local/LocalTokenModule.kt @@ -0,0 +1,21 @@ +package com.tangem.datasource.di.local + +import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.datasource.local.token.DefaultUserTokensResponseStore +import com.tangem.datasource.local.token.UserTokensResponseStore +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 LocalTokenModule { + + @Provides + @Singleton + fun provideUserTokensResponseStore(appPreferencesStore: AppPreferencesStore): UserTokensResponseStore { + return DefaultUserTokensResponseStore(appPreferencesStore = appPreferencesStore) + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/nft/DefaultNFTPersistenceStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/nft/DefaultNFTPersistenceStore.kt index a418b31a5d..42d11c055f 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/nft/DefaultNFTPersistenceStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/nft/DefaultNFTPersistenceStore.kt @@ -3,13 +3,14 @@ package com.tangem.datasource.local.nft import androidx.datastore.core.DataStore import com.tangem.blockchain.nft.models.NFTAsset import com.tangem.blockchain.nft.models.NFTCollection +import com.tangem.datasource.local.nft.custom.NFTPriceId import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map internal class DefaultNFTPersistenceStore( private val collectionsPersistenceStore: DataStore>, - private val pricesPersistenceStore: DataStore>, + private val pricesPersistenceStore: DataStore>, ) : NFTPersistenceStore { override fun getCollections(): Flow?> = collectionsPersistenceStore.data @@ -27,11 +28,12 @@ internal class DefaultNFTPersistenceStore( } override fun getSalePrice(assetId: NFTAsset.Identifier): Flow = pricesPersistenceStore.data - .map { it[assetId] } + .map { data -> data.associate { it.assetId to it.price }[assetId] } override suspend fun getSalePricesSync(): Map? = pricesPersistenceStore .data .firstOrNull() + ?.associate { it.assetId to it.price } override suspend fun saveCollections(collections: List) { collectionsPersistenceStore.updateData { @@ -41,7 +43,7 @@ internal class DefaultNFTPersistenceStore( override suspend fun saveSalePrice(assetId: NFTAsset.Identifier, salePrice: NFTAsset.SalePrice) { pricesPersistenceStore.updateData { - it.toMutableMap().apply { this[assetId] = salePrice } + it.toMutableList().plus(NFTPriceId(assetId = assetId, price = salePrice)) } } diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/nft/NFTPersistenceStoreFactory.kt b/core/datasource/src/main/java/com/tangem/datasource/local/nft/NFTPersistenceStoreFactory.kt index cb2e6399bf..2dad3c41c8 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/nft/NFTPersistenceStoreFactory.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/nft/NFTPersistenceStoreFactory.kt @@ -5,12 +5,11 @@ import androidx.datastore.core.DataStore import androidx.datastore.core.DataStoreFactory import androidx.datastore.dataStoreFile import com.squareup.moshi.Moshi -import com.tangem.blockchain.nft.models.NFTAsset import com.tangem.blockchain.nft.models.NFTCollection import com.tangem.datasource.di.NetworkMoshi +import com.tangem.datasource.local.nft.custom.NFTPriceId 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 @@ -48,8 +47,8 @@ class NFTPersistenceStoreFactory @Inject constructor( // 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(), - defaultValue = emptyMap(), + types = listTypes(), + defaultValue = emptyList(), ), ) } diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/nft/custom/NFTPriceId.kt b/core/datasource/src/main/java/com/tangem/datasource/local/nft/custom/NFTPriceId.kt new file mode 100644 index 0000000000..0f933cd743 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/nft/custom/NFTPriceId.kt @@ -0,0 +1,8 @@ +package com.tangem.datasource.local.nft.custom + +import com.tangem.blockchain.nft.models.NFTAsset + +data class NFTPriceId( + val assetId: NFTAsset.Identifier, + val price: NFTAsset.SalePrice, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultUserTokensResponseStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultUserTokensResponseStore.kt new file mode 100644 index 0000000000..c04690b7ce --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultUserTokensResponseStore.kt @@ -0,0 +1,25 @@ +package com.tangem.datasource.local.token + +import com.tangem.datasource.api.tangemTech.models.UserTokensResponse +import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.datasource.local.preferences.PreferencesKeys +import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull +import com.tangem.domain.wallets.models.UserWalletId + +/** + * Default implementation of [UserTokensResponseStore] + * + * @property appPreferencesStore app preferences store + * +[REDACTED_AUTHOR] + */ +internal class DefaultUserTokensResponseStore( + private val appPreferencesStore: AppPreferencesStore, +) : UserTokensResponseStore { + + override suspend fun getSyncOrNull(userWalletId: UserWalletId): UserTokensResponse? { + return appPreferencesStore.getObjectSyncOrNull( + key = PreferencesKeys.getUserTokensKey(userWalletId = userWalletId.stringValue), + ) + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/token/UserTokensResponseStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/token/UserTokensResponseStore.kt new file mode 100644 index 0000000000..df802c591c --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/token/UserTokensResponseStore.kt @@ -0,0 +1,15 @@ +package com.tangem.datasource.local.token + +import com.tangem.datasource.api.tangemTech.models.UserTokensResponse +import com.tangem.domain.wallets.models.UserWalletId + +/** + * Store of [UserTokensResponse] + * +[REDACTED_AUTHOR] + */ +interface UserTokensResponseStore { + + /** Get [UserTokensResponse] synchronously by [userWalletId] or null */ + suspend fun getSyncOrNull(userWalletId: UserWalletId): UserTokensResponse? +} \ No newline at end of file diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index aabccb9ff7..d3c9f55037 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -278,7 +278,7 @@ Kann nicht gegen %s ausgetauscht werden Bereitgestellt von Status - Tangem bietet Token-Swaps über Drittanbieter gemäß den jeweiligen Bedingungen des jeweiligen Anbieters an. + Anbieter erleichtern Transaktionen Anbieter Es ist ein Fehler aufgetreten. Code: %s Fehler %1$s. Der ausgewählte Anbieter kann die angegebene Transaktion nicht verarbeiten. Bitte runde den Wert auf %2$s auf oder änder diesen. @@ -319,11 +319,11 @@ Verifizierung erforderlich Wartet auf Transaktions-Hash Liste aller Token, die deiner Wallet hinzugefügt wurden - Beste Preise werden abgerufen … + Aktuelle Preise werden abgerufen ... Variabler Zinssatz Durch die Nutzung der Swap-Funktion erklärst du dich mit den folgenden Bedingungen des Anbieters einverstanden %s Durch die Nutzung der Swap-Funktionalität erklärst du dich mit des Anbieters %1$s und %2$s einverstanden - Weitere Anbieter werden bald folgen.\nBleib dabei! + Weitere Anbieter folgen in Kürze Anbieter Bester Preis Verfügbar bis zu %s @@ -419,6 +419,7 @@ Dieses Asset wird derzeit in der Wallet nicht unterstützt. Dieses Asset ist für dieses Wallet nicht verfügbar Hinzufügen + APY %s Verfügbare Netzwerke Mein Portfolio Markt @@ -448,6 +449,9 @@ Top-Gewinner Top-Verlierer Beliebt + Staking ist der einfachste Weg, um Belohnungen für Deine Kryptowährung zu erhalten. %s + Mehr anzeigen + Verdiene bis zu %s APY Über %s %d börse @@ -530,6 +534,13 @@ Basisinformationen Netzwerk Vertragsadresse + Kontrollabschnitt + Kontrollabschnitt + Kontrollabschnitt + Kontrollabschnitt + Kontrollabschnitt + Kontrollabschnitt + Kontrollabschnitt Letzter Verkaufspreis Raritätsetikett Seltenheitsgrad @@ -1051,6 +1062,8 @@ Validierer: %s Minimum %s Der Mindesttransaktionsbetrag beträgt %1$s. + Die Netzwerkgebühren für beliebte Token im Tron-Netzwerk können höher sein. Das Staking von TRX kann helfen, die Transaktionskosten zu senken. + Spare bei den Tron-Netzwerkgebühren Versuche es erneut Du hast dieselbe Karte oder Ring gescannt. Um ein Zwillings-Wallet zu erstellen, musst du die Karte oder Ring mit der Nummer %d scannen. Du hast die falsche Doppelkarte oder Ring gescannt. Bitte versuche eine andere Karte oder Ring @@ -1237,6 +1250,7 @@ Zeitüberschreitungsfehler. Bitte versuche es später erneut. WalletConnect konnte nicht hergestellt werden Diese Domäne kann nicht verifiziert werden. Überprüfe die Anfrage sorgfältig und bestätige diese dann. + Geh zurück zu Deinem Browser und stell erneut eine Verbindung über WalletConnect her. WalletConnect-Sitzung wurde getrennt Fehlercode: %s. Wenn das Problem weiterhin besteht, wende Dich bitte an unseren Support. Wir haben einen unbekannten Fehler festgestellt. @@ -1244,14 +1258,22 @@ Nicht unterstützte Netzwerke Tangem unterstützt ein erforderliches Netzwerk um %s. Verifizierte Domain + Falsche Karte oder falscher Ring in der Tangem-App ausgewählt Wir haben eine Art Problem + Verbinden Netzwerk Netzwerke Wallet + Verbundene Netzwerke + Anzeige des Kontostands und der Aktivitäten in Deiner Wallet Signiere die Transaktionen ohne eine Vorankündigung Genehmigung für Transaktionen anfordern Wird nicht in der Lage sein, + Möchte + Verbindungsanfrage Verbindungen + Inhalt + Daten kopieren Alle trennen Text über die Trennung aller dApps Alle dApps trennen @@ -1262,6 +1284,11 @@ Keine Sitzungen Diese Domain wird von mehreren Sicherheitsanbietern als unsicher eingestuft. Verlasse diese umgehend, um Dein Vermögen zu schützen. Bekanntes Sicherheitsrisiko + Anfrage von + Art der Signatur + Transaktionsanfrage + Transaktionsanfrage + Wallet verbinden Verwerfen Du hast eine unterbrochene Sicherung. Möchtest du diese fortsetzen? Ja, fortsetzen diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index 31ae60add2..06e907a6d2 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -527,6 +527,13 @@ 基本情報 チェーン コントラクトアドレス + スタブ + スタブ + スタブ + スタブ + スタブ + スタブ + スタブ 最終販売価格 レアリティ・ラベル レアリティ・ランク @@ -1044,7 +1051,7 @@ バリデーター: %s 最小%s 最小取引金額は%1$sです。 - Tronネットワークの人気トークンの手数料は高めです。TRXをステーキングすると、より安く、あるいは無料で取引できます。 + Tronネットワークの人気トークンの手数料は高い可能性があります。TRXをステーキングすると、取引コストを削減できる可能性があります。 Tronネットワーク手数料を節約 もう一度やり直してください 同じカードをスキャンしました。ツインウォレットを作成するには、番号%dのカードをスキャンする必要があります。 @@ -1237,6 +1244,7 @@ 検証済みドメイン Tangemアプリで誤ったカードまたはリングが選択されました 問題が起きています + アドレス 接続する ネットワーク ネットワーク @@ -1249,6 +1257,8 @@ したい 接続リクエスト 接続 + 内容 + データをコピー すべての接続を解除する すべてのdAppsの接続解除に関するテキスト すべてのdAppを接続解除する @@ -1259,6 +1269,11 @@ セッションなし このドメインは複数のセキュリティプロバイダーから安全でないとの警告を受けています。あなたの資産を守るため、直ちにアクセスを中止してください。 既知のセキュリティリスク + リクエスト元 + 署名タイプ + 宛先 + 取引リクエスト + 取引リクエスト ウォレットコネクト 破棄 バックアップが中断されました。再開しますか? diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index b4b174998e..2081101d9d 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -1063,7 +1063,7 @@ validator: %s Minimum %s The minimum transaction amount is %1$s. - Tron network fees for popular tokens are higher. Stake some TRX for cheaper or free transactions. + Tron network fees for popular tokens can be higher. Staking TRX may help reduce transaction costs. Save on Tron network fees Try again You\'ve scanned the same card. To create a twin wallet you need to scan the card with number %d @@ -1305,6 +1305,7 @@ Verified domain Wrong card or ring selected in Tangem App We\'ve got some kind of problem + Address Connect Network Networks @@ -1331,6 +1332,7 @@ Known security risk Request from Signature Type + To Transaction request Transaction request Wallet connect diff --git a/core/ui/src/main/res/drawable/ic_fee_new_24.xml b/core/ui/src/main/res/drawable/ic_fee_new_24.xml new file mode 100644 index 0000000000..a644c488cf --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_fee_new_24.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/core/ui/src/main/res/drawable/ic_user_square_24.xml b/core/ui/src/main/res/drawable/ic_user_square_24.xml new file mode 100644 index 0000000000..655ae700f5 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_user_square_24.xml @@ -0,0 +1,17 @@ + + + + + diff --git a/data/common/build.gradle.kts b/data/common/build.gradle.kts index 96dc4861ca..05ffe54f76 100644 --- a/data/common/build.gradle.kts +++ b/data/common/build.gradle.kts @@ -14,9 +14,11 @@ dependencies { implementation(projects.core.datasource) /* Domain */ - implementation(projects.domain.models) + implementation(projects.domain.demo) implementation(projects.domain.legacy) + implementation(projects.domain.models) implementation(projects.domain.tokens.models) + implementation(projects.domain.wallets.models) /* Libs - SDK */ implementation(tangemDeps.blockchain) @@ -28,8 +30,16 @@ dependencies { kapt(deps.hilt.kapt) /* Libs - Other */ - implementation(deps.kotlin.coroutines) - implementation(deps.jodatime) - implementation(deps.timber) + implementation(deps.androidx.datastore) implementation(deps.arrow.core) + implementation(deps.jodatime) + implementation(deps.kotlin.coroutines) + implementation(deps.timber) + + /* Test */ + testImplementation(projects.common.test) + testImplementation(deps.test.coroutine) + testImplementation(deps.test.junit) + testImplementation(deps.test.mockk) + testImplementation(deps.test.truth) } \ No newline at end of file diff --git a/data/common/src/main/kotlin/com/tangem/data/common/currency/CardCryptoCurrencyFactory.kt b/data/common/src/main/kotlin/com/tangem/data/common/currency/CardCryptoCurrencyFactory.kt new file mode 100644 index 0000000000..fca4ffba1a --- /dev/null +++ b/data/common/src/main/kotlin/com/tangem/data/common/currency/CardCryptoCurrencyFactory.kt @@ -0,0 +1,46 @@ +package com.tangem.data.common.currency + +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.wallets.models.UserWalletId + +/** + * Factory for creating list of [CryptoCurrency] for selected card + * +[REDACTED_AUTHOR] + */ +interface CardCryptoCurrencyFactory { + + /** + * Universal method for creating list of [CryptoCurrency] in [network] for any card + * + * @param userWalletId user wallet id that determines type of card + * @param network network + */ + @Throws + suspend fun create(userWalletId: UserWalletId, network: Network): List + + /** + * Create default coins for multi currency card + * + * @param scanResponse scan response + */ + fun createDefaultCoinsForMultiCurrencyCard(scanResponse: ScanResponse): List + + /** + * Create primary currency for single currency card + * + * @param scanResponse scan response + */ + @Throws + fun createPrimaryCurrencyForSingleCurrencyCard(scanResponse: ScanResponse): CryptoCurrency + + /** + * Create currencies for single currency card with token (like, NODL) + * + * @param scanResponse scan response + */ + @Throws + fun createCurrenciesForSingleCurrencyCardWithToken(scanResponse: ScanResponse): List +} \ No newline at end of file diff --git a/data/common/src/main/kotlin/com/tangem/data/common/currency/DefaultCardCryptoCurrencyFactory.kt b/data/common/src/main/kotlin/com/tangem/data/common/currency/DefaultCardCryptoCurrencyFactory.kt new file mode 100644 index 0000000000..c4da63c87c --- /dev/null +++ b/data/common/src/main/kotlin/com/tangem/data/common/currency/DefaultCardCryptoCurrencyFactory.kt @@ -0,0 +1,128 @@ +package com.tangem.data.common.currency + +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchainsdk.utils.ExcludedBlockchains +import com.tangem.blockchainsdk.utils.fromNetworkId +import com.tangem.datasource.local.token.UserTokensResponseStore +import com.tangem.datasource.local.userwallet.UserWalletsStore +import com.tangem.domain.common.TapWorkarounds.isTestCard +import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.demo.DemoConfig +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.models.UserWalletId + +/** + * Default implementation of factory for creating list of [CryptoCurrency] for selected card + * + * @property demoConfig demo config + * @property excludedBlockchains excluded blockchains + * @property userWalletsStore user wallets store + * @property userTokensResponseStore user tokens response store + */ +internal class DefaultCardCryptoCurrencyFactory( + private val demoConfig: DemoConfig, + private val excludedBlockchains: ExcludedBlockchains, + private val userWalletsStore: UserWalletsStore, + private val userTokensResponseStore: UserTokensResponseStore, +) : CardCryptoCurrencyFactory { + + private val cryptoCurrencyFactory by lazy { CryptoCurrencyFactory(excludedBlockchains) } + + override suspend fun create(userWalletId: UserWalletId, network: Network): List { + val userWallet = userWalletsStore.getSyncStrict(key = userWalletId) + + val blockchain = Blockchain.fromNetworkId(networkId = network.backendId) + + // multi-currency wallet + if (userWallet.isMultiCurrency) return getMultiWalletCurrencies(userWallet = userWallet, network = network) + + // check if the blockchain of single-currency wallet is the same as network + val cardBlockchain = userWallet.scanResponse.cardTypesResolver.getBlockchain() + if (cardBlockchain != blockchain) return emptyList() + + // single-currency wallet with token (NODL) + if (userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()) { + return createCurrenciesForSingleCurrencyCardWithToken(userWallet.scanResponse) + } + + // single-currency wallet + return createPrimaryCurrencyForSingleCurrencyCard(userWallet.scanResponse).let(::listOf) + } + + override fun createDefaultCoinsForMultiCurrencyCard(scanResponse: ScanResponse): List { + val card = scanResponse.card + + var blockchains = if (demoConfig.isDemoCardId(card.cardId)) { + demoConfig.demoBlockchains + } else { + listOf(Blockchain.Bitcoin, Blockchain.Ethereum) + } + + if (card.isTestCard) { + blockchains = blockchains.mapNotNull { it.getTestnetVersion() } + } + + return blockchains.mapNotNull { + cryptoCurrencyFactory.createCoin( + blockchain = it, + extraDerivationPath = null, + scanResponse = scanResponse, + ) + } + } + + override fun createPrimaryCurrencyForSingleCurrencyCard(scanResponse: ScanResponse): CryptoCurrency { + return with(getSingleWalletCurrencies(scanResponse)) { + primaryToken ?: coin + } + } + + override fun createCurrenciesForSingleCurrencyCardWithToken(scanResponse: ScanResponse): List { + return with(getSingleWalletCurrencies(scanResponse)) { + listOfNotNull(coin, primaryToken) + } + } + + private suspend fun getMultiWalletCurrencies(userWallet: UserWallet, network: Network): List { + val response = userTokensResponseStore.getSyncOrNull(userWalletId = userWallet.walletId) + ?: return emptyList() + + val responseCurrenciesFactory = ResponseCryptoCurrenciesFactory(excludedBlockchains) + + return responseCurrenciesFactory.createCurrencies( + tokens = response.tokens.filter { + it.networkId == network.backendId && it.derivationPath == network.derivationPath.value + }, + scanResponse = userWallet.scanResponse, + ) + } + + private fun getSingleWalletCurrencies(scanResponse: ScanResponse): SingleWalletCurrencies { + val resolver = scanResponse.cardTypesResolver + val blockchain = resolver.getBlockchain() + + val coin = cryptoCurrencyFactory.createCoin( + blockchain = blockchain, + extraDerivationPath = null, + scanResponse = scanResponse, + ) + + requireNotNull(coin) { "Coin for the single currency card cannot be null" } + + val primaryToken = resolver.getPrimaryToken()?.let { token -> + cryptoCurrencyFactory.createToken( + sdkToken = token, + blockchain = blockchain, + extraDerivationPath = null, + scanResponse = scanResponse, + ) + } + + return SingleWalletCurrencies(coin = coin, primaryToken = primaryToken) + } + + private data class SingleWalletCurrencies(val coin: CryptoCurrency, val primaryToken: CryptoCurrency?) +} \ No newline at end of file diff --git a/data/common/src/main/kotlin/com/tangem/data/common/di/DataCommonModule.kt b/data/common/src/main/kotlin/com/tangem/data/common/di/DataCommonModule.kt new file mode 100644 index 0000000000..c9f546e6dd --- /dev/null +++ b/data/common/src/main/kotlin/com/tangem/data/common/di/DataCommonModule.kt @@ -0,0 +1,33 @@ +package com.tangem.data.common.di + +import com.tangem.blockchainsdk.utils.ExcludedBlockchains +import com.tangem.data.common.currency.CardCryptoCurrencyFactory +import com.tangem.data.common.currency.DefaultCardCryptoCurrencyFactory +import com.tangem.datasource.local.token.UserTokensResponseStore +import com.tangem.datasource.local.userwallet.UserWalletsStore +import com.tangem.domain.demo.DemoConfig +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 DataCommonModule { + + @Provides + @Singleton + fun provideCardCryptoCurrencyFactory( + excludedBlockchains: ExcludedBlockchains, + userWalletsStore: UserWalletsStore, + userTokensResponseStore: UserTokensResponseStore, + ): CardCryptoCurrencyFactory { + return DefaultCardCryptoCurrencyFactory( + demoConfig = DemoConfig(), + excludedBlockchains = excludedBlockchains, + userWalletsStore = userWalletsStore, + userTokensResponseStore = userTokensResponseStore, + ) + } +} \ No newline at end of file diff --git a/data/common/src/test/kotlin/com/tangem/data/common/currency/DefaultCardCryptoCurrencyFactoryTest.kt b/data/common/src/test/kotlin/com/tangem/data/common/currency/DefaultCardCryptoCurrencyFactoryTest.kt new file mode 100644 index 0000000000..e6b868dc39 --- /dev/null +++ b/data/common/src/test/kotlin/com/tangem/data/common/currency/DefaultCardCryptoCurrencyFactoryTest.kt @@ -0,0 +1,430 @@ +package com.tangem.data.common.currency + +import android.net.Uri +import com.google.common.truth.Truth +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchainsdk.utils.ExcludedBlockchains +import com.tangem.common.card.WalletData +import com.tangem.common.test.domain.card.MockScanResponseFactory +import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory +import com.tangem.datasource.local.token.UserTokensResponseStore +import com.tangem.datasource.local.userwallet.UserWalletsStore +import com.tangem.domain.common.configs.GenericCardConfig +import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.demo.DemoConfig +import com.tangem.domain.models.scan.ProductType +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.models.UserWalletId +import io.mockk.* +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test + +/** +[REDACTED_AUTHOR] + */ +internal class DefaultCardCryptoCurrencyFactoryTest { + + private val userWalletsStore: UserWalletsStore = mockk() + private val userTokensResponseStore: UserTokensResponseStore = mockk() + + private val factory = DefaultCardCryptoCurrencyFactory( + demoConfig = DemoConfig(), + excludedBlockchains = ExcludedBlockchains(), + userWalletsStore = userWalletsStore, + userTokensResponseStore = userTokensResponseStore, + ) + + @Before + fun setup() { + mockkStatic(Uri::class) + every { Uri.parse(any()) } returns mockk() + } + + @Test + fun `test create if userTokensResponse is not empty`() = runTest { + val multiWallet = createMultiWallet() + + val userTokensResponse = UserTokensResponseFactory().createUserTokensResponse( + currencies = listOf(ethereum), + isGroupedByNetwork = false, + isSortedByBalance = false, + ) + + coEvery { userWalletsStore.getSyncStrict(key = multiWallet.walletId) } returns multiWallet + coEvery { userTokensResponseStore.getSyncOrNull(multiWallet.walletId) } returns userTokensResponse + + val actual = factory.create(userWalletId = multiWallet.walletId, network = ethereum.network) + + coVerifyOrder { + userWalletsStore.getSyncStrict(key = multiWallet.walletId) + userTokensResponseStore.getSyncOrNull(multiWallet.walletId) + } + + val expected = listOf(ethereum) + + Truth.assertThat(actual).isEqualTo(expected) + } + + @Test + fun `test create if userTokensResponse is empty`() = runTest { + val multiWallet = createMultiWallet() + + val userTokensResponse = UserTokensResponseFactory().createUserTokensResponse( + currencies = listOf(), + isGroupedByNetwork = false, + isSortedByBalance = false, + ) + + coEvery { userWalletsStore.getSyncStrict(key = multiWallet.walletId) } returns multiWallet + coEvery { userTokensResponseStore.getSyncOrNull(multiWallet.walletId) } returns userTokensResponse + + val actual = factory.create(userWalletId = multiWallet.walletId, network = ethereum.network) + + coVerifyOrder { + userWalletsStore.getSyncStrict(key = multiWallet.walletId) + userTokensResponseStore.getSyncOrNull(multiWallet.walletId) + } + + val expected = emptyList() + + Truth.assertThat(actual).isEqualTo(expected) + } + + @Test + fun `test create if userTokensResponse is null`() = runTest { + val multiWallet = createMultiWallet() + + coEvery { userWalletsStore.getSyncStrict(key = multiWallet.walletId) } returns multiWallet + coEvery { userTokensResponseStore.getSyncOrNull(multiWallet.walletId) } returns null + + val actual = factory.create(userWalletId = multiWallet.walletId, network = ethereum.network) + + coVerifyOrder { + userWalletsStore.getSyncStrict(key = multiWallet.walletId) + userTokensResponseStore.getSyncOrNull(multiWallet.walletId) + } + + val expected = emptyList() + + Truth.assertThat(actual).isEqualTo(expected) + } + + @Test + fun `test create if userTokensResponse does not contain currency of selected network`() = runTest { + val multiWallet = createMultiWallet() + + val userTokensResponse = UserTokensResponseFactory().createUserTokensResponse( + currencies = listOf(bitcoin), + isGroupedByNetwork = false, + isSortedByBalance = false, + ) + + coEvery { userWalletsStore.getSyncStrict(key = multiWallet.walletId) } returns multiWallet + coEvery { userTokensResponseStore.getSyncOrNull(multiWallet.walletId) } returns userTokensResponse + + val actual = factory.create(userWalletId = multiWallet.walletId, network = ethereum.network) + + coVerifyOrder { + userWalletsStore.getSyncStrict(key = multiWallet.walletId) + userTokensResponseStore.getSyncOrNull(multiWallet.walletId) + } + + val expected = emptyList() + + Truth.assertThat(actual).isEqualTo(expected) + } + + @Test + fun `test create if single wallet has another primary network`() = runTest { + val singleWallet = createSingleWallet() + + coEvery { userWalletsStore.getSyncStrict(key = singleWallet.walletId) } returns singleWallet + + val actual = factory.create(userWalletId = singleWallet.walletId, network = bitcoin.network) + + coVerifyOrder { + userWalletsStore.getSyncStrict(key = singleWallet.walletId) + singleWallet.scanResponse.cardTypesResolver.getBlockchain() + } + + val expected = emptyList() + + Truth.assertThat(actual).isEqualTo(expected) + } + + @Test + fun `test create if card is single wallet`() = runTest { + val singleWallet = createSingleWallet() + + coEvery { userWalletsStore.getSyncStrict(key = singleWallet.walletId) } returns singleWallet + + val actual = factory.create(userWalletId = singleWallet.walletId, network = ethereum.network) + + coVerifyOrder { + userWalletsStore.getSyncStrict(key = singleWallet.walletId) + singleWallet.scanResponse.cardTypesResolver.getBlockchain() + } + + val expected = listOf(ethereum) + + Truth.assertThat(actual).isEqualTo(expected) + } + + @Test + fun `test create if card is single wallet with token`() = runTest { + val singleWallet = createSingleWalletWithToken() + + coEvery { userWalletsStore.getSyncStrict(key = singleWallet.walletId) } returns singleWallet + + val actual = factory.create(userWalletId = singleWallet.walletId, network = ethereum.network) + + val token = CryptoCurrencyFactory(excludedBlockchains = ExcludedBlockchains()).createToken( + sdkToken = singleWallet.scanResponse.cardTypesResolver.getPrimaryToken()!!, + blockchain = Blockchain.Ethereum, + extraDerivationPath = null, + scanResponse = singleWallet.scanResponse, + ) + val expected = listOf(ethereum, token) + + Truth.assertThat(actual).isEqualTo(expected) + } + + @Test + fun `test createDefaultCoinsForMultiCurrencyCard if card is prod`() = runTest { + val multiWallet = createMultiWallet() + + val actual = factory.createDefaultCoinsForMultiCurrencyCard(scanResponse = multiWallet.scanResponse) + + val expected = listOf(bitcoin, ethereum) + + Truth.assertThat(actual).isEqualTo(expected) + } + + @Test + fun `test createDefaultCoinsForMultiCurrencyCard if card is test`() = runTest { + val multiWallet = createMultiWallet().let { + it.copy( + scanResponse = it.scanResponse.copy( + card = it.scanResponse.card.copy(cardId = "FF99", batchId = "99FF"), + ), + ) + } + + val actual = factory.createDefaultCoinsForMultiCurrencyCard(scanResponse = multiWallet.scanResponse) + + val expected = listOf( + cryptoCurrencyFactory.createCoin(blockchain = Blockchain.BitcoinTestnet), + cryptoCurrencyFactory.createCoin(blockchain = Blockchain.EthereumTestnet).setCanHandleTokens(true), + ) + + Truth.assertThat(actual).isEqualTo(expected) + } + + @Test + fun `test createDefaultCoinsForMultiCurrencyCard if card is demo`() = runTest { + val multiWallet = createMultiWallet().let { + it.copy( + scanResponse = it.scanResponse.copy( + card = it.scanResponse.card.copy(cardId = "AC01000000041225"), + ), + ) + } + + val actual = factory.createDefaultCoinsForMultiCurrencyCard(scanResponse = multiWallet.scanResponse) + + val expected = listOf( + bitcoin, + ethereum, + cryptoCurrencyFactory.createCoin(blockchain = Blockchain.Dogecoin), + cryptoCurrencyFactory.createCoin(blockchain = Blockchain.Solana), + ) + + Truth.assertThat(actual).isEqualTo(expected) + } + + @Test + fun `test createPrimaryCurrencyForSingleCurrencyCard if unable to create token`() = runTest { + val singleWallet = UserWallet( + name = "Note", + walletId = UserWalletId("011"), + cardsInWallet = setOf(), + isMultiCurrency = false, + scanResponse = MockScanResponseFactory.create( + cardConfig = GenericCardConfig(maxWalletCount = 2), + derivedKeys = emptyMap(), + ), + hasBackupError = false, + ) + + val actual = runCatching { + factory.createPrimaryCurrencyForSingleCurrencyCard(scanResponse = singleWallet.scanResponse) + } + + val exception = IllegalArgumentException("Coin for the single currency card cannot be null") + + Truth.assertThat(actual.isFailure).isTrue() + Truth.assertThat(actual.exceptionOrNull()).isInstanceOf(exception::class.java) + Truth.assertThat(actual.exceptionOrNull()).hasMessageThat().isEqualTo(exception.message) + } + + @Test + fun `test createPrimaryCurrencyForSingleCurrencyCard if primaryToken is null`() = runTest { + val singleWallet = createSingleWallet() + + val actual = factory.createPrimaryCurrencyForSingleCurrencyCard(scanResponse = singleWallet.scanResponse) + + val expected = ethereum + + Truth.assertThat(actual).isEqualTo(expected) + } + + @Test + fun `test createPrimaryCurrencyForSingleCurrencyCard if primaryToken is not null`() = runTest { + val singleWallet = createSingleWalletWithToken() + + val actual = factory.createPrimaryCurrencyForSingleCurrencyCard(scanResponse = singleWallet.scanResponse) + + val expected = CryptoCurrencyFactory(excludedBlockchains = ExcludedBlockchains()).createToken( + sdkToken = singleWallet.scanResponse.cardTypesResolver.getPrimaryToken()!!, + blockchain = Blockchain.Ethereum, + extraDerivationPath = null, + scanResponse = singleWallet.scanResponse, + ) + + Truth.assertThat(actual).isEqualTo(expected) + } + + @Test + fun `test createCurrenciesForSingleCurrencyCardWithToken if unable to create token`() = runTest { + val singleWalletWithToken = UserWallet( + name = "Note", + walletId = UserWalletId("011"), + cardsInWallet = setOf(), + isMultiCurrency = false, + scanResponse = MockScanResponseFactory.create( + cardConfig = GenericCardConfig(maxWalletCount = 2), + derivedKeys = emptyMap(), + ), + hasBackupError = false, + ) + + val actual = runCatching { + factory.createCurrenciesForSingleCurrencyCardWithToken(scanResponse = singleWalletWithToken.scanResponse) + } + + val exception = IllegalArgumentException("Coin for the single currency card cannot be null") + + Truth.assertThat(actual.isFailure).isTrue() + Truth.assertThat(actual.exceptionOrNull()).isInstanceOf(exception::class.java) + Truth.assertThat(actual.exceptionOrNull()).hasMessageThat().isEqualTo(exception.message) + } + + @Test + fun `test createCurrenciesForSingleCurrencyCardWithToken if primaryToken is null`() = runTest { + val singleWalletWithToken = createSingleWallet() + + val actual = factory.createCurrenciesForSingleCurrencyCardWithToken( + scanResponse = singleWalletWithToken.scanResponse, + ) + + val expected = listOf(ethereum) + + Truth.assertThat(actual).isEqualTo(expected) + } + + @Test + fun `test createCurrenciesForSingleCurrencyCardWithToken if primaryToken is not null`() = runTest { + val singleWalletWithToken = createSingleWalletWithToken() + + val actual = factory.createCurrenciesForSingleCurrencyCardWithToken( + scanResponse = singleWalletWithToken.scanResponse, + ) + + val token = CryptoCurrencyFactory(excludedBlockchains = ExcludedBlockchains()).createToken( + sdkToken = singleWalletWithToken.scanResponse.cardTypesResolver.getPrimaryToken()!!, + blockchain = Blockchain.Ethereum, + extraDerivationPath = null, + scanResponse = singleWalletWithToken.scanResponse, + ) + + val expected = listOf(ethereum, token) + + Truth.assertThat(actual).isEqualTo(expected) + } + + private fun createMultiWallet(): UserWallet { + return UserWallet( + name = "Wallet 1", + walletId = UserWalletId("011"), + cardsInWallet = setOf(), + isMultiCurrency = true, + scanResponse = MockScanResponseFactory.create( + cardConfig = GenericCardConfig(maxWalletCount = 2), + derivedKeys = emptyMap(), + ), + hasBackupError = false, + ) + } + + private fun createSingleWallet(): UserWallet { + return UserWallet( + name = "Note", + walletId = UserWalletId("011"), + cardsInWallet = setOf(), + isMultiCurrency = false, + scanResponse = MockScanResponseFactory.create( + cardConfig = GenericCardConfig(maxWalletCount = 2), + derivedKeys = emptyMap(), + ).let { + it.copy( + card = it.card.copy(batchId = "AB10"), + productType = ProductType.Note, + ) + }, + hasBackupError = false, + ) + } + + private fun createSingleWalletWithToken(): UserWallet { + return UserWallet( + name = "NODL", + walletId = UserWalletId("011"), + cardsInWallet = setOf(), + isMultiCurrency = false, + scanResponse = MockScanResponseFactory.create( + cardConfig = GenericCardConfig(maxWalletCount = 2), + derivedKeys = emptyMap(), + ).copy( + productType = ProductType.Note, + walletData = WalletData( + blockchain = "ETH", + token = WalletData.Token( + name = "Ethereum", + symbol = "ETH", + contractAddress = "0x", + decimals = 8, + ), + ), + ), + hasBackupError = false, + ) + } + + private companion object { + + val cryptoCurrencyFactory = MockCryptoCurrencyFactory() + + val ethereum = cryptoCurrencyFactory.ethereum.setCanHandleTokens(value = true) + + val bitcoin = cryptoCurrencyFactory.createCoin(blockchain = Blockchain.Bitcoin) + + fun CryptoCurrency.setCanHandleTokens(value: Boolean): CryptoCurrency { + return when (this) { + is CryptoCurrency.Coin -> copy(network = network.copy(canHandleTokens = value)) + is CryptoCurrency.Token -> copy(network = network.copy(canHandleTokens = value)) + } + } + } +} \ No newline at end of file diff --git a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt index 84780326f4..f5755416bd 100644 --- a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt +++ b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt @@ -6,12 +6,12 @@ import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.blockchainsdk.utils.toNetworkId import com.tangem.data.common.api.safeApiCall +import com.tangem.data.common.currency.CardCryptoCurrencyFactory import com.tangem.data.common.currency.UserTokensResponseFactory import com.tangem.data.common.currency.getBlockchain import com.tangem.data.common.utils.retryOnError import com.tangem.data.managetokens.utils.ManageTokensUpdateFetcher import com.tangem.data.managetokens.utils.ManagedCryptoCurrencyFactory -import com.tangem.data.tokens.utils.CardCryptoCurrenciesFactory import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.api.tangemTech.models.UserTokensResponse @@ -26,7 +26,6 @@ import com.tangem.domain.common.extensions.canHandleToken import com.tangem.domain.common.extensions.supportedBlockchains import com.tangem.domain.common.extensions.supportedTokens import com.tangem.domain.common.util.cardTypesResolver -import com.tangem.domain.demo.DemoConfig import com.tangem.domain.managetokens.model.* import com.tangem.domain.managetokens.model.ManagedCryptoCurrency.SourceNetwork import com.tangem.domain.managetokens.repository.ManageTokensRepository @@ -48,12 +47,12 @@ internal class DefaultManageTokensRepository( private val appPreferencesStore: AppPreferencesStore, private val testnetTokensStorage: TestnetTokensStorage, private val excludedBlockchains: ExcludedBlockchains, + private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory, private val dispatchers: CoroutineDispatcherProvider, ) : ManageTokensRepository { private val managedCryptoCurrencyFactory = ManagedCryptoCurrencyFactory(excludedBlockchains) private val userTokensResponseFactory = UserTokensResponseFactory() - private val cardCurrenciesFactory = CardCryptoCurrenciesFactory(DemoConfig(), excludedBlockchains) // region getTokenListBatchFlow override fun getTokenListBatchFlow( @@ -77,7 +76,7 @@ internal class DefaultManageTokensRepository( prefetchDistance = batchSize, batchSize = batchSize, subFetcher = { request, _, isFirstBatchFetching -> - val userWallet = request.params.userWalletId?.let { getUserWallet(it) } + val userWallet = request.params.userWalletId?.let(userWalletsStore::getSyncStrict) if (userWallet?.scanResponse?.card?.isTestCard == true) { fetchTestnetCurrencies(userWallet, request) @@ -190,17 +189,11 @@ internal class DefaultManageTokensRepository( private fun createDefaultUserTokensResponse(userWallet: UserWallet) = userTokensResponseFactory.createUserTokensResponse( - currencies = cardCurrenciesFactory.createDefaultCoinsForMultiCurrencyCard(userWallet.scanResponse), + currencies = cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyCard(userWallet.scanResponse), isGroupedByNetwork = false, isSortedByBalance = false, ) - private suspend fun getUserWallet(userWalletId: UserWalletId): UserWallet { - return requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) { - "Unable to find a user wallet with provided ID: $userWalletId" - } - } - private fun getSupportedBlockchains(userWallet: UserWallet?): List { return userWallet?.scanResponse?.let { it.card.supportedBlockchains(it.cardTypesResolver, excludedBlockchains) @@ -261,7 +254,7 @@ internal class DefaultManageTokensRepository( userWalletId: UserWalletId, sourceNetwork: SourceNetwork, ): CurrencyUnsupportedState? { - val userWallet = getUserWallet(userWalletId = userWalletId) + val userWallet = userWalletsStore.getSyncStrict(key = userWalletId) val blockchain = getBlockchain(sourceNetwork.id) return when (sourceNetwork) { is SourceNetwork.Default -> checkTokenUnsupportedState(userWallet = userWallet, blockchain = blockchain) @@ -274,7 +267,7 @@ internal class DefaultManageTokensRepository( rawNetworkId: String, isMainNetwork: Boolean, ): CurrencyUnsupportedState? { - val userWallet = getUserWallet(userWalletId = userWalletId) + val userWallet = userWalletsStore.getSyncStrict(key = userWalletId) val blockchain = Blockchain.fromNetworkId(networkId = rawNetworkId) ?: error("Can not create blockchain with given networkId -> $rawNetworkId") return if (isMainNetwork) { diff --git a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/di/ManageTokensDataModule.kt b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/di/ManageTokensDataModule.kt index 3f64ae5970..73044971f5 100644 --- a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/di/ManageTokensDataModule.kt +++ b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/di/ManageTokensDataModule.kt @@ -1,6 +1,7 @@ package com.tangem.data.managetokens.di import com.tangem.blockchainsdk.utils.ExcludedBlockchains +import com.tangem.data.common.currency.CardCryptoCurrencyFactory import com.tangem.data.managetokens.DefaultCustomTokensRepository import com.tangem.data.managetokens.DefaultManageTokensRepository import com.tangem.data.managetokens.utils.ManageTokensUpdateFetcher @@ -32,15 +33,17 @@ internal object ManageTokensDataModule { testnetTokensStorage: TestnetTokensStorage, dispatchers: CoroutineDispatcherProvider, excludedBlockchains: ExcludedBlockchains, + cardCryptoCurrencyFactory: CardCryptoCurrencyFactory, ): ManageTokensRepository { return DefaultManageTokensRepository( - tangemTechApi, - userWalletsStore, - manageTokensUpdateFetcher, - appPreferencesStore, - testnetTokensStorage, - excludedBlockchains, - dispatchers, + tangemTechApi = tangemTechApi, + userWalletsStore = userWalletsStore, + manageTokensUpdateFetcher = manageTokensUpdateFetcher, + appPreferencesStore = appPreferencesStore, + testnetTokensStorage = testnetTokensStorage, + excludedBlockchains = excludedBlockchains, + cardCryptoCurrencyFactory = cardCryptoCurrencyFactory, + dispatchers = dispatchers, ) } diff --git a/data/networks/src/main/java/com/tangem/data/networks/single/DefaultSingleNetworkStatusFetcher.kt b/data/networks/src/main/java/com/tangem/data/networks/single/DefaultSingleNetworkStatusFetcher.kt index 4d2247b73e..1a1ce6a081 100644 --- a/data/networks/src/main/java/com/tangem/data/networks/single/DefaultSingleNetworkStatusFetcher.kt +++ b/data/networks/src/main/java/com/tangem/data/networks/single/DefaultSingleNetworkStatusFetcher.kt @@ -1,27 +1,14 @@ package com.tangem.data.networks.single import arrow.core.Either -import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchainsdk.utils.ExcludedBlockchains -import com.tangem.blockchainsdk.utils.fromNetworkId -import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory +import com.tangem.data.common.currency.CardCryptoCurrencyFactory import com.tangem.data.networks.store.NetworksStatusesStoreV2 -import com.tangem.data.tokens.utils.CardCryptoCurrenciesFactory import com.tangem.data.tokens.utils.NetworkStatusFactory -import com.tangem.datasource.api.tangemTech.models.UserTokensResponse -import com.tangem.datasource.local.preferences.AppPreferencesStore -import com.tangem.datasource.local.preferences.PreferencesKeys -import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull -import com.tangem.datasource.local.userwallet.UserWalletsStore -import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.core.utils.catchOn -import com.tangem.domain.demo.DemoConfig import com.tangem.domain.networks.single.SingleNetworkStatusFetcher import com.tangem.domain.tokens.model.CryptoCurrency -import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.model.NetworkStatus import com.tangem.domain.walletmanager.WalletManagersFacade -import com.tangem.domain.wallets.models.UserWallet import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.withContext import timber.log.Timber @@ -30,27 +17,20 @@ import javax.inject.Inject /** * Default implementation of [SingleNetworkStatusFetcher] * - * @param excludedBlockchains excluded blockchains - * @property walletManagersFacade wallet managers facade - * @property networksStatusesStore networks statuses store - * @property userWalletsStore user wallets store - * @property appPreferencesStore app preferences store - * @property dispatchers dispatchers + * @property walletManagersFacade wallet managers facade + * @property networksStatusesStore networks statuses store + * @property cardCryptoCurrencyFactory card crypto currency factory + * @property dispatchers dispatchers * [REDACTED_AUTHOR] */ internal class DefaultSingleNetworkStatusFetcher @Inject constructor( - excludedBlockchains: ExcludedBlockchains, private val walletManagersFacade: WalletManagersFacade, private val networksStatusesStore: NetworksStatusesStoreV2, - private val userWalletsStore: UserWalletsStore, - private val appPreferencesStore: AppPreferencesStore, + private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory, private val dispatchers: CoroutineDispatcherProvider, ) : SingleNetworkStatusFetcher { - private val demoConfig = DemoConfig() - private val cardCurrenciesFactory = CardCryptoCurrenciesFactory(demoConfig, excludedBlockchains) - private val responseCurrenciesFactory = ResponseCryptoCurrenciesFactory(excludedBlockchains) private val networkStatusFactory = NetworkStatusFactory() override suspend fun invoke(params: SingleNetworkStatusFetcher.Params) = Either.catchOn(dispatchers.default) { @@ -59,9 +39,10 @@ internal class DefaultSingleNetworkStatusFetcher @Inject constructor( is SingleNetworkStatusFetcher.Params.Simple -> { networksStatusesStore.refresh(userWalletId = params.userWalletId, network = params.network) - val userWallet = userWalletsStore.getSyncStrict(key = params.userWalletId) - - createCurrencies(userWallet = userWallet, network = params.network) + cardCryptoCurrencyFactory.create( + userWalletId = params.userWalletId, + network = params.network, + ) } } @@ -105,36 +86,4 @@ internal class DefaultSingleNetworkStatusFetcher @Inject constructor( Timber.e("Failed to fetch network status for $params: $it") networksStatusesStore.storeError(userWalletId = params.userWalletId, network = params.network) } - - private suspend fun createCurrencies(userWallet: UserWallet, network: Network): List { - val blockchain = Blockchain.fromNetworkId(networkId = network.backendId) - - // multi-currency wallet - if (userWallet.isMultiCurrency) return getMultiWalletCurrencies(userWallet = userWallet, network = network) - - // check if the blockchain of single-currency wallet is the same as network - val cardBlockchain = userWallet.scanResponse.cardTypesResolver.getBlockchain() - if (cardBlockchain != blockchain) return emptyList() - - // single-currency wallet with token (NODL) - if (userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()) { - return cardCurrenciesFactory.createCurrenciesForSingleCurrencyCardWithToken(userWallet.scanResponse) - } - - // single-currency wallet - return cardCurrenciesFactory.createPrimaryCurrencyForSingleCurrencyCard(userWallet.scanResponse).let(::listOf) - } - - private suspend fun getMultiWalletCurrencies(userWallet: UserWallet, network: Network): List { - val response = appPreferencesStore.getObjectSyncOrNull( - key = PreferencesKeys.getUserTokensKey(userWallet.walletId.stringValue), - ) ?: return emptyList() - - return responseCurrenciesFactory.createCurrencies( - tokens = response.tokens.filter { - it.networkId == network.backendId && it.derivationPath == network.derivationPath.value - }, - scanResponse = userWallet.scanResponse, - ) - } } \ No newline at end of file diff --git a/data/networks/src/test/java/com/tangem/data/networks/single/DefaultSingleNetworkStatusFetcherTest.kt b/data/networks/src/test/java/com/tangem/data/networks/single/DefaultSingleNetworkStatusFetcherTest.kt index 7ae26a8b31..bbd429ae23 100644 --- a/data/networks/src/test/java/com/tangem/data/networks/single/DefaultSingleNetworkStatusFetcherTest.kt +++ b/data/networks/src/test/java/com/tangem/data/networks/single/DefaultSingleNetworkStatusFetcherTest.kt @@ -2,8 +2,8 @@ package com.tangem.data.networks.single import com.google.common.truth.Truth import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory +import com.tangem.data.common.currency.CardCryptoCurrencyFactory import com.tangem.data.networks.store.NetworksStatusesStoreV2 -import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.networks.single.SingleNetworkStatusFetcher import com.tangem.domain.tokens.model.NetworkStatus import com.tangem.domain.walletmanager.WalletManagersFacade @@ -22,35 +22,35 @@ import org.junit.Test */ internal class DefaultSingleNetworkStatusFetcherTest { - private val walletManagersFacade: WalletManagersFacade = mockk(relaxed = true) - private val networksStatusesStore: NetworksStatusesStoreV2 = mockk(relaxed = true) - private val userWalletsStore: UserWalletsStore = mockk(relaxed = true) + private val walletManagersFacade: WalletManagersFacade = mockk(relaxUnitFun = true) + private val networksStatusesStore: NetworksStatusesStoreV2 = mockk(relaxUnitFun = true) + private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory = mockk() private val fetcher = DefaultSingleNetworkStatusFetcher( - excludedBlockchains = mockk(relaxed = true), walletManagersFacade = walletManagersFacade, networksStatusesStore = networksStatusesStore, - userWalletsStore = userWalletsStore, - appPreferencesStore = mockk(relaxed = true), + cardCryptoCurrencyFactory = cardCryptoCurrencyFactory, dispatchers = TestingCoroutineDispatcherProvider(), ) @Test fun `fetch network status successfully`() = runTest { - val params = SingleNetworkStatusFetcher.Params.Simple(userWalletId = userWalletId, network = network) + val params = createParams() + + coEvery { cardCryptoCurrencyFactory.create(params.userWalletId, params.network) } returns listOf(ethereum) val result = UpdateWalletManagerResult.MissedDerivation - coEvery { walletManagersFacade.update(userWalletId, network, emptySet()) } returns result + coEvery { walletManagersFacade.update(params.userWalletId, params.network, emptySet()) } returns result val actual = fetcher(params) coVerifyOrder { - networksStatusesStore.refresh(userWalletId = userWalletId, network = network) - userWalletsStore.getSyncStrict(key = userWalletId) - walletManagersFacade.update(userWalletId, network, emptySet()) + networksStatusesStore.refresh(params.userWalletId, params.network) + cardCryptoCurrencyFactory.create(params.userWalletId, params.network) + walletManagersFacade.update(params.userWalletId, params.network, emptySet()) networksStatusesStore.storeSuccess( - userWalletId = userWalletId, - value = NetworkStatus(network, NetworkStatus.MissedDerivation), + userWalletId = params.userWalletId, + value = NetworkStatus(params.network, NetworkStatus.MissedDerivation), ) } @@ -59,17 +59,17 @@ internal class DefaultSingleNetworkStatusFetcherTest { @Test fun `fetch network status failure`() = runTest { - val params = SingleNetworkStatusFetcher.Params.Simple(userWalletId = userWalletId, network = network) + val params = createParams() val exception = IllegalStateException() - coEvery { userWalletsStore.getSyncStrict(key = userWalletId) } throws exception + coEvery { cardCryptoCurrencyFactory.create(params.userWalletId, params.network) } throws exception val actual = fetcher(params) coVerifyOrder { - networksStatusesStore.refresh(userWalletId = userWalletId, network = network) - userWalletsStore.getSyncStrict(key = userWalletId) - networksStatusesStore.storeError(userWalletId = userWalletId, network = network) + networksStatusesStore.refresh(userWalletId = params.userWalletId, network = params.network) + cardCryptoCurrencyFactory.create(userWalletId = params.userWalletId, network = params.network) + networksStatusesStore.storeError(userWalletId = params.userWalletId, network = params.network) } coVerify(inverse = true) { @@ -81,8 +81,15 @@ internal class DefaultSingleNetworkStatusFetcherTest { Truth.assertThat(actual.leftOrNull()).isEqualTo(exception) } + private fun createParams(): SingleNetworkStatusFetcher.Params { + return SingleNetworkStatusFetcher.Params.Simple( + userWalletId = UserWalletId("011"), + network = ethereum.network, + ) + } + private companion object { - val userWalletId = UserWalletId("011") - val network = MockCryptoCurrencyFactory().ethereum.network + + val ethereum = MockCryptoCurrencyFactory().ethereum } } \ No newline at end of file diff --git a/data/notifications/src/main/java/com/tangem/data/notifications/DefaultNotificationsRepository.kt b/data/notifications/src/main/java/com/tangem/data/notifications/DefaultNotificationsRepository.kt index 1470589ef2..d0ea5837e7 100644 --- a/data/notifications/src/main/java/com/tangem/data/notifications/DefaultNotificationsRepository.kt +++ b/data/notifications/src/main/java/com/tangem/data/notifications/DefaultNotificationsRepository.kt @@ -4,12 +4,11 @@ import com.tangem.data.notifications.converters.NotificationsEligibleNetworkConv import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.api.tangemTech.models.NotificationApplicationCreateBody -import com.tangem.datasource.api.tangemTech.models.WalletBody -import com.tangem.datasource.api.tangemTech.models.WalletIdBody import com.tangem.utils.info.AppInfoProvider import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.PreferencesKeys import com.tangem.datasource.local.preferences.utils.* +import com.tangem.domain.notifications.models.ApplicationId import com.tangem.domain.notifications.repository.NotificationsRepository import com.tangem.domain.notifications.models.NotificationsEligibleNetwork import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -23,7 +22,7 @@ internal class DefaultNotificationsRepository @Inject constructor( private val dispatchers: CoroutineDispatcherProvider, ) : NotificationsRepository { - override suspend fun createApplicationId(pushToken: String?): String = withContext(dispatchers.io) { + override suspend fun createApplicationId(pushToken: String?): ApplicationId = withContext(dispatchers.io) { tangemTechApi.createApplicationId( NotificationApplicationCreateBody( platform = appInfoProvider.platform, @@ -33,15 +32,16 @@ internal class DefaultNotificationsRepository @Inject constructor( timezone = appInfoProvider.timezone, pushToken = pushToken, ), - ).getOrThrow().appId + ).getOrThrow().appId.let(::ApplicationId) } - override suspend fun saveApplicationId(appId: String) { - appPreferencesStore.store(PreferencesKeys.NOTIFICATIONS_APPLICATION_ID_KEY, appId) + override suspend fun saveApplicationId(appId: ApplicationId) { + appPreferencesStore.store(PreferencesKeys.NOTIFICATIONS_APPLICATION_ID_KEY, appId.value) } - override suspend fun getApplicationId(): String? { + override suspend fun getApplicationId(): ApplicationId? { return appPreferencesStore.getSyncOrNull(PreferencesKeys.NOTIFICATIONS_APPLICATION_ID_KEY) + ?.let(::ApplicationId) } override suspend fun incrementTronTokenFeeNotificationShowCounter() { @@ -61,31 +61,10 @@ internal class DefaultNotificationsRepository @Inject constructor( ) } - override suspend fun associateApplicationIdWithWallets(appId: String, wallets: List) = - withContext(dispatchers.io) { - tangemTechApi.associateApplicationIdWithWallets( - applicationId = appId, - body = wallets.map { - WalletIdBody(it) - }, - ).getOrThrow() - } - - override suspend fun setWalletName(walletId: String, walletName: String) = withContext(dispatchers.io) { - tangemTechApi.updateWallet( - walletId, - WalletBody(name = walletName), - ).getOrThrow() - } - - override suspend fun getWalletName(walletId: String): String? = withContext(dispatchers.io) { - tangemTechApi.getWalletById(walletId).getOrThrow().name - } - - override suspend fun sendPushToken(appId: String, pushToken: String) { + override suspend fun sendPushToken(appId: ApplicationId, pushToken: String) { withContext(dispatchers.io) { tangemTechApi.updatePushTokenForApplicationId( - appId, + appId.value, NotificationApplicationCreateBody( pushToken = pushToken, ), diff --git a/data/notifications/src/test/java/com/tangem/data/notifications/DefaultNotificationsRepositoryTest.kt b/data/notifications/src/test/java/com/tangem/data/notifications/DefaultNotificationsRepositoryTest.kt index 305732cde6..691dff0714 100644 --- a/data/notifications/src/test/java/com/tangem/data/notifications/DefaultNotificationsRepositoryTest.kt +++ b/data/notifications/src/test/java/com/tangem/data/notifications/DefaultNotificationsRepositoryTest.kt @@ -12,6 +12,7 @@ import com.squareup.moshi.Moshi import com.tangem.data.notifications.converters.NotificationsEligibleNetworkConverter import com.tangem.datasource.api.common.response.ApiResponse import com.tangem.datasource.api.tangemTech.models.* +import com.tangem.domain.notifications.models.ApplicationId import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider import io.mockk.coEvery import io.mockk.coVerify @@ -41,9 +42,9 @@ class DefaultNotificationsRepositoryTest { fun `GIVEN valid push token WHEN createApplicationId THEN returns application id`() = runTest { // GIVEN val pushToken = "test-push-token" - val expectedAppId = "test-app-id" + val expectedAppId = ApplicationId("test-app-id") val expectedAppIdResponse = NotificationApplicationIdResponse( - appId = expectedAppId, + appId = expectedAppId.value, ) coEvery { appInfoProvider.platform } returns "android" coEvery { appInfoProvider.device } returns "test-device" @@ -76,7 +77,7 @@ class DefaultNotificationsRepositoryTest { @Test fun `GIVEN application id WHEN saveApplicationId THEN stores it in preferences`() = runTest { // GIVEN - val appId = "test-app-id" + val appId = ApplicationId("test-app-id") val preferences = mockk(relaxed = true) coEvery { preferencesDataStore.updateData(any()) } returns preferences @@ -90,10 +91,10 @@ class DefaultNotificationsRepositoryTest { @Test fun `GIVEN stored application id WHEN getApplicationId THEN returns it`() = runTest { // GIVEN - val expectedAppId = "test-app-id" + val expectedAppId = ApplicationId("test-app-id") val preferences = mockk(relaxed = true) val key = stringPreferencesKey(PreferencesKeys.NOTIFICATIONS_APPLICATION_ID_KEY.name) - every { preferences[key] } returns expectedAppId + every { preferences[key] } returns expectedAppId.value coEvery { preferencesDataStore.data } returns flowOf(preferences) // WHEN @@ -103,75 +104,24 @@ class DefaultNotificationsRepositoryTest { assertThat(result).isEqualTo(expectedAppId) } - @Test - fun `GIVEN application id and wallet list WHEN associateApplicationIdWithWallets THEN associates them`() = runTest { - // GIVEN - val appId = "test-app-id" - val wallets = listOf("wallet1", "wallet2") - coEvery { - tangemTechApi.associateApplicationIdWithWallets( - appId, - wallets.map { WalletIdBody(it) }, - ) - } returns ApiResponse.Success(Unit) - - // WHEN - repository.associateApplicationIdWithWallets(appId, wallets) - - // THEN - coVerify { tangemTechApi.associateApplicationIdWithWallets(appId, wallets.map { WalletIdBody(it) }) } - } - - @Test - fun `GIVEN wallet id and name WHEN setWalletName THEN updates wallet name`() = runTest { - // GIVEN - val walletId = "test-wallet-id" - val walletName = "Test Wallet" - coEvery { - tangemTechApi.updateWallet( - walletId, - WalletBody(name = walletName), - ) - } returns ApiResponse.Success(Unit) - - // WHEN - repository.setWalletName(walletId, walletName) - - // THEN - coVerify { tangemTechApi.updateWallet(walletId, WalletBody(name = walletName)) } - } - - @Test - fun `GIVEN wallet id WHEN getWalletName THEN returns wallet name`() = runTest { - // GIVEN - val walletId = "test-wallet-id" - val expectedName = "Test Wallet" - coEvery { tangemTechApi.getWalletById(walletId) } returns ApiResponse.Success( - WalletResponse( - notifyStatus = false, - name = expectedName, - id = walletId, - ), - ) - - // WHEN - val result = repository.getWalletName(walletId) - - // THEN - assertThat(result).isEqualTo(expectedName) - } - @Test fun `GIVEN application id and push token WHEN sendPushToken THEN updates push token`() = runTest { // GIVEN - val appId = "test-app-id" + val appId = ApplicationId("test-app-id") val pushToken = "test-push-token" coEvery { tangemTechApi.updatePushTokenForApplicationId( - appId, - NotificationApplicationCreateBody(pushToken = pushToken), + appId.value, + NotificationApplicationCreateBody( + pushToken = pushToken, + platform = null, + device = null, + systemVersion = null, + language = null, + timezone = null, + ), ) - } returns ApiResponse.Success(appId) + } returns ApiResponse.Success(appId.value) // WHEN repository.sendPushToken(appId, pushToken) @@ -179,8 +129,15 @@ class DefaultNotificationsRepositoryTest { // THEN coVerify { tangemTechApi.updatePushTokenForApplicationId( - appId, - NotificationApplicationCreateBody(pushToken = pushToken), + appId.value, + NotificationApplicationCreateBody( + pushToken = pushToken, + platform = null, + device = null, + systemVersion = null, + language = null, + timezone = null, + ), ) } } diff --git a/data/quotes/src/main/java/com/tangem/data/quotes/di/QuoteFetcherModule.kt b/data/quotes/src/main/java/com/tangem/data/quotes/di/QuoteFetcherModule.kt index 5a5eb2ecfd..1bdef12d69 100644 --- a/data/quotes/src/main/java/com/tangem/data/quotes/di/QuoteFetcherModule.kt +++ b/data/quotes/src/main/java/com/tangem/data/quotes/di/QuoteFetcherModule.kt @@ -2,8 +2,10 @@ package com.tangem.data.quotes.di import com.tangem.data.quotes.multi.DefaultMultiQuoteFetcher import com.tangem.data.quotes.multi.DefaultMultiQuoteUpdater +import com.tangem.data.quotes.single.DefaultSingleQuoteFetcher import com.tangem.domain.quotes.multi.MultiQuoteFetcher import com.tangem.domain.quotes.multi.MultiQuoteUpdater +import com.tangem.domain.quotes.single.SingleQuoteFetcher import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -21,4 +23,8 @@ internal interface QuoteFetcherModule { @Binds @Singleton fun bindMultiQuoteUpdater(impl: DefaultMultiQuoteUpdater): MultiQuoteUpdater + + @Binds + @Singleton + fun bindSingleQuoteFetcher(impl: DefaultSingleQuoteFetcher): SingleQuoteFetcher } \ No newline at end of file diff --git a/data/quotes/src/main/java/com/tangem/data/quotes/single/DefaultSingleQuoteFetcher.kt b/data/quotes/src/main/java/com/tangem/data/quotes/single/DefaultSingleQuoteFetcher.kt new file mode 100644 index 0000000000..9cd56dacc2 --- /dev/null +++ b/data/quotes/src/main/java/com/tangem/data/quotes/single/DefaultSingleQuoteFetcher.kt @@ -0,0 +1,17 @@ +package com.tangem.data.quotes.single + +import com.tangem.domain.quotes.multi.MultiQuoteFetcher +import com.tangem.domain.quotes.single.SingleQuoteFetcher +import javax.inject.Inject + +internal class DefaultSingleQuoteFetcher @Inject constructor( + private val multiQuoteFetcher: MultiQuoteFetcher, +) : SingleQuoteFetcher { + + override suspend fun invoke(params: SingleQuoteFetcher.Params) = multiQuoteFetcher.invoke( + MultiQuoteFetcher.Params( + currenciesIds = setOf(params.rawCurrencyId), + appCurrencyId = params.appCurrencyId, + ), + ) +} \ No newline at end of file diff --git a/data/quotes/src/test/java/com/tangem/data/quotes/single/DefaultSingleQuoteFetcherTest.kt b/data/quotes/src/test/java/com/tangem/data/quotes/single/DefaultSingleQuoteFetcherTest.kt new file mode 100644 index 0000000000..fc1799ba0c --- /dev/null +++ b/data/quotes/src/test/java/com/tangem/data/quotes/single/DefaultSingleQuoteFetcherTest.kt @@ -0,0 +1,174 @@ +package com.tangem.data.quotes.single + +import com.google.common.truth.Truth +import com.tangem.common.test.data.quote.MockQuoteResponseFactory +import com.tangem.data.quotes.multi.DefaultMultiQuoteFetcher +import com.tangem.data.quotes.store.QuotesStoreV2 +import com.tangem.datasource.api.common.response.ApiResponse +import com.tangem.datasource.api.common.response.ApiResponseError +import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse +import com.tangem.datasource.api.tangemTech.models.QuotesResponse +import com.tangem.datasource.appcurrency.AppCurrencyResponseStore +import com.tangem.domain.quotes.single.SingleQuoteFetcher +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.coVerifyOrder +import io.mockk.mockk +import kotlinx.coroutines.test.runTest +import org.junit.Test +import java.math.BigDecimal + +internal class DefaultSingleQuoteFetcherTest { + + private val tangemTechApi = mockk(relaxed = true) + private val appCurrencyResponseStore = mockk(relaxed = true) + private val quotesStore = mockk(relaxed = true) + + private val multiFetcher = DefaultMultiQuoteFetcher( + tangemTechApi = tangemTechApi, + appCurrencyResponseStore = appCurrencyResponseStore, + quotesStore = quotesStore, + dispatchers = TestingCoroutineDispatcherProvider(), + ) + + private val singleFetcher = DefaultSingleQuoteFetcher(multiFetcher) + + @Test + fun `fetch single quote successfully`() = runTest { + val params = SingleQuoteFetcher.Params(rawCurrencyId = currenciesId, appCurrencyId = null) + + coEvery { appCurrencyResponseStore.getSyncOrNull() } returns usdAppCurrency + + val coinIds = "BTC" + coEvery { + tangemTechApi.getQuotes(currencyId = "usd", coinIds = coinIds) + } returns ApiResponse.Success(successResponse) + + val actual = singleFetcher(params) + + coVerifyOrder { + quotesStore.refresh(currenciesIds = setOf(params.rawCurrencyId)) + appCurrencyResponseStore.getSyncOrNull() + tangemTechApi.getQuotes(currencyId = "usd", coinIds = coinIds) + quotesStore.storeActual(values = successResponse.quotes) + } + + coVerify(inverse = true) { + quotesStore.storeError(currenciesIds = any()) + } + + Truth.assertThat(actual.isRight()).isTrue() + } + + @Test + fun `fetch single quote successfully if appCurrencyId from params is not null`() = runTest { + val appCurrencyId = "usd" + val params = SingleQuoteFetcher.Params(rawCurrencyId = currenciesId, appCurrencyId = appCurrencyId) + + val coinIds = "BTC" + coEvery { + tangemTechApi.getQuotes(currencyId = appCurrencyId, coinIds = coinIds) + } returns ApiResponse.Success(successResponse) + + val actual = singleFetcher(params) + + coVerifyOrder { + quotesStore.refresh(currenciesIds = setOf(currenciesId)) + tangemTechApi.getQuotes(currencyId = "usd", coinIds = coinIds) + quotesStore.storeActual(values = successResponse.quotes) + } + + Truth.assertThat(actual.isRight()).isTrue() + } + + @Test + fun `fetch single quote failure because appCurrencyId from params is blank`() = runTest { + val appCurrencyId = "" + val params = SingleQuoteFetcher.Params(rawCurrencyId = currenciesId, appCurrencyId = appCurrencyId) + + val actual = singleFetcher(params) + + coVerifyOrder { + quotesStore.refresh(currenciesIds = setOf(currenciesId)) + quotesStore.storeError(currenciesIds = setOf(currenciesId)) + } + + Truth.assertThat(actual.isLeft()).isTrue() + Truth.assertThat(actual.leftOrNull()).isInstanceOf(IllegalStateException::class.java) + Truth.assertThat(actual.leftOrNull()).hasMessageThat() + .isEqualTo("Unable to get AppCurrency for updating quotes") + } + + @Test + fun `fetch single quote failure because api request failed`() = runTest { + val params = SingleQuoteFetcher.Params(rawCurrencyId = currenciesId, appCurrencyId = null) + + coEvery { appCurrencyResponseStore.getSyncOrNull() } returns usdAppCurrency + + val coinIds = "BTC" + + @Suppress("UNCHECKED_CAST") + val errorResponse = ApiResponse.Error(ApiResponseError.NetworkException) as ApiResponse + coEvery { tangemTechApi.getQuotes(currencyId = "usd", coinIds = coinIds) } returns errorResponse + + val actual = singleFetcher(params) + + coVerifyOrder { + quotesStore.refresh(currenciesIds = setOf(currenciesId)) + appCurrencyResponseStore.getSyncOrNull() + tangemTechApi.getQuotes(currencyId = "usd", coinIds = coinIds) + quotesStore.storeError(currenciesIds = setOf(currenciesId)) + } + + coVerify(inverse = true) { + quotesStore.storeActual(values = any()) + } + + Truth.assertThat(actual.isLeft()).isTrue() + } + + @Test + fun `fetch single quote failure because app currency not found`() = runTest { + val params = SingleQuoteFetcher.Params(rawCurrencyId = currenciesId, appCurrencyId = null) + + coEvery { appCurrencyResponseStore.getSyncOrNull() } returns null + + val actual = singleFetcher(params) + + coVerifyOrder { + quotesStore.refresh(currenciesIds = setOf(currenciesId)) + appCurrencyResponseStore.getSyncOrNull() + quotesStore.storeError(currenciesIds = setOf(currenciesId)) + } + + coVerify(inverse = true) { + tangemTechApi.getQuotes(currencyId = any(), coinIds = any()) + quotesStore.storeActual(values = any()) + } + + Truth.assertThat(actual.isLeft()).isTrue() + } + + private companion object { + + val currenciesId = CryptoCurrency.RawID(value = "BTC") + + val usdAppCurrency = CurrenciesResponse.Currency( + id = "USD".lowercase(), + code = "USD", + name = "US Dollar", + unit = "$", + type = "fiat", + rateBTC = "", + ) + + val successResponse = QuotesResponse( + quotes = mapOf( + "BTC" to MockQuoteResponseFactory.createSinglePrice(value = BigDecimal.ONE), + ), + ) + } +} \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt index 1172126125..af3cf30f5f 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt @@ -2,6 +2,7 @@ package com.tangem.data.tokens.di import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.data.common.cache.CacheRegistry +import com.tangem.data.common.currency.CardCryptoCurrencyFactory import com.tangem.data.tokens.repository.* import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.exchangeservice.swap.ExpressServiceLoader @@ -33,6 +34,7 @@ internal object TokensDataModule { dispatchers: CoroutineDispatcherProvider, expressServiceLoader: ExpressServiceLoader, excludedBlockchains: ExcludedBlockchains, + cardCryptoCurrencyFactory: CardCryptoCurrencyFactory, ): CurrenciesRepository { return DefaultCurrenciesRepository( tangemTechApi = tangemTechApi, @@ -43,6 +45,7 @@ internal object TokensDataModule { expressServiceLoader = expressServiceLoader, dispatchers = dispatchers, excludedBlockchains = excludedBlockchains, + cardCryptoCurrencyFactory = cardCryptoCurrencyFactory, ) } @@ -74,6 +77,7 @@ internal object TokensDataModule { cacheRegistry: CacheRegistry, dispatchers: CoroutineDispatcherProvider, excludedBlockchains: ExcludedBlockchains, + cardCryptoCurrencyFactory: CardCryptoCurrencyFactory, ): NetworksRepository { return DefaultNetworksRepository( networksStatusesStore = networksStatusesStore, @@ -83,6 +87,7 @@ internal object TokensDataModule { cacheRegistry = cacheRegistry, dispatchers = dispatchers, excludedBlockchains = excludedBlockchains, + cardCryptoCurrencyFactory = cardCryptoCurrencyFactory, ) } diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt index 9454dd4a87..d68c468811 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt @@ -6,7 +6,6 @@ import com.tangem.blockchainsdk.utils.* import com.tangem.data.common.api.safeApiCall import com.tangem.data.common.cache.CacheRegistry import com.tangem.data.common.currency.* -import com.tangem.data.tokens.utils.CardCryptoCurrenciesFactory import com.tangem.data.tokens.utils.CustomTokensMerger import com.tangem.data.tokens.utils.UserTokensBackwardCompatibility import com.tangem.datasource.api.common.response.ApiResponseError @@ -51,12 +50,12 @@ internal class DefaultCurrenciesRepository( private val expressServiceLoader: ExpressServiceLoader, private val dispatchers: CoroutineDispatcherProvider, private val excludedBlockchains: ExcludedBlockchains, + private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory, ) : CurrenciesRepository { private val demoConfig = DemoConfig() private val responseCurrenciesFactory = ResponseCryptoCurrenciesFactory(excludedBlockchains) private val cryptoCurrencyFactory = CryptoCurrencyFactory(excludedBlockchains) - private val cardCurrenciesFactory = CardCryptoCurrenciesFactory(demoConfig, excludedBlockchains) private val userTokensResponseFactory = UserTokensResponseFactory() private val userTokensBackwardCompatibility = UserTokensBackwardCompatibility() private val customTokensMerger = CustomTokensMerger(tangemTechApi, dispatchers) @@ -223,7 +222,7 @@ internal class DefaultCurrenciesRepository( val userWallet = getUserWallet(userWalletId) ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = false) - val currency = cardCurrenciesFactory.createPrimaryCurrencyForSingleCurrencyCard(userWallet.scanResponse) + val currency = cardCryptoCurrencyFactory.createPrimaryCurrencyForSingleCurrencyCard(userWallet.scanResponse) fetchExpressAssetsByNetworkIds(userWalletId, listOf(currency), refresh) currency } @@ -237,8 +236,8 @@ internal class DefaultCurrenciesRepository( val userWallet = getUserWallet(userWalletId) ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = false) - val currencies = cardCurrenciesFactory.createCurrenciesForSingleCurrencyCardWithToken( - userWallet.scanResponse, + val currencies = cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken( + scanResponse = userWallet.scanResponse, ) fetchExpressAssetsByNetworkIds(userWalletId, currencies, refresh) currencies @@ -253,7 +252,9 @@ internal class DefaultCurrenciesRepository( val userWallet = getUserWallet(userWalletId) ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = false) - val currency = cardCurrenciesFactory.createCurrenciesForSingleCurrencyCardWithToken(userWallet.scanResponse) + val currency = cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken( + scanResponse = userWallet.scanResponse, + ) .find { it.id == id } requireNotNull(currency) { "Unable to find currency with provided ID: $id" } fetchExpressAssetsByNetworkIds(userWalletId, listOf(currency)) @@ -672,7 +673,7 @@ internal class DefaultCurrenciesRepository( private fun createDefaultUserTokensResponse(userWallet: UserWallet) = userTokensResponseFactory.createUserTokensResponse( - currencies = cardCurrenciesFactory.createDefaultCoinsForMultiCurrencyCard(userWallet.scanResponse), + currencies = cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyCard(userWallet.scanResponse), isGroupedByNetwork = false, isSortedByBalance = false, ) diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt index b85018c34b..80acea39bf 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt @@ -5,8 +5,8 @@ import com.tangem.blockchain.common.address.AddressType import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.data.common.cache.CacheRegistry +import com.tangem.data.common.currency.CardCryptoCurrencyFactory import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory -import com.tangem.data.tokens.utils.CardCryptoCurrenciesFactory import com.tangem.data.tokens.utils.NetworkStatusFactory import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.datasource.local.network.NetworksStatusesStore @@ -15,7 +15,6 @@ import com.tangem.datasource.local.preferences.PreferencesKeys import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.common.util.cardTypesResolver -import com.tangem.domain.demo.DemoConfig import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyAddress import com.tangem.domain.tokens.model.Network @@ -38,12 +37,11 @@ internal class DefaultNetworksRepository( private val userWalletsStore: UserWalletsStore, private val appPreferencesStore: AppPreferencesStore, private val cacheRegistry: CacheRegistry, + private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory, private val dispatchers: CoroutineDispatcherProvider, excludedBlockchains: ExcludedBlockchains, ) : NetworksRepository { - private val demoConfig = DemoConfig() - private val cardCurrenciesFactory = CardCryptoCurrenciesFactory(demoConfig, excludedBlockchains) private val responseCurrenciesFactory = ResponseCryptoCurrenciesFactory(excludedBlockchains) private val networkStatusFactory = NetworkStatusFactory() @@ -225,10 +223,14 @@ internal class DefaultNetworksRepository( responseCurrenciesFactory.createCurrencies(response, userWallet.scanResponse).asSequence() } else { if (userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()) { - cardCurrenciesFactory.createCurrenciesForSingleCurrencyCardWithToken(userWallet.scanResponse) + cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken( + scanResponse = userWallet.scanResponse, + ) .asSequence() } else { - val currency = cardCurrenciesFactory.createPrimaryCurrencyForSingleCurrencyCard(userWallet.scanResponse) + val currency = cardCryptoCurrencyFactory.createPrimaryCurrencyForSingleCurrencyCard( + scanResponse = userWallet.scanResponse, + ) sequenceOf(currency) } diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/CardCryptoCurrenciesFactory.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/CardCryptoCurrenciesFactory.kt deleted file mode 100644 index 1e3ed23c01..0000000000 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/CardCryptoCurrenciesFactory.kt +++ /dev/null @@ -1,86 +0,0 @@ -package com.tangem.data.tokens.utils - -import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchainsdk.utils.ExcludedBlockchains -import com.tangem.data.common.currency.CryptoCurrencyFactory -import com.tangem.domain.common.TapWorkarounds.isTestCard -import com.tangem.domain.common.util.cardTypesResolver -import com.tangem.domain.demo.DemoConfig -import com.tangem.domain.models.scan.ScanResponse -import com.tangem.domain.tokens.model.CryptoCurrency - -class CardCryptoCurrenciesFactory( - private val demoConfig: DemoConfig, - excludedBlockchains: ExcludedBlockchains, -) { - - private val cryptoCurrencyFactory = CryptoCurrencyFactory(excludedBlockchains) - - fun createDefaultCoinsForMultiCurrencyCard(scanResponse: ScanResponse): List { - val card = scanResponse.card - - var blockchains = if (demoConfig.isDemoCardId(card.cardId)) { - demoConfig.demoBlockchains - } else { - listOf(Blockchain.Bitcoin, Blockchain.Ethereum) - } - - if (card.isTestCard) { - blockchains = blockchains.mapNotNull { it.getTestnetVersion() } - } - - return blockchains.mapNotNull { - cryptoCurrencyFactory.createCoin( - blockchain = it, - extraDerivationPath = null, - scanResponse = scanResponse, - ) - } - } - - fun createPrimaryCurrencyForSingleCurrencyCard(scanResponse: ScanResponse): CryptoCurrency { - val resolver = scanResponse.cardTypesResolver - val blockchain = resolver.getBlockchain() - - val coin = cryptoCurrencyFactory.createCoin( - blockchain = blockchain, - extraDerivationPath = null, - scanResponse = scanResponse, - ) - requireNotNull(coin) { "Coin for the single currency card cannot be null" } - - val primaryToken = resolver.getPrimaryToken()?.let { token -> - cryptoCurrencyFactory.createToken( - sdkToken = token, - blockchain = blockchain, - extraDerivationPath = null, - scanResponse = scanResponse, - ) - } - - return primaryToken ?: coin - } - - fun createCurrenciesForSingleCurrencyCardWithToken(scanResponse: ScanResponse): List { - val resolver = scanResponse.cardTypesResolver - val blockchain = resolver.getBlockchain() - - val coin = cryptoCurrencyFactory.createCoin( - blockchain = blockchain, - extraDerivationPath = null, - scanResponse = scanResponse, - ) - requireNotNull(coin) { "Coin for the single currency card cannot be null" } - - val primaryToken = resolver.getPrimaryToken()?.let { token -> - cryptoCurrencyFactory.createToken( - sdkToken = token, - blockchain = blockchain, - extraDerivationPath = null, - scanResponse = scanResponse, - ) - } - - return listOfNotNull(coin, primaryToken) - } -} \ No newline at end of file diff --git a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt index 6ba07ef2e1..540ffdb8ed 100644 --- a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt +++ b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt @@ -190,12 +190,19 @@ internal class DefaultTransactionRepository( null } + val contractAddress = when (val identifier = nftAsset.identifier) { + is NFTAsset.Identifier.EVM -> identifier.tokenAddress + is NFTAsset.Identifier.Solana -> identifier.tokenAddress + is NFTAsset.Identifier.TON -> identifier.tokenAddress + NFTAsset.Identifier.Unknown -> "" + } + return@withContext createTransaction( amount = Amount( value = nftAsset.amount?.toBigDecimal() ?: error("Invalid amount"), token = Token( symbol = blockchain.currency, - contractAddress = "", + contractAddress = contractAddress, decimals = nftAsset.decimals ?: error("Invalid decimals"), ), ), diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/di/WalletConnectDataModule.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/di/WalletConnectDataModule.kt index 8d43fc1ebe..84a40f03e0 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/di/WalletConnectDataModule.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/di/WalletConnectDataModule.kt @@ -42,6 +42,7 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) +@Suppress("TooManyFunctions") internal object WalletConnectDataModule { @Provides @@ -86,6 +87,7 @@ internal object WalletConnectDataModule { dispatchers: CoroutineDispatcherProvider, legacyStore: WalletConnectSessionsRepository, getWallets: GetWalletsUseCase, + associateNetworks: AssociateNetworksDelegate, ): DefaultWcSessionsManager { val scope = CoroutineScope(SupervisorJob() + dispatchers.io) return DefaultWcSessionsManager( @@ -93,6 +95,7 @@ internal object WalletConnectDataModule { dispatchers = dispatchers, legacyStore = legacyStore, getWallets = getWallets, + associateNetworks = associateNetworks, scope = scope, ) } @@ -121,12 +124,12 @@ internal object WalletConnectDataModule { @Singleton fun wcEthNetwork( @SdkMoshi moshi: Moshi, - excludedBlockchains: ExcludedBlockchains, sessionsManager: WcSessionsManager, factories: WcEthNetwork.Factories, + namespaceConverter: WcEthNetwork.NamespaceConverter, ): WcEthNetwork = WcEthNetwork( moshi = moshi, - excludedBlockchains = excludedBlockchains, + namespaceConverter = namespaceConverter, sessionsManager = sessionsManager, factories = factories, ) @@ -135,7 +138,7 @@ internal object WalletConnectDataModule { @Singleton fun wcSolanaNetwork( @SdkMoshi moshi: Moshi, - excludedBlockchains: ExcludedBlockchains, + namespaceConverter: WcSolanaNetwork.NamespaceConverter, sessionsManager: WcSessionsManager, factories: WcSolanaNetwork.Factories, walletManager: UserWalletManager, @@ -143,28 +146,28 @@ internal object WalletConnectDataModule { moshi = moshi, sessionsManager = sessionsManager, factories = factories, - excludedBlockchains = excludedBlockchains, + namespaceConverter = namespaceConverter, walletManager = walletManager, ) @Provides @Singleton fun caipNamespaceDelegate( - diHelperBox: DiHelperBox, + namespaceConverters: Set<@JvmSuppressWildcards WcNamespaceConverter>, walletManagersFacade: WalletManagersFacade, ): CaipNamespaceDelegate = CaipNamespaceDelegate( - namespaceConverters = diHelperBox.converters, + namespaceConverters = namespaceConverters, walletManagersFacade = walletManagersFacade, ) @Provides @Singleton fun associateNetworksDelegate( - diHelperBox: DiHelperBox, + namespaceConverters: Set<@JvmSuppressWildcards WcNamespaceConverter>, getWallets: GetWalletsUseCase, currenciesRepository: CurrenciesRepository, ): AssociateNetworksDelegate = AssociateNetworksDelegate( - namespaceConverters = diHelperBox.converters, + namespaceConverters = namespaceConverters, getWallets = getWallets, currenciesRepository = currenciesRepository, ) @@ -176,10 +179,16 @@ internal object WalletConnectDataModule { ethNetwork, solanaNetwork, ), - converters = setOf( - ethNetwork, - solanaNetwork, - ), + ) + + @Provides + @Singleton + fun namespaceConverters( + ethNamespaceConverter: WcEthNetwork.NamespaceConverter, + solanaNamespaceConverter: WcSolanaNetwork.NamespaceConverter, + ): Set<@JvmSuppressWildcards WcNamespaceConverter> = setOf( + ethNamespaceConverter, + solanaNamespaceConverter, ) @Provides @@ -188,6 +197,20 @@ internal object WalletConnectDataModule { return DefaultWcRequestUseCaseFactory(diHelperBox.handlers) } + @Provides + @Singleton + fun wcEthNetworkNamespaceConverter(excludedBlockchains: ExcludedBlockchains): WcEthNetwork.NamespaceConverter { + return WcEthNetwork.NamespaceConverter(excludedBlockchains) + } + + @Provides + @Singleton + fun wcSolanaNetworkNamespaceConverter( + excludedBlockchains: ExcludedBlockchains, + ): WcSolanaNetwork.NamespaceConverter { + return WcSolanaNetwork.NamespaceConverter(excludedBlockchains) + } + @Provides @Singleton fun providesWcDisconnectUseCase(sessionsManager: WcSessionsManager): WcDisconnectUseCase { @@ -195,7 +218,6 @@ internal object WalletConnectDataModule { } internal class DiHelperBox( - val converters: Set, val handlers: Set, ) } \ No newline at end of file diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthNetwork.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthNetwork.kt index 175f715409..5dda26b898 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthNetwork.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthNetwork.kt @@ -22,12 +22,10 @@ import jakarta.inject.Inject internal class WcEthNetwork( private val moshi: Moshi, - private val excludedBlockchains: ExcludedBlockchains, private val sessionsManager: WcSessionsManager, private val factories: Factories, -) : WcRequestToUseCaseConverter, WcNamespaceConverter { - - override val namespaceKey: NamespaceKey = NamespaceKey("eip155") + private val namespaceConverter: NamespaceConverter, +) : WcRequestToUseCaseConverter { override fun toWcMethodName(request: WcSdkSessionRequest): WcEthMethodName? { val methodKey = request.request.method @@ -39,7 +37,7 @@ internal class WcEthNetwork( val name = toWcMethodName(request) ?: return null val method: WcEthMethod = name.toMethod(request) ?: return null val session = sessionsManager.findSessionByTopic(request.topic) ?: return null - val network = toNetwork(request.chainId.orEmpty(), session.wallet) ?: return null + val network = namespaceConverter.toNetwork(request.chainId.orEmpty(), session.wallet) ?: return null val accountAddress = when (method) { is WcEthMethod.MessageSign -> method.account is WcEthMethod.SendTransaction -> method.transaction.from @@ -100,24 +98,31 @@ internal class WcEthNetwork( return WcEthMethod.SignTypedData(params = params, account = account, dataForSign = data) } - override fun toNetwork(chainId: String, wallet: UserWallet): Network? { - return toNetwork(chainId, wallet, excludedBlockchains) - } + internal class NamespaceConverter constructor( + private val excludedBlockchains: ExcludedBlockchains, + ) : WcNamespaceConverter { - override fun toBlockchain(chainId: CAIP2): Blockchain? { - if (chainId.namespace != namespaceKey.key) return null - val ethChainId = chainId.reference.toIntOrNull() ?: return null - return Blockchain.fromChainId(ethChainId) - } + override val namespaceKey: NamespaceKey = NamespaceKey("eip155") - override fun toCAIP2(network: Network): CAIP2? { - val blockchain = Blockchain.fromId(network.id.value) - if (!blockchain.isEvm()) return null - val chainId = blockchain.getChainId() ?: return null - return CAIP2( - namespace = namespaceKey.key, - reference = chainId.toString(), - ) + override fun toNetwork(chainId: String, wallet: UserWallet): Network? { + return toNetwork(chainId, wallet, excludedBlockchains) + } + + override fun toBlockchain(chainId: CAIP2): Blockchain? { + if (chainId.namespace != namespaceKey.key) return null + val ethChainId = chainId.reference.toIntOrNull() ?: return null + return Blockchain.fromChainId(ethChainId) + } + + override fun toCAIP2(network: Network): CAIP2? { + val blockchain = Blockchain.fromId(network.id.value) + if (!blockchain.isEvm()) return null + val chainId = blockchain.getChainId() ?: return null + return CAIP2( + namespace = namespaceKey.key, + reference = chainId.toString(), + ) + } } internal class Factories @Inject constructor( diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaNetwork.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaNetwork.kt index 275c995ecf..21cc7cb152 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaNetwork.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaNetwork.kt @@ -26,11 +26,9 @@ internal class WcSolanaNetwork( private val moshi: Moshi, private val sessionsManager: WcSessionsManager, private val factories: Factories, - private val excludedBlockchains: ExcludedBlockchains, + private val namespaceConverter: NamespaceConverter, private val walletManager: UserWalletManager, -) : WcNamespaceConverter, WcRequestToUseCaseConverter { - - override val namespaceKey: NamespaceKey = NamespaceKey("solana") +) : WcRequestToUseCaseConverter { override fun toWcMethodName(request: WcSdkSessionRequest): WcSolanaMethodName? { val methodKey = request.request.method @@ -42,7 +40,7 @@ internal class WcSolanaNetwork( val name = toWcMethodName(request) ?: return null val method: WcSolanaMethod = name.toMethod(request) ?: return null val session = sessionsManager.findSessionByTopic(request.topic) ?: return null - val network = toNetwork(request.chainId.orEmpty(), session.wallet) ?: return null + val network = namespaceConverter.toNetwork(request.chainId.orEmpty(), session.wallet) ?: return null val accountAddress = getAccountAddress(network) val context = WcMethodUseCaseContext( session = session, @@ -66,31 +64,38 @@ internal class WcSolanaNetwork( } } - override fun toBlockchain(chainId: CAIP2): Blockchain? { - if (chainId.namespace != namespaceKey.key) return null - return when (chainId.reference) { - MAINNET_CHAIN_ID -> Blockchain.Solana - TESTNET_CHAIN_ID -> Blockchain.SolanaTestnet - else -> null - } - } + internal class NamespaceConverter @Inject constructor( + private val excludedBlockchains: ExcludedBlockchains, + ) : WcNamespaceConverter { - override fun toNetwork(chainId: String, wallet: UserWallet): Network? { - return toNetwork(chainId, wallet, excludedBlockchains) - } + override val namespaceKey: NamespaceKey = NamespaceKey("solana") - override fun toCAIP2(network: Network): CAIP2? { - val blockchain = Blockchain.fromId(network.id.value) - val chainId = when (blockchain) { - Blockchain.Solana -> MAINNET_CHAIN_ID - Blockchain.SolanaTestnet -> TESTNET_CHAIN_ID - else -> null + override fun toBlockchain(chainId: CAIP2): Blockchain? { + if (chainId.namespace != namespaceKey.key) return null + return when (chainId.reference) { + MAINNET_CHAIN_ID -> Blockchain.Solana + TESTNET_CHAIN_ID -> Blockchain.SolanaTestnet + else -> null + } + } + + override fun toNetwork(chainId: String, wallet: UserWallet): Network? { + return toNetwork(chainId, wallet, excludedBlockchains) + } + + override fun toCAIP2(network: Network): CAIP2? { + val blockchain = Blockchain.fromId(network.id.value) + val chainId = when (blockchain) { + Blockchain.Solana -> MAINNET_CHAIN_ID + Blockchain.SolanaTestnet -> TESTNET_CHAIN_ID + else -> null + } + chainId ?: return null + return CAIP2( + namespace = namespaceKey.key, + reference = chainId, + ) } - chainId ?: return null - return CAIP2( - namespace = namespaceKey.key, - reference = chainId, - ) } private fun WcSolanaMethodName.toMethod(request: WcSdkSessionRequest): WcSolanaMethod? { diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/AssociateNetworksDelegate.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/AssociateNetworksDelegate.kt index 15ffca4caf..04906abf44 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/AssociateNetworksDelegate.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/AssociateNetworksDelegate.kt @@ -1,6 +1,7 @@ package com.tangem.data.walletconnect.pair import com.reown.walletkit.client.Wallet +import com.reown.walletkit.client.Wallet.Model.Namespace import com.tangem.data.walletconnect.utils.WcNamespaceConverter import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Network @@ -16,6 +17,16 @@ internal class AssociateNetworksDelegate constructor( private val currenciesRepository: CurrenciesRepository, ) { + suspend fun associate(wallet: UserWallet, namespaces: Map): Set { + val walletNetworks = getWalletNetworks(wallet) + val namespacesSet = namespaces.values.flatMap { proposal -> proposal.chains ?: listOf() }.toSet() + return namespacesSet.mapNotNullTo(mutableSetOf()) { chainId -> + val wcNetwork = namespaceConverters + .firstNotNullOfOrNull { it.toNetwork(chainId, wallet) } ?: return@mapNotNullTo null + walletNetworks.find { network -> wcNetwork.id == network.id } + } + } + @Throws(WcPairError.UnsupportedNetworks::class) suspend fun associate(sessionProposal: Wallet.Model.SessionProposal): Map { val userWallets = getWallets.invokeSync().filter { it.isMultiCurrency } @@ -31,9 +42,7 @@ internal class AssociateNetworksDelegate constructor( requiredNamespaces: Set, optionalNamespaces: Set, ): ProposalNetwork { - val walletNetworks = currenciesRepository.getMultiCurrencyWalletCurrenciesSync(wallet.walletId) - .filterIsInstance() - .map { it.network } + val walletNetworks = getWalletNetworks(wallet) val unknownRequired = mutableSetOf() val missingRequired = mutableSetOf() @@ -74,6 +83,11 @@ internal class AssociateNetworksDelegate constructor( ) } + private suspend fun getWalletNetworks(wallet: UserWallet): List = + currenciesRepository.getMultiCurrencyWalletCurrenciesSync(wallet.walletId) + .filterIsInstance() + .map { it.network } + private fun Map.setOfChainId(): Set = this.values.flatMap { proposal -> proposal.chains ?: listOf() }.toSet() diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/DefaultWcPairUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/DefaultWcPairUseCase.kt index fc91e5eefe..5bcbce241a 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/DefaultWcPairUseCase.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/DefaultWcPairUseCase.kt @@ -14,7 +14,6 @@ import com.tangem.domain.walletconnect.model.sdkcopy.WcAppMetaData import com.tangem.domain.walletconnect.repository.WcSessionsManager import com.tangem.domain.walletconnect.usecase.pair.WcPairState import com.tangem.domain.walletconnect.usecase.pair.WcPairUseCase -import com.tangem.domain.wallets.models.UserWallet import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -78,9 +77,11 @@ internal class DefaultWcPairUseCase @AssistedInject constructor( sessionForApprove = sessionForApprove, sdkSessionProposal = sdkSessionProposal, ).map { settledSession -> - val newSession = settledSession.session.toDomain( + val newSession = WcSession( wallet = sessionForApprove.wallet, + sdkModel = WcSdkSessionConverter.convert(settledSession.session), securityStatus = proposalState.dAppSession.securityStatus, + networks = sessionForApprove.network.toSet(), ) sessionsManager.saveSession(newSession) newSession @@ -141,14 +142,6 @@ internal class DefaultWcPairUseCase @AssistedInject constructor( } },) - private fun Wallet.Model.Session.toDomain(wallet: UserWallet, securityStatus: CheckDAppResult): WcSession { - return WcSession( - wallet = wallet, - sdkModel = WcSdkSessionConverter.convert(this), - securityStatus = securityStatus, - ) - } - private sealed interface TerminalAction { data class Approve(val sessionForApprove: WcSessionApprove) : TerminalAction data object Reject : TerminalAction diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sessions/DefaultWcSessionsManager.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sessions/DefaultWcSessionsManager.kt index 98e554f3fe..1c537ef70d 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sessions/DefaultWcSessionsManager.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sessions/DefaultWcSessionsManager.kt @@ -6,6 +6,7 @@ import arrow.core.right import com.domain.blockaid.models.dapp.CheckDAppResult import com.reown.walletkit.client.Wallet import com.reown.walletkit.client.WalletKit +import com.tangem.data.walletconnect.pair.AssociateNetworksDelegate import com.tangem.data.walletconnect.utils.WcSdkObserver import com.tangem.data.walletconnect.utils.WcSdkSessionConverter import com.tangem.datasource.local.walletconnect.WalletConnectStore @@ -29,6 +30,7 @@ internal class DefaultWcSessionsManager( private val legacyStore: WalletConnectSessionsRepository, private val getWallets: GetWalletsUseCase, private val dispatchers: CoroutineDispatcherProvider, + private val associateNetworks: AssociateNetworksDelegate, private val scope: CoroutineScope, ) : WcSessionsManager, WcSdkObserver { @@ -76,10 +78,12 @@ internal class DefaultWcSessionsManager( ?: return@withContext null val sdkSession = WalletKit.getActiveSessionByTopic(topic) ?: return@withContext null val wallet = storedSession.wallet + val networks = associateNetworks.associate(wallet, sdkSession.namespaces) WcSession( wallet = wallet, sdkModel = WcSdkSessionConverter.convert(sdkSession), securityStatus = storedSession.securityStatus, + networks = networks, ) } @@ -114,7 +118,7 @@ internal class DefaultWcSessionsManager( return mustSaveInNewStore.isNotEmpty() } - private fun associate( + private suspend fun associate( inSdk: List, inStore: Set, wallets: List, @@ -122,7 +126,13 @@ internal class DefaultWcSessionsManager( val wcSessions = inStore.mapNotNull { session -> val wallet = wallets.find { it.walletId == session.walletId } ?: return@mapNotNull null val sdkSession = inSdk.find { it.topic == session.topic } ?: return@mapNotNull null - WcSession(wallet = wallet, sdkModel = WcSdkSessionConverter.convert(sdkSession), session.securityStatus) + val networks = associateNetworks.associate(wallet, sdkSession.namespaces) + WcSession( + wallet = wallet, + sdkModel = WcSdkSessionConverter.convert(sdkSession), + securityStatus = session.securityStatus, + networks = networks, + ) } return wcSessions } diff --git a/data/wallet-connect/src/test/kotlin/com/tangem/domain/walletconnect/DefaultWcPairUseCaseTest.kt b/data/wallet-connect/src/test/kotlin/com/tangem/domain/walletconnect/DefaultWcPairUseCaseTest.kt index 1792340585..6a86ee96f2 100644 --- a/data/wallet-connect/src/test/kotlin/com/tangem/domain/walletconnect/DefaultWcPairUseCaseTest.kt +++ b/data/wallet-connect/src/test/kotlin/com/tangem/domain/walletconnect/DefaultWcPairUseCaseTest.kt @@ -20,6 +20,7 @@ import com.tangem.domain.walletconnect.model.WcSession import com.tangem.domain.walletconnect.model.WcSessionApprove import com.tangem.domain.walletconnect.repository.WcSessionsManager import com.tangem.domain.walletconnect.usecase.pair.WcPairState +import com.tangem.domain.wallets.models.UserWalletId import io.mockk.coEvery import io.mockk.coVerifyOrder import io.mockk.mockk @@ -91,6 +92,7 @@ internal class DefaultWcPairUseCaseTest { wallet = sessionForApprove.wallet, sdkModel = WcSdkSessionConverter.convert(this), securityStatus = CheckDAppResult.SAFE, + networks = setOf(), ) private fun useCaseFactory() = DefaultWcPairUseCase( @@ -99,7 +101,7 @@ internal class DefaultWcPairUseCaseTest { caipNamespaceDelegate = caipNamespaceDelegate, sdkDelegate = sdkDelegate, blockAidVerifier = blockAidVerifier, - pairRequest = WcPairRequest(url, source), + pairRequest = WcPairRequest(userWalletId = UserWalletId(""), uri = url, source = source), ) @Before diff --git a/data/wallets/build.gradle.kts b/data/wallets/build.gradle.kts index e84f33b3bc..a9ca9af06f 100644 --- a/data/wallets/build.gradle.kts +++ b/data/wallets/build.gradle.kts @@ -36,6 +36,7 @@ dependencies { implementation(deps.arrow.core) /** tests */ + testImplementation(projects.domain.models) testImplementation(deps.test.junit) testImplementation(deps.test.coroutine) testImplementation(deps.test.truth) diff --git a/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletsRepository.kt b/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletsRepository.kt index 39096c2551..1b8c62c423 100644 --- a/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletsRepository.kt +++ b/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletsRepository.kt @@ -1,5 +1,8 @@ package com.tangem.data.wallets +import com.tangem.data.wallets.converters.UserWalletRemoteInfoConverter +import com.tangem.data.wallets.converters.WalletIdBodyConverter +import com.tangem.datasource.api.common.AuthProvider import com.tangem.datasource.api.common.response.ApiResponseError.HttpException import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.tangemTech.TangemTechApi @@ -17,7 +20,9 @@ import com.tangem.datasource.local.preferences.utils.getSyncOrDefault import com.tangem.datasource.local.preferences.utils.store import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.wallets.models.SeedPhraseNotificationsStatus +import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.wallets.models.UserWalletRemoteInfo import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.utils.WEEK_MILLIS import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -28,12 +33,14 @@ import kotlinx.coroutines.withContext typealias SeedPhraseNotificationsStatuses = Map +@Suppress("TooManyFunctions") internal class DefaultWalletsRepository( private val appPreferencesStore: AppPreferencesStore, private val tangemTechApi: TangemTechApi, private val userWalletsStore: UserWalletsStore, private val seedPhraseNotificationVisibilityStore: RuntimeStateStore, private val dispatchers: CoroutineDispatcherProvider, + private val authProvider: AuthProvider, ) : WalletsRepository { override suspend fun shouldSaveUserWalletsSync(): Boolean { @@ -254,6 +261,53 @@ internal class DefaultWalletsRepository( } } + override suspend fun setWalletName(walletId: String, walletName: String) = withContext(dispatchers.io) { + tangemTechApi.updateWallet( + walletId = walletId, + body = WalletBody(name = walletName), + ).getOrThrow() + } + + override suspend fun getWalletInfo(walletId: String): UserWalletRemoteInfo = withContext(dispatchers.io) { + UserWalletRemoteInfoConverter.convert( + value = tangemTechApi.getWalletById(walletId).getOrThrow(), + ) + } + + override suspend fun getWalletsInfo(applicationId: String, updateCache: Boolean): List = + withContext(dispatchers.io) { + tangemTechApi.getWallets(applicationId) + .getOrThrow() + .map { walletInfo -> + val userWallet = UserWalletRemoteInfoConverter.convert( + value = walletInfo, + ) + if (updateCache) { + setNotificationsEnabledLocally( + userWalletId = userWallet.walletId, + isEnabled = userWallet.isNotificationsEnabled, + ) + } + userWallet + } + } + + override suspend fun associateWallets(applicationId: String, wallets: List) = + withContext(dispatchers.io) { + val publicKeys = authProvider.getCardsPublicKeys() + val walletsBody = wallets.map { userWallet -> + WalletIdBodyConverter.convert( + userWallet = userWallet, + publicKeys = publicKeys.filterKeys { userWallet.cardsInWallet.contains(it) }, + ) + } + + tangemTechApi.associateApplicationIdWithWallets( + applicationId = applicationId, + body = walletsBody, + ).getOrThrow() + } + private suspend fun loadAndSaveNotificationsEnabled(userWalletId: UserWalletId): Boolean { val walletResponse = tangemTechApi.getWalletById(walletId = userWalletId.stringValue).getOrThrow() val isEnabled = walletResponse.notifyStatus diff --git a/data/wallets/src/main/java/com/tangem/data/wallets/converters/UserWalletRemoteInfoConverter.kt b/data/wallets/src/main/java/com/tangem/data/wallets/converters/UserWalletRemoteInfoConverter.kt new file mode 100644 index 0000000000..4ac595663e --- /dev/null +++ b/data/wallets/src/main/java/com/tangem/data/wallets/converters/UserWalletRemoteInfoConverter.kt @@ -0,0 +1,16 @@ +package com.tangem.data.wallets.converters + +import com.tangem.datasource.api.tangemTech.models.WalletResponse +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.wallets.models.UserWalletRemoteInfo +import com.tangem.utils.converter.Converter + +internal object UserWalletRemoteInfoConverter : Converter { + override fun convert(value: WalletResponse): UserWalletRemoteInfo { + return UserWalletRemoteInfo( + walletId = UserWalletId(value.id), + name = value.name.orEmpty(), + isNotificationsEnabled = value.notifyStatus, + ) + } +} \ No newline at end of file diff --git a/data/wallets/src/main/java/com/tangem/data/wallets/converters/WalletIdBodyConverter.kt b/data/wallets/src/main/java/com/tangem/data/wallets/converters/WalletIdBodyConverter.kt new file mode 100644 index 0000000000..fdf8d28b3b --- /dev/null +++ b/data/wallets/src/main/java/com/tangem/data/wallets/converters/WalletIdBodyConverter.kt @@ -0,0 +1,21 @@ +package com.tangem.data.wallets.converters + +import com.tangem.datasource.api.tangemTech.models.CardInfoBody +import com.tangem.datasource.api.tangemTech.models.WalletIdBody +import com.tangem.domain.wallets.models.UserWallet + +internal object WalletIdBodyConverter { + + fun convert(userWallet: UserWallet, publicKeys: Map): WalletIdBody { + return WalletIdBody( + walletId = userWallet.walletId.stringValue, + name = userWallet.name, + cards = publicKeys.map { + CardInfoBody( + cardId = it.key, + cardPublicKey = it.value, + ) + }, + ) + } +} \ No newline at end of file diff --git a/data/wallets/src/main/java/com/tangem/data/wallets/di/WalletsDataModule.kt b/data/wallets/src/main/java/com/tangem/data/wallets/di/WalletsDataModule.kt index 778257f353..a154dc4f99 100644 --- a/data/wallets/src/main/java/com/tangem/data/wallets/di/WalletsDataModule.kt +++ b/data/wallets/src/main/java/com/tangem/data/wallets/di/WalletsDataModule.kt @@ -2,6 +2,7 @@ package com.tangem.data.wallets.di import com.tangem.data.wallets.DefaultWalletNamesMigrationRepository import com.tangem.data.wallets.DefaultWalletsRepository +import com.tangem.datasource.api.common.AuthProvider import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.local.datastore.RuntimeStateStore import com.tangem.datasource.local.preferences.AppPreferencesStore @@ -26,6 +27,7 @@ internal object WalletsDataModule { tangemTechApi: TangemTechApi, userWalletsStore: UserWalletsStore, dispatchers: CoroutineDispatcherProvider, + authProvider: AuthProvider, ): WalletsRepository { return DefaultWalletsRepository( appPreferencesStore = appPreferencesStore, @@ -33,6 +35,7 @@ internal object WalletsDataModule { userWalletsStore = userWalletsStore, seedPhraseNotificationVisibilityStore = RuntimeStateStore(defaultValue = emptyMap()), dispatchers = dispatchers, + authProvider = authProvider, ) } diff --git a/data/wallets/src/test/java/com/tangem/data/wallets/DefaultWalletsRepositoryTest.kt b/data/wallets/src/test/java/com/tangem/data/wallets/DefaultWalletsRepositoryTest.kt index 28f07454c2..af1295b684 100644 --- a/data/wallets/src/test/java/com/tangem/data/wallets/DefaultWalletsRepositoryTest.kt +++ b/data/wallets/src/test/java/com/tangem/data/wallets/DefaultWalletsRepositoryTest.kt @@ -18,6 +18,8 @@ import io.mockk.coVerify import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import com.google.common.truth.Truth.assertThat +import com.tangem.datasource.api.common.AuthProvider +import com.tangem.domain.wallets.models.UserWallet import org.junit.Before import org.junit.Test @@ -48,6 +50,7 @@ class DefaultWalletsRepositoryTest { userWalletsStore = mockk(), seedPhraseNotificationVisibilityStore = mockk(), dispatchers = dispatchers, + authProvider = mockk(), ) } @@ -173,4 +176,131 @@ class DefaultWalletsRepositoryTest { } coVerify(exactly = 1) { preferencesDataStore.updateData(any()) } } + + @Test + fun `GIVEN API returns wallets WHEN getWalletsInfo THEN should return converted wallets and update cache if requested`() = runTest { + // GIVEN + val applicationId = "test_app_id" + val wallet1Id = "1234567890abcdef" + val wallet2Id = "fedcba0987654321" + val walletResponses = listOf( + WalletResponse( + id = wallet1Id, + notifyStatus = true, + ), + WalletResponse( + id = wallet2Id, + notifyStatus = false, + ), + ) + coEvery { tangemTechApi.getWallets(applicationId) } returns ApiResponse.Success(walletResponses) + coEvery { preferencesDataStore.updateData(any()) } returns mockk() + + // WHEN + val result = repository.getWalletsInfo(applicationId, updateCache = true) + + // THEN + assertThat(result).hasSize(2) + assertThat(result[0].walletId.stringValue).isEqualTo(wallet1Id) + assertThat(result[0].isNotificationsEnabled).isTrue() + assertThat(result[1].walletId.stringValue).isEqualTo(wallet2Id) + assertThat(result[1].isNotificationsEnabled).isFalse() + + coVerify(exactly = 1) { tangemTechApi.getWallets(applicationId) } + coVerify(exactly = 2) { preferencesDataStore.updateData(any()) } + } + + @Test + fun `GIVEN API returns wallets WHEN getWalletsInfo with updateCache false THEN should return converted wallets without updating cache`() = runTest { + // GIVEN + val applicationId = "test_app_id" + val wallet1Id = "1234567890abcdef" + val walletResponses = listOf( + WalletResponse( + id = wallet1Id, + notifyStatus = true, + ), + ) + coEvery { tangemTechApi.getWallets(applicationId) } returns ApiResponse.Success(walletResponses) + + // WHEN + val result = repository.getWalletsInfo(applicationId, updateCache = false) + + // THEN + assertThat(result).hasSize(1) + assertThat(result[0].walletId.stringValue).isEqualTo(wallet1Id) + assertThat(result[0].isNotificationsEnabled).isTrue() + + coVerify(exactly = 1) { tangemTechApi.getWallets(applicationId) } + coVerify(exactly = 0) { preferencesDataStore.updateData(any()) } + } + + @Test + fun `GIVEN user wallets and application ID WHEN associateWallets THEN should convert and send to API`() = runTest { + // GIVEN + val applicationId = "test_app_id" + val wallet1Id = "1234567890abcdef" + val wallet2Id = "fedcba0987654321" + val card1PublicKey = "card1_public_key" + val card2PublicKey = "card2_public_key" + + val userWallets = listOf( + mockk { + every { cardsInWallet } returns setOf(card1PublicKey) + every { walletId } returns UserWalletId(wallet1Id) + every { name } returns "Wallet 1" + }, + mockk { + every { cardsInWallet } returns setOf(card2PublicKey) + every { walletId } returns UserWalletId(wallet2Id) + every { name } returns "Wallet 2" + }, + ) + + val publicKeys = mapOf( + card1PublicKey to "public_key_1", + card2PublicKey to "public_key_2", + ) + + val authProvider = mockk { + every { getCardsPublicKeys() } returns publicKeys + } + + repository = DefaultWalletsRepository( + appPreferencesStore = appPreferenceStore, + tangemTechApi = tangemTechApi, + userWalletsStore = mockk(), + seedPhraseNotificationVisibilityStore = mockk(), + dispatchers = dispatchers, + authProvider = authProvider, + ) + + coEvery { + tangemTechApi.associateApplicationIdWithWallets( + eq(applicationId), + any(), + ) + } returns ApiResponse.Success(Unit) + + // WHEN + repository.associateWallets(applicationId, userWallets) + + // THEN + coVerify(exactly = 1) { + tangemTechApi.associateApplicationIdWithWallets( + eq(applicationId), + match { body -> + body.size == 2 && + body.any { + it.walletId == wallet1Id && it.cards.any { card -> card.cardPublicKey == "public_key_1" } && + it.name == "Wallet 1" + } && + body.any { + it.walletId == wallet2Id && it.cards.any { card -> card.cardPublicKey == "public_key_2" } && + it.name == "Wallet 2" + } + }, + ) + } + } } \ No newline at end of file diff --git a/data/wallets/src/test/java/com/tangem/data/wallets/converters/WalletIdBodyConverterTest.kt b/data/wallets/src/test/java/com/tangem/data/wallets/converters/WalletIdBodyConverterTest.kt new file mode 100644 index 0000000000..2aa327b6d9 --- /dev/null +++ b/data/wallets/src/test/java/com/tangem/data/wallets/converters/WalletIdBodyConverterTest.kt @@ -0,0 +1,80 @@ +package com.tangem.data.wallets.converters + +import com.tangem.datasource.api.tangemTech.models.CardInfoBody +import com.tangem.datasource.api.tangemTech.models.WalletIdBody +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.models.UserWalletId +import com.google.common.truth.Truth.assertThat +import io.mockk.mockk +import org.junit.Test + +class WalletIdBodyConverterTest { + + @Test + fun `GIVEN user wallet with cards WHEN convert THEN should return correct WalletIdBody`() { + // GIVEN + val walletId = UserWalletId("1234567890abcdef") + val walletName = "Test Wallet" + val userWallet = UserWallet( + walletId = walletId, + name = walletName, + cardsInWallet = setOf("card1", "card2"), + isMultiCurrency = true, + hasBackupError = false, + scanResponse = mockk(), + ) + val publicKeys = mapOf( + "card1" to "public_key_1", + "card2" to "public_key_2", + ) + + // WHEN + val result = WalletIdBodyConverter.convert(userWallet, publicKeys) + + // THEN + assertThat(result).isEqualTo( + WalletIdBody( + walletId = walletId.stringValue, + name = walletName, + cards = listOf( + CardInfoBody( + cardId = "card1", + cardPublicKey = "public_key_1", + ), + CardInfoBody( + cardId = "card2", + cardPublicKey = "public_key_2", + ), + ), + ), + ) + } + + @Test + fun `GIVEN user wallet without cards WHEN convert THEN should return WalletIdBody with empty cards list`() { + // GIVEN + val walletId = UserWalletId("1234567890abcdef") + val walletName = "Test Wallet" + val userWallet = UserWallet( + walletId = walletId, + name = walletName, + cardsInWallet = emptySet(), + isMultiCurrency = true, + hasBackupError = false, + scanResponse = mockk(), + ) + val publicKeys = emptyMap() + + // WHEN + val result = WalletIdBodyConverter.convert(userWallet, publicKeys) + + // THEN + assertThat(result).isEqualTo( + WalletIdBody( + walletId = walletId.stringValue, + name = walletName, + cards = emptyList(), + ), + ) + } +} \ No newline at end of file diff --git a/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/SaveManagedTokensUseCase.kt b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/SaveManagedTokensUseCase.kt index 0c83963864..29cddff1c0 100644 --- a/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/SaveManagedTokensUseCase.kt +++ b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/SaveManagedTokensUseCase.kt @@ -14,7 +14,6 @@ import com.tangem.domain.tokens.TokensFeatureToggles import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.repository.CurrenciesRepository -import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.models.UserWalletId @@ -24,7 +23,6 @@ class SaveManagedTokensUseCase( private val customTokensRepository: CustomTokensRepository, private val walletManagersFacade: WalletManagersFacade, private val currenciesRepository: CurrenciesRepository, - private val networksRepository: NetworksRepository, private val derivationsRepository: DerivationsRepository, private val stakingRepository: StakingRepository, private val quotesRepository: QuotesRepository, @@ -102,20 +100,12 @@ class SaveManagedTokensUseCase( val networkToUpdate = currenciesToAdd.map { it.network } .subtract(existingCurrencies.map { it.network }.toSet()) - if (tokensFeatureToggles.isNetworksLoadingRefactoringEnabled) { - multiNetworkStatusFetcher( - MultiNetworkStatusFetcher.Params( - userWalletId = userWalletId, - networks = networksToUpdate + networkToUpdate, - ), - ) - } else { - networksRepository.getNetworkStatusesSync( + multiNetworkStatusFetcher( + MultiNetworkStatusFetcher.Params( userWalletId = userWalletId, networks = networksToUpdate + networkToUpdate, - refresh = true, - ) - } + ), + ) } private suspend fun refreshUpdatedYieldBalances( diff --git a/domain/markets/src/main/java/com/tangem/domain/markets/SaveMarketTokensUseCase.kt b/domain/markets/src/main/java/com/tangem/domain/markets/SaveMarketTokensUseCase.kt index 1d0215f044..cbc09a8700 100644 --- a/domain/markets/src/main/java/com/tangem/domain/markets/SaveMarketTokensUseCase.kt +++ b/domain/markets/src/main/java/com/tangem/domain/markets/SaveMarketTokensUseCase.kt @@ -12,7 +12,6 @@ import com.tangem.domain.tokens.TokensFeatureToggles import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.repository.CurrenciesRepository -import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.wallets.models.UserWalletId @@ -30,7 +29,6 @@ class SaveMarketTokensUseCase( private val derivationsRepository: DerivationsRepository, private val marketsTokenRepository: MarketsTokenRepository, private val currenciesRepository: CurrenciesRepository, - private val networksRepository: NetworksRepository, private val stakingRepository: StakingRepository, private val quotesRepository: QuotesRepository, private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher, @@ -83,20 +81,12 @@ class SaveMarketTokensUseCase( } private suspend fun refreshUpdatedNetworks(userWalletId: UserWalletId, addedCurrencies: List) { - if (tokensFeatureToggles.isNetworksLoadingRefactoringEnabled) { - multiNetworkStatusFetcher( - MultiNetworkStatusFetcher.Params( - userWalletId = userWalletId, - networks = addedCurrencies.map(CryptoCurrency::network).toSet(), - ), - ) - } else { - networksRepository.getNetworkStatusesSync( + multiNetworkStatusFetcher( + MultiNetworkStatusFetcher.Params( userWalletId = userWalletId, networks = addedCurrencies.map(CryptoCurrency::network).toSet(), - refresh = true, - ) - } + ), + ) } private suspend fun refreshUpdatedYieldBalances( diff --git a/domain/notifications/models/src/main/java/com/tangem/domain/notifications/models/ApplicationId.kt b/domain/notifications/models/src/main/java/com/tangem/domain/notifications/models/ApplicationId.kt new file mode 100644 index 0000000000..62db4696b3 --- /dev/null +++ b/domain/notifications/models/src/main/java/com/tangem/domain/notifications/models/ApplicationId.kt @@ -0,0 +1,4 @@ +package com.tangem.domain.notifications.models + +@JvmInline +value class ApplicationId(val value: String) \ No newline at end of file diff --git a/domain/notifications/src/main/java/com/tangem/domain/notifications/GetApplicationIdUseCase.kt b/domain/notifications/src/main/java/com/tangem/domain/notifications/GetApplicationIdUseCase.kt index 026d3a89c6..963d3b99d7 100644 --- a/domain/notifications/src/main/java/com/tangem/domain/notifications/GetApplicationIdUseCase.kt +++ b/domain/notifications/src/main/java/com/tangem/domain/notifications/GetApplicationIdUseCase.kt @@ -1,6 +1,7 @@ package com.tangem.domain.notifications import arrow.core.Either +import com.tangem.domain.notifications.models.ApplicationId import com.tangem.domain.notifications.repository.NotificationsRepository import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -10,7 +11,7 @@ class GetApplicationIdUseCase( ) { private val mutex = Mutex() - suspend operator fun invoke(): Either = Either.catch { + suspend operator fun invoke(): Either = Either.catch { val localApplicationId = notificationsRepository.getApplicationId() if (localApplicationId != null) return@catch localApplicationId diff --git a/domain/notifications/src/main/java/com/tangem/domain/notifications/repository/NotificationsRepository.kt b/domain/notifications/src/main/java/com/tangem/domain/notifications/repository/NotificationsRepository.kt index b7f53ab51e..a8c46854df 100644 --- a/domain/notifications/src/main/java/com/tangem/domain/notifications/repository/NotificationsRepository.kt +++ b/domain/notifications/src/main/java/com/tangem/domain/notifications/repository/NotificationsRepository.kt @@ -1,31 +1,23 @@ package com.tangem.domain.notifications.repository +import com.tangem.domain.notifications.models.ApplicationId import com.tangem.domain.notifications.models.NotificationsEligibleNetwork interface NotificationsRepository { @Throws - suspend fun createApplicationId(pushToken: String? = null): String + suspend fun createApplicationId(pushToken: String? = null): ApplicationId - suspend fun saveApplicationId(appId: String) + suspend fun saveApplicationId(appId: ApplicationId) - suspend fun getApplicationId(): String? + suspend fun getApplicationId(): ApplicationId? suspend fun getTronTokenFeeNotificationShowCounter(): Int suspend fun incrementTronTokenFeeNotificationShowCounter() @Throws - suspend fun associateApplicationIdWithWallets(appId: String, wallets: List) - - @Throws - suspend fun setWalletName(walletId: String, walletName: String) - - @Throws - suspend fun getWalletName(walletId: String): String? - - @Throws - suspend fun sendPushToken(appId: String, pushToken: String) + suspend fun sendPushToken(appId: ApplicationId, pushToken: String) @Throws suspend fun getEligibleNetworks(): List diff --git a/domain/notifications/src/test/java/com/tangem/domain/notifications/GetApplicationIdUseCaseTest.kt b/domain/notifications/src/test/java/com/tangem/domain/notifications/GetApplicationIdUseCaseTest.kt index 350f5bad73..6b2984554b 100644 --- a/domain/notifications/src/test/java/com/tangem/domain/notifications/GetApplicationIdUseCaseTest.kt +++ b/domain/notifications/src/test/java/com/tangem/domain/notifications/GetApplicationIdUseCaseTest.kt @@ -2,6 +2,7 @@ package com.tangem.domain.notifications import arrow.core.Either import com.google.common.truth.Truth.assertThat +import com.tangem.domain.notifications.models.ApplicationId import com.tangem.domain.notifications.repository.NotificationsRepository import io.mockk.coEvery import io.mockk.coVerify @@ -20,7 +21,7 @@ class GetApplicationIdUseCaseTest { @Test fun `GIVEN local application ID exists WHEN invoke THEN return local application ID`() = runTest { // GIVEN - val expectedApplicationId = "test-app-id" + val expectedApplicationId = ApplicationId("test-app-id") coEvery { notificationsRepository.getApplicationId() } returns expectedApplicationId // WHEN @@ -39,7 +40,7 @@ class GetApplicationIdUseCaseTest { @Test fun `GIVEN local application ID does not exist WHEN invoke THEN create and save new application ID`() = runTest { // GIVEN - val newApplicationId = "new-app-id" + val newApplicationId = ApplicationId("new-app-id") coEvery { notificationsRepository.getApplicationId() } returns null coEvery { notificationsRepository.createApplicationId() } returns newApplicationId coEvery { notificationsRepository.saveApplicationId(newApplicationId) } returns Unit @@ -81,7 +82,7 @@ class GetApplicationIdUseCaseTest { fun `GIVEN no local application ID WHEN multiple concurrent invokes THEN create only one application ID`() = runTest { // GIVEN - val newApplicationId = "new-app-id" + val newApplicationId = ApplicationId("new-app-id") var isIdCreated = false coEvery { notificationsRepository.getApplicationId() } answers { @@ -115,7 +116,7 @@ class GetApplicationIdUseCaseTest { @Test fun `GIVEN no local application ID WHEN multiple concurrent invokes with delay THEN create only one application ID`() = runTest { // GIVEN - val newApplicationId = "new-app-id" + val newApplicationId = ApplicationId("new-app-id") var isIdCreated = false coEvery { notificationsRepository.getApplicationId() } answers { diff --git a/domain/notifications/src/test/java/com/tangem/domain/notifications/SendPushTokenUseCaseTest.kt b/domain/notifications/src/test/java/com/tangem/domain/notifications/SendPushTokenUseCaseTest.kt index aeb18310a1..f079a021d1 100644 --- a/domain/notifications/src/test/java/com/tangem/domain/notifications/SendPushTokenUseCaseTest.kt +++ b/domain/notifications/src/test/java/com/tangem/domain/notifications/SendPushTokenUseCaseTest.kt @@ -2,6 +2,7 @@ package com.tangem.domain.notifications import arrow.core.Either import com.google.common.truth.Truth.assertThat +import com.tangem.domain.notifications.models.ApplicationId import com.tangem.domain.notifications.repository.NotificationsRepository import com.tangem.utils.notifications.PushNotificationsTokenProvider import io.mockk.coEvery @@ -33,7 +34,7 @@ class SendPushTokenUseCaseTest { @Test fun `GIVEN valid application ID and token WHEN invoke THEN token is sent successfully`() = runTest { // GIVEN - val applicationId = "test-app-id" + val applicationId = ApplicationId("test-app-id") val token = "test-token" coEvery { getApplicationIdUseCase() } returns Either.Right(applicationId) coEvery { pushNotificationsTokenProvider.getToken() } returns token @@ -62,7 +63,7 @@ class SendPushTokenUseCaseTest { @Test fun `GIVEN repository throws error WHEN invoke THEN returns error`() = runTest { // GIVEN - val applicationId = "test-app-id" + val applicationId = ApplicationId("test-app-id") val token = "test-token" val expectedError = RuntimeException("Network error") coEvery { getApplicationIdUseCase() } returns Either.Right(applicationId) diff --git a/domain/quotes/src/main/java/com/tangem/domain/quotes/single/SingleQuoteFetcher.kt b/domain/quotes/src/main/java/com/tangem/domain/quotes/single/SingleQuoteFetcher.kt new file mode 100644 index 0000000000..6923e9a50e --- /dev/null +++ b/domain/quotes/src/main/java/com/tangem/domain/quotes/single/SingleQuoteFetcher.kt @@ -0,0 +1,22 @@ +package com.tangem.domain.quotes.single + +import com.tangem.domain.core.flow.FlowFetcher +import com.tangem.domain.tokens.model.CryptoCurrency + +/** + * Fetcher of quote for [CryptoCurrency.RawID] + * +[REDACTED_AUTHOR] + */ +interface SingleQuoteFetcher : FlowFetcher { + + /** + * Params + * + * @property rawCurrencyId crypto currency id + */ + data class Params( + val rawCurrencyId: CryptoCurrency.RawID, + val appCurrencyId: String?, + ) +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/AddCryptoCurrenciesUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/AddCryptoCurrenciesUseCase.kt index ca0fecd294..a8bb9a147e 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/AddCryptoCurrenciesUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/AddCryptoCurrenciesUseCase.kt @@ -12,7 +12,6 @@ import com.tangem.domain.staking.single.SingleYieldBalanceFetcher import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.repository.CurrenciesRepository -import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.async @@ -29,7 +28,6 @@ import kotlinx.coroutines.coroutineScope @Suppress("LongParameterList") class AddCryptoCurrenciesUseCase( private val currenciesRepository: CurrenciesRepository, - private val networksRepository: NetworksRepository, private val stakingRepository: StakingRepository, private val quotesRepository: QuotesRepository, private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher, @@ -139,26 +137,13 @@ class AddCryptoCurrenciesUseCase( } ?.network - if (tokensFeatureToggles.isNetworksLoadingRefactoringEnabled) { - multiNetworkStatusFetcher( - MultiNetworkStatusFetcher.Params( - userWalletId = userWalletId, - networks = setOfNotNull(networksToUpdate, networkToUpdate), - ), - ) - } else { - catch( - { - networksRepository.getNetworkStatusesSync( - userWalletId = userWalletId, - networks = setOfNotNull(networksToUpdate, networkToUpdate), - refresh = true, - ) - }, - ) { - raise(it) - } - } + multiNetworkStatusFetcher( + MultiNetworkStatusFetcher.Params( + userWalletId = userWalletId, + networks = setOfNotNull(networksToUpdate, networkToUpdate), + ), + ) + .bind() } private suspend fun refreshUpdatedYieldBalances(userWalletId: UserWalletId, addedCurrency: CryptoCurrency) { diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchCardTokenListUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchCardTokenListUseCase.kt index 7d56e24a4b..1019ef739e 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchCardTokenListUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchCardTokenListUseCase.kt @@ -13,7 +13,6 @@ import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.repository.CurrenciesRepository -import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.async @@ -23,7 +22,6 @@ import kotlinx.coroutines.coroutineScope @Suppress("LongParameterList") class FetchCardTokenListUseCase( private val currenciesRepository: CurrenciesRepository, - private val networksRepository: NetworksRepository, private val quotesRepository: QuotesRepository, private val stakingRepository: StakingRepository, private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher, @@ -41,7 +39,6 @@ class FetchCardTokenListUseCase( fetchNetworksStatuses( userWalletId = userWalletId, networks = currencies.mapTo(destination = hashSetOf(), transform = CryptoCurrency::network), - refresh = refresh, ) } val fetchQuotes = async { @@ -80,19 +77,12 @@ class FetchCardTokenListUseCase( private suspend fun Raise.fetchNetworksStatuses( userWalletId: UserWalletId, networks: Set, - refresh: Boolean, ) { - if (tokensFeatureToggles.isNetworksLoadingRefactoringEnabled) { - multiNetworkStatusFetcher( - MultiNetworkStatusFetcher.Params(userWalletId = userWalletId, networks = networks), - ) - .mapLeft { TokenListError.DataError(it) } - } else { - catch( - block = { networksRepository.getNetworkStatusesSync(userWalletId, networks, refresh) }, - catch = { raise(TokenListError.DataError(it)) }, - ) - } + multiNetworkStatusFetcher( + MultiNetworkStatusFetcher.Params(userWalletId = userWalletId, networks = networks), + ) + .mapLeft(TokenListError::DataError) + .bind() } private suspend fun fetchQuotes(currenciesIds: Set, refresh: Boolean) { diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchCurrencyStatusUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchCurrencyStatusUseCase.kt index fd282510f7..303758d938 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchCurrencyStatusUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchCurrencyStatusUseCase.kt @@ -13,7 +13,6 @@ import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.repository.CurrenciesRepository -import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.async @@ -26,14 +25,12 @@ import kotlinx.coroutines.coroutineScope * by providing a specific currency ID or fetching the status of the primary currency. * * @param currenciesRepository The repository for retrieving currency-related data. - * @param networksRepository The repository for retrieving network-related data. * @param quotesRepository The repository for retrieving cryptocurrency quotes. */ // TODO: Add tests @Suppress("LongParameterList") class FetchCurrencyStatusUseCase( private val currenciesRepository: CurrenciesRepository, - private val networksRepository: NetworksRepository, private val quotesRepository: QuotesRepository, private val stakingRepository: StakingRepository, private val singleNetworkStatusFetcher: SingleNetworkStatusFetcher, @@ -86,7 +83,7 @@ class FetchCurrencyStatusUseCase( refresh: Boolean, ) = coroutineScope { val fetchStatus = async { - fetchNetworkStatus(userWalletId, currency.network, refresh) + fetchNetworkStatus(userWalletId, currency.network) } val fetchQuote = async { fetchQuote(currency.id, refresh) @@ -120,23 +117,12 @@ class FetchCurrencyStatusUseCase( } } - private suspend fun Raise.fetchNetworkStatus( - userWalletId: UserWalletId, - network: Network, - refresh: Boolean, - ) { - if (tokensFeatureToggles.isNetworksLoadingRefactoringEnabled) { - singleNetworkStatusFetcher( - params = SingleNetworkStatusFetcher.Params.Simple(userWalletId = userWalletId, network = network), - ) - .mapLeft { CurrencyStatusError.DataError(it) } - } else { - catch( - block = { networksRepository.getNetworkStatusesSync(userWalletId, setOf(network), refresh) }, - ) { - raise(CurrencyStatusError.DataError(it)) - } - } + private suspend fun Raise.fetchNetworkStatus(userWalletId: UserWalletId, network: Network) { + singleNetworkStatusFetcher( + params = SingleNetworkStatusFetcher.Params.Simple(userWalletId = userWalletId, network = network), + ) + .mapLeft { CurrencyStatusError.DataError(it) } + .bind() } private suspend fun Raise.fetchQuote(currencyId: CryptoCurrency.ID, refresh: Boolean) { diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchTokenListUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchTokenListUseCase.kt index e0300eb83a..3061b1152d 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchTokenListUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchTokenListUseCase.kt @@ -15,7 +15,6 @@ import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.repository.CurrenciesRepository -import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.async @@ -27,7 +26,6 @@ import kotlinx.coroutines.coroutineScope * network statuses, and quotes for tokens associated with a user's wallet. * * @param currenciesRepository The repository for retrieving currency-related data. - * @param networksRepository The repository for retrieving network-related data. * @param quotesRepository The repository for retrieving cryptocurrency quotes. * @param stakingRepository The repository for retrieving staking-related data. */ @@ -35,7 +33,6 @@ import kotlinx.coroutines.coroutineScope @Suppress("LongParameterList") class FetchTokenListUseCase( private val currenciesRepository: CurrenciesRepository, - private val networksRepository: NetworksRepository, private val quotesRepository: QuotesRepository, private val stakingRepository: StakingRepository, private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher, @@ -71,7 +68,6 @@ class FetchTokenListUseCase( fetchNetworksStatuses( userWalletId = userWalletId, networks = currencies.mapTo(hashSetOf()) { it.network }, - refresh = mode.refreshNetworksStatuses, ) } val fetchQuotes = async { @@ -111,23 +107,12 @@ class FetchTokenListUseCase( private suspend fun Raise.fetchNetworksStatuses( userWalletId: UserWalletId, networks: Set, - refresh: Boolean, ) { - if (tokensFeatureToggles.isNetworksLoadingRefactoringEnabled) { - if (refresh) { - multiNetworkStatusFetcher( - params = MultiNetworkStatusFetcher.Params(userWalletId, networks), - ) - .mapLeft { TokenListError.DataError(it) } - .bind() - } - } else { - catch( - block = { networksRepository.getNetworkStatusesSync(userWalletId, networks, refresh) }, - ) { - raise(TokenListError.DataError(it)) - } - } + multiNetworkStatusFetcher( + params = MultiNetworkStatusFetcher.Params(userWalletId = userWalletId, networks = networks), + ) + .mapLeft(TokenListError::DataError) + .bind() } private suspend fun fetchQuotes(currenciesIds: Set, refresh: Boolean) { @@ -175,25 +160,21 @@ class FetchTokenListUseCase( */ enum class RefreshMode( internal val refreshCurrencies: Boolean, - internal val refreshNetworksStatuses: Boolean, internal val refreshQuotes: Boolean, internal val refreshYieldBalances: Boolean, ) { NONE( refreshCurrencies = false, - refreshNetworksStatuses = false, refreshQuotes = false, refreshYieldBalances = false, ), FULL( refreshCurrencies = true, - refreshNetworksStatuses = true, refreshQuotes = true, refreshYieldBalances = true, ), SKIP_CURRENCIES( refreshCurrencies = false, - refreshNetworksStatuses = true, refreshQuotes = true, refreshYieldBalances = true, ), diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyStatusSyncUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyStatusSyncUseCase.kt deleted file mode 100644 index 20354b45c4..0000000000 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyStatusSyncUseCase.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.tangem.domain.tokens - -import arrow.core.Either -import com.tangem.domain.tokens.error.CurrencyStatusError -import com.tangem.domain.tokens.error.mapper.mapToCurrencyError -import com.tangem.domain.tokens.model.CryptoCurrency -import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations -import com.tangem.domain.wallets.models.UserWalletId - -class GetCryptoCurrencyStatusSyncUseCase( - private val currencyStatusOperations: BaseCurrencyStatusOperations, -) { - - // multi-currency - suspend operator fun invoke( - userWalletId: UserWalletId, - cryptoCurrencyId: CryptoCurrency.ID, - isSingleWalletWithTokens: Boolean = false, - ): Either { - return currencyStatusOperations.getCurrencyStatusSync(userWalletId, cryptoCurrencyId, isSingleWalletWithTokens) - .mapLeft { error -> error.mapToCurrencyError() } - } - - // single-currency - suspend operator fun invoke(userWalletId: UserWalletId): Either { - return currencyStatusOperations.getPrimaryCurrencyStatusSync(userWalletId) - .mapLeft { error -> error.mapToCurrencyError() } - } -} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyStatusUpdatesUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyStatusUpdatesUseCase.kt deleted file mode 100644 index 0595b091fe..0000000000 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyStatusUpdatesUseCase.kt +++ /dev/null @@ -1,62 +0,0 @@ -package com.tangem.domain.tokens - -import arrow.core.Either -import com.tangem.domain.tokens.error.CurrencyStatusError -import com.tangem.domain.tokens.error.mapper.mapToCurrencyError -import com.tangem.domain.tokens.model.CryptoCurrency -import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations -import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations -import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.coroutines.flow.* - -/** - * Use case for fetching the status of a cryptocurrency associated with a user wallet. - * - */ -class GetCurrencyStatusUpdatesUseCase( - private val currencyStatusOperations: BaseCurrencyStatusOperations, - private val dispatchers: CoroutineDispatcherProvider, -) { - - /** - * Invokes the use case. - * - * @param userWalletId The unique identifier of the user's wallet. - * @param currencyId The unique identifier of the cryptocurrency. - * @param isSingleWalletWithTokens Indicates whether the user wallet contains only one token on card (old cards) - * @return A [Flow] emitting either a [CurrencyStatusError] or a [CryptoCurrencyStatus], indicating the result of the fetch operation. - */ - operator fun invoke( - userWalletId: UserWalletId, - currencyId: CryptoCurrency.ID, - isSingleWalletWithTokens: Boolean, - ): Flow> { - return flow { - emitAll( - getCurrencyStatus( - userWalletId = userWalletId, - currencyId = currencyId, - isSingleWalletWithTokens = isSingleWalletWithTokens, - ), - ) - }.flowOn(dispatchers.io) - } - - private suspend fun getCurrencyStatus( - userWalletId: UserWalletId, - currencyId: CryptoCurrency.ID, - isSingleWalletWithTokens: Boolean, - ): Flow> { - val currencyFlow = currencyStatusOperations.getCurrencyStatusFlow( - userWalletId = userWalletId, - currencyId = currencyId, - isSingleWalletWithTokens = isSingleWalletWithTokens, - ) - - return currencyFlow.map { maybeCurrency -> - maybeCurrency.mapLeft(CurrenciesStatusesOperations.Error::mapToCurrencyError) - } - } -} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyStatusesSyncUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetMultiCryptoCurrencyStatusUseCase.kt similarity index 50% rename from domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyStatusesSyncUseCase.kt rename to domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetMultiCryptoCurrencyStatusUseCase.kt index 590cb6caa0..56b50913c1 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyStatusesSyncUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetMultiCryptoCurrencyStatusUseCase.kt @@ -1,17 +1,25 @@ package com.tangem.domain.tokens import arrow.core.Either +import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.error.mapper.mapToTokenListError import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.Flow -class GetCryptoCurrencyStatusesSyncUseCase( +class GetMultiCryptoCurrencyStatusUseCase( private val currencyStatusOperations: BaseCurrencyStatusOperations, ) { - suspend operator fun invoke(userWalletId: UserWalletId): Either> { + /** + * Returns synchronously list of cryptocurrency statuses for Multi-Currency wallet + * + * @param userWalletId The unique identifier of the user's wallet. + * @return A [Flow] emitting either a [CurrencyStatusError] or a [CryptoCurrencyStatus], indicating the result of the fetch operation. + */ + suspend fun invokeMultiWalletSync(userWalletId: UserWalletId): Either> { return currencyStatusOperations.getCurrenciesStatusesSync(userWalletId) .mapLeft { error -> error.mapToTokenListError() } } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyStatusUpdatesUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyStatusUpdatesUseCase.kt deleted file mode 100644 index 000411bff6..0000000000 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyStatusUpdatesUseCase.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.tangem.domain.tokens - -import arrow.core.Either -import com.tangem.domain.tokens.error.CurrencyStatusError -import com.tangem.domain.tokens.error.mapper.mapToCurrencyError -import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations -import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations -import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.coroutines.flow.* - -/** - * Use case for fetching the status of the primary cryptocurrency associated with a user wallet. - * - * @property dispatchers Provides coroutine dispatchers. - */ -class GetPrimaryCurrencyStatusUpdatesUseCase( - private val dispatchers: CoroutineDispatcherProvider, - private val currencyStatusOperations: BaseCurrencyStatusOperations, -) { - - /** - * Invokes the use case. - * - * @param userWalletId The unique identifier of the user's wallet. - * @return A [Flow] emitting either a [CurrencyStatusError] or a [CryptoCurrencyStatus], indicating the result of the fetch operation. - */ - operator fun invoke(userWalletId: UserWalletId): Flow> { - return flow { - emitAll(getPrimaryCurrency(userWalletId)) - }.flowOn(dispatchers.io) - } - - private suspend fun getPrimaryCurrency( - userWalletId: UserWalletId, - ): Flow> { - return currencyStatusOperations.getPrimaryCurrencyStatusFlow(userWalletId).map { maybeCurrency -> - maybeCurrency.mapLeft(CurrenciesStatusesOperations.Error::mapToCurrencyError) - } - } -} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetSingleCryptoCurrencyStatusUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetSingleCryptoCurrencyStatusUseCase.kt new file mode 100644 index 0000000000..928221f31b --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetSingleCryptoCurrencyStatusUseCase.kt @@ -0,0 +1,106 @@ +package com.tangem.domain.tokens + +import arrow.core.Either +import com.tangem.domain.tokens.error.CurrencyStatusError +import com.tangem.domain.tokens.error.mapper.mapToCurrencyError +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations +import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.* + +/** + * Use case for fetching the status of a cryptocurrency associated with a user wallet. + * + */ +class GetSingleCryptoCurrencyStatusUseCase( + private val currencyStatusOperations: BaseCurrencyStatusOperations, + private val dispatchers: CoroutineDispatcherProvider, +) { + + /** + * Returns cryptocurrency status flow for Multi-Currency wallet + * + * @param userWalletId The unique identifier of the user's wallet. + * @param currencyId The unique identifier of the cryptocurrency. + * @param isSingleWalletWithTokens Indicates whether the user wallet contains only one token on card (old cards) + * @return A [Flow] emitting either a [CurrencyStatusError] or a [CryptoCurrencyStatus], indicating the result of the fetch operation. + */ + fun invokeMultiWallet( + userWalletId: UserWalletId, + currencyId: CryptoCurrency.ID, + isSingleWalletWithTokens: Boolean, + ): Flow> { + return flow { + emitAll( + getCurrencyStatus( + userWalletId = userWalletId, + currencyId = currencyId, + isSingleWalletWithTokens = isSingleWalletWithTokens, + ), + ) + }.flowOn(dispatchers.io) + } + + /** + * Returns cryptocurrency status flow for primary currency for Single-Currency wallet + * + * @param userWalletId The unique identifier of the user's wallet. + * @return A [Flow] emitting either a [CurrencyStatusError] or a [CryptoCurrencyStatus], indicating the result of the fetch operation. + */ + fun invokeSingleWallet(userWalletId: UserWalletId): Flow> { + return flow { + emitAll( + currencyStatusOperations.getPrimaryCurrencyStatusFlow(userWalletId).map { maybeCurrency -> + maybeCurrency.mapLeft(CurrenciesStatusesOperations.Error::mapToCurrencyError) + }, + ) + }.flowOn(dispatchers.io) + } + + /** + * Returns synchronously cryptocurrency status for Multi-Currency wallet + * + * @param userWalletId The unique identifier of the user's wallet. + * @param cryptoCurrencyId The unique identifier of the cryptocurrency. + * @param isSingleWalletWithTokens Indicates whether the user wallet contains only one token on card (old cards) + * @return A [Flow] emitting either a [CurrencyStatusError] or a [CryptoCurrencyStatus], indicating the result of the fetch operation. + */ + suspend fun invokeMultiWalletSync( + userWalletId: UserWalletId, + cryptoCurrencyId: CryptoCurrency.ID, + isSingleWalletWithTokens: Boolean = false, + ): Either { + return currencyStatusOperations.getCurrencyStatusSync(userWalletId, cryptoCurrencyId, isSingleWalletWithTokens) + .mapLeft { error -> error.mapToCurrencyError() } + } + + /** + * Returns synchronously cryptocurrency status for primary currency for Single-Currency wallet + * + * @param userWalletId The unique identifier of the user's wallet. + * @return A [Flow] emitting either a [CurrencyStatusError] or a [CryptoCurrencyStatus], indicating the result of the fetch operation. + */ + suspend fun invokeSingleWalletSync(userWalletId: UserWalletId): Either { + return currencyStatusOperations.getPrimaryCurrencyStatusSync(userWalletId) + .mapLeft { error -> error.mapToCurrencyError() } + } + + private suspend fun getCurrencyStatus( + userWalletId: UserWalletId, + currencyId: CryptoCurrency.ID, + isSingleWalletWithTokens: Boolean, + ): Flow> { + val currencyFlow = currencyStatusOperations.getCurrencyStatusFlow( + userWalletId = userWalletId, + currencyId = currencyId, + isSingleWalletWithTokens = isSingleWalletWithTokens, + ) + + return currencyFlow.map { maybeCurrency -> + maybeCurrency.mapLeft(CurrenciesStatusesOperations.Error::mapToCurrencyError) + } + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/TokensFeatureToggles.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/TokensFeatureToggles.kt index be40961b10..b969947239 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/TokensFeatureToggles.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/TokensFeatureToggles.kt @@ -7,8 +7,6 @@ package com.tangem.domain.tokens */ interface TokensFeatureToggles { - val isNetworksLoadingRefactoringEnabled: Boolean - val isQuotesLoadingRefactoringEnabled: Boolean val isStakingLoadingRefactoringEnabled: Boolean diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/UpdateDelayedNetworkStatusUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/UpdateDelayedNetworkStatusUseCase.kt index 6626764234..ada4cab157 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/UpdateDelayedNetworkStatusUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/UpdateDelayedNetworkStatusUseCase.kt @@ -2,12 +2,10 @@ package com.tangem.domain.tokens import arrow.core.Either import arrow.core.raise.Raise -import arrow.core.raise.catch import arrow.core.raise.either import com.tangem.domain.networks.single.SingleNetworkStatusFetcher import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.model.Network -import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.delay @@ -15,14 +13,9 @@ import kotlinx.coroutines.delay * Use case responsible for fetching currency status information, including network status * and quotes for a given cryptocurrency. It provides methods to fetch currency status either * by providing a specific currency ID or fetching the status of the primary currency. - * - * @param networksRepository The repository for retrieving network-related data. */ - class UpdateDelayedNetworkStatusUseCase( - private val networksRepository: NetworksRepository, private val singleNetworkStatusFetcher: SingleNetworkStatusFetcher, - private val tokensFeatureToggles: TokensFeatureToggles, ) { /** @@ -30,37 +23,24 @@ class UpdateDelayedNetworkStatusUseCase( * * @param userWalletId The ID of the user's wallet. * @param network Network of the cryptocurrency. - * @param refresh Indicates whether to force a refresh of the status data. * @return An [Either] representing success (Right) or an error (Left) in fetching the status. */ suspend operator fun invoke( userWalletId: UserWalletId, network: Network, delayMillis: Long = 0L, - refresh: Boolean = false, ): Either { delay(delayMillis) return either { - fetchNetworkStatus(userWalletId, network, refresh) + fetchNetworkStatus(userWalletId, network) } } - private suspend fun Raise.fetchNetworkStatus( - userWalletId: UserWalletId, - network: Network, - refresh: Boolean, - ) { - if (tokensFeatureToggles.isNetworksLoadingRefactoringEnabled) { - singleNetworkStatusFetcher( - params = SingleNetworkStatusFetcher.Params.Simple(userWalletId = userWalletId, network = network), - ) - .mapLeft { CurrencyStatusError.DataError(it) } - } else { - catch( - block = { networksRepository.getNetworkStatusesSync(userWalletId, setOf(network), refresh) }, - ) { - raise(CurrencyStatusError.DataError(it)) - } - } + private suspend fun Raise.fetchNetworkStatus(userWalletId: UserWalletId, network: Network) { + singleNetworkStatusFetcher( + params = SingleNetworkStatusFetcher.Params.Simple(userWalletId = userWalletId, network = network), + ) + .mapLeft(CurrencyStatusError::DataError) + .bind() } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt index c2d41790de..b1d6b2f500 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt @@ -22,7 +22,6 @@ import com.tangem.domain.tokens.TokensFeatureToggles import com.tangem.domain.tokens.model.* import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations.Error import com.tangem.domain.tokens.repository.CurrenciesRepository -import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.tokens.utils.CurrencyStatusProxyCreator import com.tangem.domain.wallets.models.UserWalletId @@ -33,7 +32,6 @@ import kotlinx.coroutines.flow.* * * @property currenciesRepository repository for currencies * @property quotesRepository repository for quotes - * @property networksRepository repository for networks * @property stakingRepository repository for staking * [REDACTED_AUTHOR] @@ -43,7 +41,6 @@ abstract class BaseCurrencyStatusOperations( private val currenciesRepository: CurrenciesRepository, private val quotesRepository: QuotesRepository, private val quotesRepositoryV2: QuotesRepositoryV2, - private val networksRepository: NetworksRepository, private val stakingRepository: StakingRepository, private val multiNetworkStatusSupplier: MultiNetworkStatusSupplier, private val singleNetworkStatusSupplier: SingleNetworkStatusSupplier, @@ -203,24 +200,14 @@ abstract class BaseCurrencyStatusOperations( ?.right() ?: Error.EmptyQuotes.left() - val networkStatuses = if (tokensFeatureToggles.isNetworksLoadingRefactoringEnabled) { - singleNetworkStatusSupplier( - params = SingleNetworkStatusProducer.Params( - userWalletId = userWalletId, - network = currency.network, - ), - ) - .firstOrNull() - .right() - } else { - networksRepository.getNetworkStatusesSync( + val networkStatuses = singleNetworkStatusSupplier( + params = SingleNetworkStatusProducer.Params( userWalletId = userWalletId, - networks = setOf(currency.network), - refresh = false, - ) - .firstOrNull { it.network == currency.network } - .right() - } + network = currency.network, + ), + ) + .firstOrNull() + .right() val yieldBalances = getYieldBalanceSync(userWalletId, currency) @@ -270,7 +257,7 @@ abstract class BaseCurrencyStatusOperations( val nonEmptyCurrencies = currenciesRepository.getMultiCurrencyWalletCurrenciesSync(userWalletId).toNonEmptyListOrNull() ?: return emptyList().right() - val (networks, currenciesIds) = getIds(nonEmptyCurrencies) + val (_, currenciesIds) = getIds(nonEmptyCurrencies) val rawIds = currenciesIds.mapNotNull { it.rawCurrencyId }.toSet() val quotes = if (tokensFeatureToggles.isQuotesLoadingRefactoringEnabled) { @@ -279,16 +266,13 @@ abstract class BaseCurrencyStatusOperations( quotesRepository.getQuotesSync(rawIds, false).right() } - val networkStatuses = if (tokensFeatureToggles.isNetworksLoadingRefactoringEnabled) { - multiNetworkStatusSupplier( - params = MultiNetworkStatusProducer.Params(userWalletId = userWalletId), - ) - .firstOrNull() - .orEmpty() - .right() - } else { - networksRepository.getNetworkStatusesSync(userWalletId, networks, false).right() - } + val networkStatuses = multiNetworkStatusSupplier( + params = MultiNetworkStatusProducer.Params(userWalletId = userWalletId), + ) + .firstOrNull() + .orEmpty() + .right() + val yieldBalances = getYieldBalancesSync(userWalletId, nonEmptyCurrencies) return currencyStatusProxyCreator.createCurrenciesStatuses( @@ -326,25 +310,11 @@ abstract class BaseCurrencyStatusOperations( ) } - val networkStatus = if (tokensFeatureToggles.isNetworksLoadingRefactoringEnabled) { - singleNetworkStatusSupplier( - params = SingleNetworkStatusProducer.Params( - userWalletId = userWalletId, - network = currency.network, - ), - ) - .firstOrNull() - .right() - } else { - catch( - block = { - networksRepository.getNetworkStatusesSync(userWalletId, setOf(currency.network)) - .firstOrNull { it.network == currency.network } - .right() - }, - catch = { Error.DataError(it).left() }, - ) - } + val networkStatus = singleNetworkStatusSupplier( + params = SingleNetworkStatusProducer.Params(userWalletId = userWalletId, network = currency.network), + ) + .firstOrNull() + .right() val yieldBalances = getYieldBalanceSync(userWalletId, currency) diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt index 8814392d0a..bfda6741cd 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt @@ -30,7 +30,6 @@ import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.model.* import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations.Error import com.tangem.domain.tokens.repository.CurrenciesRepository -import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.tokens.utils.extractAddress import com.tangem.domain.wallets.models.UserWalletId @@ -43,7 +42,6 @@ class CachedCurrenciesStatusesOperations( private val currenciesRepository: CurrenciesRepository, private val quotesRepository: QuotesRepository, quotesRepositoryV2: QuotesRepositoryV2, - private val networksRepository: NetworksRepository, private val stakingRepository: StakingRepository, private val singleNetworkStatusSupplier: SingleNetworkStatusSupplier, multiNetworkStatusSupplier: MultiNetworkStatusSupplier, @@ -58,7 +56,6 @@ class CachedCurrenciesStatusesOperations( currenciesRepository = currenciesRepository, quotesRepository = quotesRepository, quotesRepositoryV2 = quotesRepositoryV2, - networksRepository = networksRepository, stakingRepository = stakingRepository, multiNetworkStatusSupplier = multiNetworkStatusSupplier, singleNetworkStatusSupplier = singleNetworkStatusSupplier, @@ -156,7 +153,7 @@ class CachedCurrenciesStatusesOperations( combine( flow = getQuotes(currenciesIds), - flow2 = getNetworksStatuses(userWalletId, networks), + flow2 = getNetworkStatusesUpdates(userWalletId, networks), flow3 = getYieldBalances(userWalletId, currencies), flow4 = fetchingState.map { val state = it[userWalletId] ?: return@map false @@ -184,13 +181,9 @@ class CachedCurrenciesStatusesOperations( coroutineScope { awaitAll( async { - if (tokensFeatureToggles.isNetworksLoadingRefactoringEnabled) { - multiNetworkStatusFetcher( - params = MultiNetworkStatusFetcher.Params(userWalletId, networks), - ) - } else { - networksRepository.fetchNetworkStatuses(userWalletId, networks) - } + multiNetworkStatusFetcher( + params = MultiNetworkStatusFetcher.Params(userWalletId = userWalletId, networks = networks), + ) }, async { val rawCurrenciesIds = currenciesIds.mapNotNullTo(mutableSetOf()) { it.rawCurrencyId } @@ -327,44 +320,12 @@ class CachedCurrenciesStatusesOperations( userWalletId: UserWalletId, network: Network, ): EitherFlow> { - return if (tokensFeatureToggles.isNetworksLoadingRefactoringEnabled) { - singleNetworkStatusSupplier( - params = SingleNetworkStatusProducer.Params(userWalletId = userWalletId, network = network), - ) - .map>> { setOf(it).right() } - .distinctUntilChanged() - .onEmpty { emit(Error.EmptyNetworksStatuses.left()) } - } else { - networksRepository.getNetworkStatusesUpdates(userWalletId, setOf(network)) - .map, Either>> { it.right() } - .retryWhen { cause, _ -> - emit(Error.DataError(cause).left()) - // adding delay before retry to avoid spam when flow restarted - delay(RETRY_DELAY) - true - } - .distinctUntilChanged() - .onEmpty { emit(Error.EmptyNetworksStatuses.left()) } - } - } - - private fun getNetworksStatuses( - userWalletId: UserWalletId, - networks: NonEmptySet, - ): EitherFlow> { - return if (tokensFeatureToggles.isNetworksLoadingRefactoringEnabled) { - getNetworkStatusesUpdates(userWalletId, networks) - } else { - networksRepository.getNetworkStatusesUpdates(userWalletId, networks) - .map, Either>> { it.right() } - .retryWhen { cause, _ -> - emit(TokenListError.DataError(cause).left()) - // adding delay before retry to avoid spam when flow restarted - delay(RETRY_DELAY) - true - } - .distinctUntilChanged() - } + return singleNetworkStatusSupplier( + params = SingleNetworkStatusProducer.Params(userWalletId = userWalletId, network = network), + ) + .map>> { setOf(it).right() } + .distinctUntilChanged() + .onEmpty { emit(Error.EmptyNetworksStatuses.left()) } } private fun getYieldBalances( diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyStatusUpdatesUseCaseTest.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyStatusUpdatesUseCaseTest.kt deleted file mode 100644 index be180baa2f..0000000000 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyStatusUpdatesUseCaseTest.kt +++ /dev/null @@ -1,197 +0,0 @@ -package com.tangem.domain.tokens - -import arrow.core.Either -import arrow.core.left -import arrow.core.right -import com.tangem.domain.core.error.DataError -import com.tangem.domain.tokens.error.CurrencyStatusError -import com.tangem.domain.tokens.mock.MockNetworks -import com.tangem.domain.tokens.mock.MockQuotes -import com.tangem.domain.tokens.mock.MockTokens -import com.tangem.domain.tokens.mock.MockTokensStates -import com.tangem.domain.tokens.model.* -import com.tangem.domain.tokens.operations.CachedCurrenciesStatusesOperations -import com.tangem.domain.tokens.repository.MockCurrenciesRepository -import com.tangem.domain.tokens.repository.MockNetworksRepository -import com.tangem.domain.tokens.repository.MockQuotesRepository -import com.tangem.domain.tokens.repository.MockStakingRepository -import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider -import io.mockk.mockk -import junit.framework.TestCase.assertEquals -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.test.runTest -import org.junit.Test -import java.math.BigDecimal - -internal class GetPrimaryCurrencyStatusUpdatesUseCaseTest { - - private val dispatchers = TestingCoroutineDispatcherProvider() - private val userWalletId = UserWalletId(value = null) - - @Test - fun `when all data received then token should be received`() = runTest { - // Given - val expectedResult = MockTokensStates.loadedTokensStates.first().right() - - val useCase = getUseCase() - - // When - val result = useCase(userWalletId).first() - - // Then - assertEquals(expectedResult, result) - } - - @Test - fun `when token getting failed then error should be received`() = runTest { - // Given - val expectedResult = CurrencyStatusError.DataError(DataError.NetworkError.NoInternetConnection).left() - - val useCase = getUseCase(token = DataError.NetworkError.NoInternetConnection.left()) - - // When - val result = useCase(userWalletId).first() - - // Then - assertEquals(expectedResult, result) - } - - @Test - fun `when quotes getting failed then currency with no quote status should be received`() = runTest { - // Given - val expectedResult = MockTokensStates.noQuotesTokensStatuses.first().right() - - val useCase = getUseCase(quotes = flowOf(DataError.NetworkError.NoInternetConnection.left())) - - // When - val result = useCase(userWalletId).first() - - // Then - assertEquals(expectedResult, result) - } - - @Test - fun `when networks statuses getting failed then error should be received`() = runTest { - // Given - val expectedResult = CurrencyStatusError.DataError(DataError.NetworkError.NoInternetConnection).left() - - val useCase = getUseCase(statuses = flowOf(DataError.NetworkError.NoInternetConnection.left())) - - // When - val result = useCase(userWalletId).first() - - // Then - assertEquals(expectedResult, result) - } - - @Test - fun `when networks statuses flow is empty then error should be received`() = runTest { - val expectedResult = CurrencyStatusError.UnableToCreateCurrency.left() - - val useCase = getUseCase(statuses = flowOf()) - - // When - val result = useCase(userWalletId).first() - - // Then - assertEquals(expectedResult, result) - } - - @Test - fun `when quotes flow is empty then no quote status should be received`() = runTest { - val expectedResult = MockTokensStates.noQuotesTokensStatuses.first().right() - - val useCase = getUseCase(quotes = flowOf(emptySet().right())) - - // When - val result = useCase(userWalletId).first() - - // Then - assertEquals(expectedResult, result) - } - - @Test - fun `when quotes are empty and statuses are verified then token without quote should be received`() = runTest { - val expectedResult = with(MockTokensStates.tokenState1) { - copy( - value = CryptoCurrencyStatus.NoQuote( - amount = BigDecimal.TEN, - hasCurrentNetworkTransactions = false, - pendingTransactions = emptySet(), - networkAddress = NetworkAddress.Single( - defaultAddress = NetworkAddress.Address(value = "mock", NetworkAddress.Address.Type.Primary), - ), - yieldBalance = null, - sources = CryptoCurrencyStatus.Sources(), - ), - ) - } - .right() - - val useCase = getUseCase( - statuses = flowOf(MockNetworks.verifiedNetworksStatuses.right()), - quotes = flowOf(emptySet().right()), - ) - - // When - val result = useCase(userWalletId).first() - - // Then - assertEquals(expectedResult, result) - } - - @Test - fun `when quotes are loaded and statuses are empty then error be received`() = runTest { - val expectedResult = CurrencyStatusError.UnableToCreateCurrency.left() - - val useCase = getUseCase( - statuses = flowOf(emptySet().right()), - quotes = flowOf(MockQuotes.quotes.right()), - ) - - // When - val result = useCase(userWalletId).first() - - // Then - assertEquals(expectedResult, result) - } - - private fun getUseCase( - token: Either = MockTokens.token1.right(), - removeCurrencyResult: Either = Unit.right(), - quotes: Flow>> = flowOf(MockQuotes.quotes.right()), - statuses: Flow>> = flowOf(MockNetworks.verifiedNetworksStatuses.right()), - ) = GetPrimaryCurrencyStatusUpdatesUseCase( - currencyStatusOperations = CachedCurrenciesStatusesOperations( - currenciesRepository = MockCurrenciesRepository( - sortTokensResult = Unit.right(), - removeCurrencyResult = removeCurrencyResult, - token = token, - tokens = flowOf(), - isGrouped = flowOf(), - isSortedByBalance = flowOf(), - ), - quotesRepository = MockQuotesRepository(quotes), - networksRepository = MockNetworksRepository(statuses), - stakingRepository = MockStakingRepository(), - - tokensFeatureToggles = object : TokensFeatureToggles { - override val isNetworksLoadingRefactoringEnabled: Boolean = false - override val isQuotesLoadingRefactoringEnabled: Boolean = false - override val isStakingLoadingRefactoringEnabled: Boolean = false - }, - singleNetworkStatusSupplier = mockk(), - multiNetworkStatusFetcher = mockk(), - multiNetworkStatusSupplier = mockk(), - multiQuoteFetcher = mockk(), - singleQuoteSupplier = mockk(), - quotesRepositoryV2 = mockk(), - singleYieldBalanceSupplier = mockk(), - multiYieldBalanceFetcher = mockk(), - ), - dispatchers = dispatchers, - ) -} \ No newline at end of file diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/AssociateAssetUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/AssociateAssetUseCase.kt index 3df20fa08d..f44929cdd3 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/AssociateAssetUseCase.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/AssociateAssetUseCase.kt @@ -7,12 +7,10 @@ import com.tangem.blockchain.extensions.SimpleResult import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.networks.single.SingleNetworkStatusProducer import com.tangem.domain.networks.single.SingleNetworkStatusSupplier -import com.tangem.domain.tokens.TokensFeatureToggles import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyAmountStatus import com.tangem.domain.tokens.model.NetworkStatus import com.tangem.domain.tokens.repository.CurrenciesRepository -import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.transaction.error.AssociateAssetError import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.models.UserWalletId @@ -23,9 +21,7 @@ class AssociateAssetUseCase( private val cardSdkConfigRepository: CardSdkConfigRepository, private val walletManagersFacade: WalletManagersFacade, private val currenciesRepository: CurrenciesRepository, - private val networksRepository: NetworksRepository, private val singleNetworkStatusSupplier: SingleNetworkStatusSupplier, - private val tokensFeatureToggles: TokensFeatureToggles, ) { suspend operator fun invoke( @@ -59,20 +55,13 @@ class AssociateAssetUseCase( } private suspend fun isBalanceZero(userWalletId: UserWalletId, currency: CryptoCurrency): Boolean { - val networkStatus = if (tokensFeatureToggles.isNetworksLoadingRefactoringEnabled) { - singleNetworkStatusSupplier( - params = SingleNetworkStatusProducer.Params( - userWalletId = userWalletId, - network = currency.network, - ), - ) - .firstOrNull() - } else { - networksRepository.getNetworkStatusesSync( + val networkStatus = singleNetworkStatusSupplier( + params = SingleNetworkStatusProducer.Params( userWalletId = userWalletId, - networks = setOf(currency.network), - ).find { it.network == currency.network } - } + network = currency.network, + ), + ) + .firstOrNull() val networkCoinAmountStatus = (networkStatus?.value as? NetworkStatus.Verified) ?.amounts diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt index 08c37d0043..0bf8d57527 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt @@ -19,7 +19,6 @@ import com.tangem.domain.common.TapWorkarounds.isTangemTwins import com.tangem.domain.demo.DemoConfig import com.tangem.domain.demo.DemoTransactionSender import com.tangem.domain.networks.single.SingleNetworkStatusFetcher -import com.tangem.domain.tokens.TokensFeatureToggles import com.tangem.domain.tokens.model.Network import com.tangem.domain.transaction.TransactionRepository import com.tangem.domain.transaction.error.SendTransactionError @@ -33,7 +32,6 @@ class SendTransactionUseCase( private val transactionRepository: TransactionRepository, private val walletManagersFacade: WalletManagersFacade, private val singleNetworkStatusFetcher: SingleNetworkStatusFetcher, - private val tokensFeatureToggles: TokensFeatureToggles, ) { suspend operator fun invoke( txsData: List, @@ -80,16 +78,15 @@ class SendTransactionUseCase( } cardSdkConfigRepository.setLinkedTerminal(linkedTerminal) + return sendResult .onRight { - if (tokensFeatureToggles.isNetworksLoadingRefactoringEnabled) { - singleNetworkStatusFetcher( - params = SingleNetworkStatusFetcher.Params.Simple( - userWalletId = userWallet.walletId, - network = network, - ), - ) - } + singleNetworkStatusFetcher( + params = SingleNetworkStatusFetcher.Params.Simple( + userWalletId = userWallet.walletId, + network = network, + ), + ) } .fold( ifRight = { result -> result.hashes.right() }, diff --git a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcPairRequest.kt b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcPairRequest.kt index 1c1c3f3674..2d70af0e55 100644 --- a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcPairRequest.kt +++ b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcPairRequest.kt @@ -1,11 +1,13 @@ package com.tangem.domain.walletconnect.model +import com.tangem.domain.wallets.models.UserWalletId import kotlinx.serialization.Serializable @Serializable data class WcPairRequest( val uri: String, val source: Source, + val userWalletId: UserWalletId, ) { enum class Source { QR, DEEPLINK, CLIPBOARD, ETC } } \ No newline at end of file diff --git a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcSession.kt b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcSession.kt index d3707d52f6..a2b1449d45 100644 --- a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcSession.kt +++ b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcSession.kt @@ -1,11 +1,13 @@ package com.tangem.domain.walletconnect.model import com.domain.blockaid.models.dapp.CheckDAppResult +import com.tangem.domain.tokens.model.Network import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSession import com.tangem.domain.wallets.models.UserWallet data class WcSession( val wallet: UserWallet, + val networks: Set, val sdkModel: WcSdkSession, val securityStatus: CheckDAppResult, ) \ No newline at end of file diff --git a/domain/wallets/build.gradle.kts b/domain/wallets/build.gradle.kts index 6ca312b904..331335a257 100644 --- a/domain/wallets/build.gradle.kts +++ b/domain/wallets/build.gradle.kts @@ -26,6 +26,7 @@ dependencies { implementation(projects.domain.tokens) implementation(projects.domain.tokens.models) implementation(projects.domain.wallets.models) + implementation(projects.domain.notifications.models) // endregion // region Tangem libraries @@ -37,4 +38,11 @@ dependencies { implementation(deps.hilt.android) kapt(deps.hilt.kapt) // end + + // region Tests + testImplementation(deps.test.junit) + testImplementation(deps.test.coroutine) + testImplementation(deps.test.truth) + testImplementation(deps.test.mockk) + // end } \ No newline at end of file diff --git a/domain/wallets/models/src/main/java/com/tangem/domain/wallets/models/UserWalletRemoteInfo.kt b/domain/wallets/models/src/main/java/com/tangem/domain/wallets/models/UserWalletRemoteInfo.kt new file mode 100644 index 0000000000..5c6e3669a5 --- /dev/null +++ b/domain/wallets/models/src/main/java/com/tangem/domain/wallets/models/UserWalletRemoteInfo.kt @@ -0,0 +1,7 @@ +package com.tangem.domain.wallets.models + +class UserWalletRemoteInfo( + val walletId: UserWalletId, + val name: String, + val isNotificationsEnabled: Boolean, +) \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/delegate/DefaultUserWalletsSyncDelegate.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/delegate/DefaultUserWalletsSyncDelegate.kt new file mode 100644 index 0000000000..37ab17d016 --- /dev/null +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/delegate/DefaultUserWalletsSyncDelegate.kt @@ -0,0 +1,56 @@ +package com.tangem.domain.wallets.delegate + +import arrow.core.Either +import arrow.core.raise.either +import arrow.core.raise.ensure +import com.tangem.common.CompletionResult +import com.tangem.domain.wallets.legacy.UserWalletsListManager +import com.tangem.domain.wallets.models.UpdateWalletError +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.wallets.models.UserWalletRemoteInfo +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.withContext + +class DefaultUserWalletsSyncDelegate( + private val userWalletsListManager: UserWalletsListManager, + private val dispatchers: CoroutineDispatcherProvider, +) : UserWalletsSyncDelegate { + + override suspend fun syncWallet(userWalletId: UserWalletId, name: String): Either { + return renameUserWallet(userWalletId, name) + } + + override suspend fun syncWallets(list: List): Either = either { + list.forEach { userWallet -> + renameUserWallet(userWallet.walletId, userWallet.name).bind() + } + } + + // TODO remove dispatchers whnen UserWalletsListManager will be main safe + private suspend fun renameUserWallet( + userWalletId: UserWalletId, + name: String, + ): Either = withContext(dispatchers.io) { + either { + val existingNames = userWalletsListManager.userWalletsSync + + ensure(existingNames.none { it.name == name && it.walletId != userWalletId }) { + UpdateWalletError.NameAlreadyExists + } + + val previousName = existingNames.firstOrNull { it.walletId == userWalletId }?.name.orEmpty() + if (previousName == name) { + raise(UpdateWalletError.NameAlreadyExists) + } + + return@withContext when ( + val result = + userWalletsListManager.update(userWalletId) { it.copy(name = name) } + ) { + is CompletionResult.Failure -> raise(UpdateWalletError.DataError(result.error)) + is CompletionResult.Success -> result.data + } + } + } +} \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/delegate/UserWalletsSyncDelegate.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/delegate/UserWalletsSyncDelegate.kt new file mode 100644 index 0000000000..94f72df038 --- /dev/null +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/delegate/UserWalletsSyncDelegate.kt @@ -0,0 +1,14 @@ +package com.tangem.domain.wallets.delegate + +import arrow.core.Either +import com.tangem.domain.wallets.models.UpdateWalletError +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.wallets.models.UserWalletRemoteInfo + +interface UserWalletsSyncDelegate { + + suspend fun syncWallet(userWalletId: UserWalletId, name: String): Either + + suspend fun syncWallets(list: List): Either +} \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/repository/WalletsRepository.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/repository/WalletsRepository.kt index 541e601c54..eaf24f6cfa 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/repository/WalletsRepository.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/repository/WalletsRepository.kt @@ -1,9 +1,12 @@ package com.tangem.domain.wallets.repository import com.tangem.domain.wallets.models.SeedPhraseNotificationsStatus +import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.wallets.models.UserWalletRemoteInfo import kotlinx.coroutines.flow.Flow +@Suppress("TooManyFunctions") interface WalletsRepository { suspend fun shouldSaveUserWalletsSync(): Boolean @@ -43,4 +46,16 @@ interface WalletsRepository { @Throws suspend fun setNotificationsEnabled(userWalletId: UserWalletId, isEnabled: Boolean) + + @Throws + suspend fun setWalletName(walletId: String, walletName: String) + + @Throws + suspend fun getWalletInfo(walletId: String): UserWalletRemoteInfo + + @Throws + suspend fun getWalletsInfo(applicationId: String, updateCache: Boolean = true): List + + @Throws + suspend fun associateWallets(applicationId: String, wallets: List) } \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/AssociateWalletsWithApplicationIdUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/AssociateWalletsWithApplicationIdUseCase.kt new file mode 100644 index 0000000000..e67bf5b744 --- /dev/null +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/AssociateWalletsWithApplicationIdUseCase.kt @@ -0,0 +1,17 @@ +package com.tangem.domain.wallets.usecase + +import arrow.core.Either +import com.tangem.domain.notifications.models.ApplicationId +import com.tangem.domain.wallets.legacy.UserWalletsListManager +import com.tangem.domain.wallets.repository.WalletsRepository + +class AssociateWalletsWithApplicationIdUseCase( + private val userWalletsListManager: UserWalletsListManager, + private val walletsRepository: WalletsRepository, +) { + + suspend operator fun invoke(applicationId: ApplicationId): Either = Either.catch { + val wallets = userWalletsListManager.userWalletsSync + walletsRepository.associateWallets(applicationId.value, wallets) + } +} \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/RenameWalletUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/RenameWalletUseCase.kt index ff568c6794..8225cd9220 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/RenameWalletUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/RenameWalletUseCase.kt @@ -2,38 +2,23 @@ package com.tangem.domain.wallets.usecase import arrow.core.Either import arrow.core.raise.either -import arrow.core.raise.ensure -import com.tangem.common.CompletionResult -import com.tangem.domain.wallets.legacy.UserWalletsListManager +import com.tangem.domain.wallets.delegate.UserWalletsSyncDelegate import com.tangem.domain.wallets.models.UpdateWalletError import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.coroutines.withContext +import com.tangem.domain.wallets.repository.WalletsRepository -/** - * Use case for rename user wallet - * - * @property userWalletsListManager user wallets list manager - */ class RenameWalletUseCase( - private val userWalletsListManager: UserWalletsListManager, - private val dispatchers: CoroutineDispatcherProvider, + private val walletsRepository: WalletsRepository, + private val userWalletsSyncDelegate: UserWalletsSyncDelegate, ) { suspend operator fun invoke(userWalletId: UserWalletId, name: String): Either = - withContext(dispatchers.io) { - either { - val existingNames = userWalletsListManager.userWalletsSync - - ensure(existingNames.none { it.name == name && it.walletId != userWalletId }) { - UpdateWalletError.NameAlreadyExists - } - - when (val result = userWalletsListManager.update(userWalletId) { it.copy(name = name) }) { - is CompletionResult.Failure -> raise(UpdateWalletError.DataError(result.error)) - is CompletionResult.Success -> result.data - } + either { + runCatching { + walletsRepository.setWalletName(userWalletId.stringValue, name) } + + userWalletsSyncDelegate.syncWallet(userWalletId, name).bind() } } \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/UpdateRemoteWalletsInfoUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/UpdateRemoteWalletsInfoUseCase.kt new file mode 100644 index 0000000000..5305089cd7 --- /dev/null +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/UpdateRemoteWalletsInfoUseCase.kt @@ -0,0 +1,19 @@ +package com.tangem.domain.wallets.usecase + +import arrow.core.Either +import arrow.core.raise.either +import com.tangem.domain.notifications.models.ApplicationId +import com.tangem.domain.wallets.delegate.UserWalletsSyncDelegate +import com.tangem.domain.wallets.models.UpdateWalletError +import com.tangem.domain.wallets.repository.WalletsRepository + +class UpdateRemoteWalletsInfoUseCase( + private val walletsRepository: WalletsRepository, + private val userWalletsSyncDelegate: UserWalletsSyncDelegate, +) { + + suspend operator fun invoke(applicationId: ApplicationId): Either = either { + val walletsInfo = walletsRepository.getWalletsInfo(applicationId.value) + userWalletsSyncDelegate.syncWallets(walletsInfo).bind() + } +} \ No newline at end of file diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/component/preview/PreviewDetailsComponent.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/component/preview/PreviewDetailsComponent.kt index 37abe05472..8efcb31776 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/component/preview/PreviewDetailsComponent.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/component/preview/PreviewDetailsComponent.kt @@ -5,6 +5,7 @@ import androidx.compose.ui.Modifier import com.tangem.core.decompose.navigation.DummyRouter import com.tangem.core.navigation.url.DummyUrlOpener import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.domain.wallets.models.UserWalletId import com.tangem.features.details.component.DetailsComponent import com.tangem.features.details.entity.DetailsFooterUM import com.tangem.features.details.entity.DetailsUM @@ -18,7 +19,12 @@ internal class PreviewDetailsComponent : DetailsComponent { private val previewBlocks = runBlocking { ItemsBuilder( router = DummyRouter(), - ).buildAll(isWalletConnectAvailable = true, onSupportClick = {}, onBuyClick = {}) + ).buildAll( + isWalletConnectAvailable = true, + userWalletId = UserWalletId(""), + onSupportClick = {}, + onBuyClick = {}, + ) } private val previewFooter = DetailsFooterUM( diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt index d2068c9fcf..10afab1fde 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt @@ -80,6 +80,7 @@ internal class DetailsModel @Inject constructor( items = MutableStateFlow( itemsBuilder.buildAll( isWalletConnectAvailable = isWalletConnectAvailable, + userWalletId = params.userWalletId, onSupportClick = ::sendFeedback, onBuyClick = ::onBuyClick, ), diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/ItemsBuilder.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/ItemsBuilder.kt index 7cd8933ad5..f0fd520172 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/ItemsBuilder.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/ItemsBuilder.kt @@ -6,6 +6,7 @@ import com.tangem.core.decompose.navigation.Router import com.tangem.core.ui.components.block.model.BlockUM import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference +import com.tangem.domain.wallets.models.UserWalletId import com.tangem.features.details.entity.DetailsItemUM import com.tangem.features.details.impl.BuildConfig import com.tangem.features.details.impl.R @@ -19,20 +20,21 @@ internal class ItemsBuilder @Inject constructor(private val router: Router) { fun buildAll( isWalletConnectAvailable: Boolean, + userWalletId: UserWalletId, onSupportClick: () -> Unit, onBuyClick: () -> Unit, ): ImmutableList = buildList { - buildWalletConnectBlock(isWalletConnectAvailable)?.let(::add) + buildWalletConnectBlock(isWalletConnectAvailable, userWalletId)?.let(::add) buildUserWalletListBlock().let(::add) buildShopBlock(onBuyClick).let(::add) buildSettingsBlock().let(::add) buildSupportBlock(onSupportClick).let(::add) }.toImmutableList() - private fun buildWalletConnectBlock(isWalletConnectAvailable: Boolean): DetailsItemUM? { + private fun buildWalletConnectBlock(isWalletConnectAvailable: Boolean, userWalletId: UserWalletId): DetailsItemUM? { return if (isWalletConnectAvailable) { DetailsItemUM.WalletConnect( - onClick = { router.push(AppRoute.WalletConnectSessions) }, + onClick = { router.push(AppRoute.WalletConnectSessions(userWalletId)) }, ) } else { null diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/collections/model/NFTCollectionsModel.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/collections/model/NFTCollectionsModel.kt index 72b200907a..1b83ed39e4 100644 --- a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/collections/model/NFTCollectionsModel.kt +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/collections/model/NFTCollectionsModel.kt @@ -15,10 +15,6 @@ import com.tangem.features.nft.collections.NFTCollectionsComponent import com.tangem.features.nft.collections.entity.NFTCollectionsStateUM import com.tangem.features.nft.collections.entity.NFTCollectionsUM import com.tangem.features.nft.collections.entity.transformer.* -import com.tangem.features.nft.collections.entity.transformer.ChangeCollectionExpandedStateTransformer -import com.tangem.features.nft.collections.entity.transformer.ToggleSearchBarTransformer -import com.tangem.features.nft.collections.entity.transformer.UpdateDataStateTransformer -import com.tangem.features.nft.collections.entity.transformer.UpdateSearchQueryTransformer import com.tangem.features.nft.impl.R import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.* diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/topup/model/OnboardingNoteTopUpModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/topup/model/OnboardingNoteTopUpModel.kt index bdac9d90ca..7330ae8a68 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/topup/model/OnboardingNoteTopUpModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/topup/model/OnboardingNoteTopUpModel.kt @@ -16,7 +16,7 @@ import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.onramp.GetLegacyTopUpUrlUseCase import com.tangem.domain.tokens.FetchCurrencyStatusUseCase -import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase +import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.model.NetworkAddress @@ -39,7 +39,7 @@ import javax.inject.Inject internal class OnboardingNoteTopUpModel @Inject constructor( paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, - private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase, + private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase, private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase, private val userWalletBuilderFactory: UserWalletBuilder.Factory, private val getLegacyTopUpUrlUseCase: GetLegacyTopUpUrlUseCase, @@ -137,8 +137,11 @@ internal class OnboardingNoteTopUpModel @Inject constructor( private fun observeCryptoCurrencyStatus() { val userWalletId = userWallet?.walletId ?: return - getPrimaryCurrencyStatusUpdatesUseCase(userWalletId = userWalletId).map { it.getOrNull() }.filterNotNull() - .onEach(::applyCryptoCurrencyStatusToState).launchIn(modelScope) + getSingleCryptoCurrencyStatusUseCase.invokeSingleWallet(userWalletId = userWalletId) + .map { it.getOrNull() } + .filterNotNull() + .onEach(::applyCryptoCurrencyStatusToState) + .launchIn(modelScope) } private fun applyCryptoCurrencyStatusToState(status: CryptoCurrencyStatus) { diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/twin/impl/model/OnboardingTwinModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/twin/impl/model/OnboardingTwinModel.kt index 1219a5763f..a36e14fec8 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/twin/impl/model/OnboardingTwinModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/twin/impl/model/OnboardingTwinModel.kt @@ -7,18 +7,18 @@ import com.tangem.common.core.TangemError import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toHexString +import com.tangem.common.ui.bottomsheet.receive.TokenReceiveBottomSheetConfig +import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.ui.UiMessageSender -import com.tangem.core.navigation.url.UrlOpener -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig -import com.tangem.common.ui.bottomsheet.receive.TokenReceiveBottomSheetConfig -import com.tangem.core.analytics.Analytics import com.tangem.core.navigation.share.ShareManager +import com.tangem.core.navigation.url.UrlOpener import com.tangem.core.ui.clipboard.ClipboardManager +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.toWrappedList import com.tangem.core.ui.format.bigdecimal.crypto @@ -34,7 +34,7 @@ import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase import com.tangem.domain.onramp.GetLegacyTopUpUrlUseCase import com.tangem.domain.tokens.FetchCurrencyStatusUseCase -import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase +import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.model.analytics.TokenReceiveAnalyticsEvent @@ -77,7 +77,7 @@ internal class OnboardingTwinModel @Inject constructor( private val tangemSdkManager: TangemSdkManager, private val issuersConfigStorage: IssuersConfigStorage, private val cardRepository: CardRepository, - private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase, + private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase, private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase, private val getLegacyTopUpUrlUseCase: GetLegacyTopUpUrlUseCase, private val urlOpener: UrlOpener, @@ -337,16 +337,16 @@ internal class OnboardingTwinModel @Inject constructor( setLoading(false) } - val cryptoCurrencyStatus = - getPrimaryCurrencyStatusUpdatesUseCase.invoke(userWallet.walletId).firstOrNull()?.getOrNull() - ?: run { - setLoading(false) - Timber.e("Unable to get currency status") - return@coroutineScope - } + val cryptoCurrencyStatus = getSingleCryptoCurrencyStatusUseCase.invokeSingleWallet(userWallet.walletId) + .firstOrNull()?.getOrNull() + ?: run { + setLoading(false) + Timber.e("Unable to get currency status") + return@coroutineScope + } launch { - getPrimaryCurrencyStatusUpdatesUseCase.invoke(userWallet.walletId) + getSingleCryptoCurrencyStatusUseCase.invokeSingleWallet(userWallet.walletId) .collect { it.onRight { status -> applyCryptoCurrencyStatusToState(status) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/model/HotCryptoModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/model/HotCryptoModel.kt index be622c09d1..cb7ccfc863 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/model/HotCryptoModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/model/HotCryptoModel.kt @@ -14,7 +14,7 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.onramp.GetHotCryptoUseCase import com.tangem.domain.onramp.model.HotCryptoCurrency -import com.tangem.domain.tokens.GetCryptoCurrencyStatusSyncUseCase +import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.features.onramp.hottokens.HotCryptoComponent import com.tangem.features.onramp.hottokens.converter.HotTokenItemStateConverter @@ -45,7 +45,7 @@ internal class HotCryptoModel @Inject constructor( paramsContainer: ParamsContainer, getHotCryptoUseCase: GetHotCryptoUseCase, getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, - private val getCryptoCurrencyStatusSyncUseCase: GetCryptoCurrencyStatusSyncUseCase, + private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase, override val dispatchers: CoroutineDispatcherProvider, ) : Model() { @@ -103,7 +103,10 @@ internal class HotCryptoModel @Inject constructor( private fun onSuccessAdding(id: CryptoCurrency.ID) { modelScope.launch { - getCryptoCurrencyStatusSyncUseCase(userWalletId = params.userWalletId, cryptoCurrencyId = id) + getSingleCryptoCurrencyStatusUseCase.invokeMultiWalletSync( + userWalletId = params.userWalletId, + cryptoCurrencyId = id, + ) .onRight { bottomSheetNavigation.dismiss() params.onTokenClick(it) diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/common/SendBalanceUpdater.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/common/SendBalanceUpdater.kt index 69254405cf..bb10f2b5d3 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/common/SendBalanceUpdater.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/common/SendBalanceUpdater.kt @@ -52,7 +52,6 @@ internal class SendBalanceUpdater @AssistedInject constructor( userWalletId = userWallet.walletId, network = cryptoCurrency.network, delayMillis = delay, - refresh = true, ) } diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/model/SendModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/model/SendModel.kt index 250e790576..3d162a7bc2 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/model/SendModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/model/SendModel.kt @@ -23,9 +23,8 @@ import com.tangem.domain.feedback.models.FeedbackEmailType import com.tangem.domain.qrscanning.models.SourceType import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase import com.tangem.domain.qrscanning.usecases.ParseQrCodeUseCase -import com.tangem.domain.tokens.GetCurrencyStatusUpdatesUseCase +import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase -import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.transaction.error.GetFeeError @@ -72,8 +71,7 @@ internal class SendModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, private val router: Router, private val getUserWalletUseCase: GetUserWalletUseCase, - private val getCurrencyStatusUpdatesUseCase: GetCurrencyStatusUpdatesUseCase, - private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase, + private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase, private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val listenToQrScanningUseCase: ListenToQrScanningUseCase, @@ -244,13 +242,13 @@ internal class SendModel @Inject constructor( isMultiCurrency: Boolean, ): Flow> { return if (isMultiCurrency) { - getCurrencyStatusUpdatesUseCase( + getSingleCryptoCurrencyStatusUseCase.invokeMultiWallet( userWalletId = userWalletId, currencyId = cryptoCurrency.id, isSingleWalletWithTokens = isSingleWalletWithToken, ) } else { - getPrimaryCurrencyStatusUpdatesUseCase(userWalletId = userWalletId) + getSingleCryptoCurrencyStatusUseCase.invokeSingleWallet(userWalletId = userWalletId) } } diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/model/NFTSendModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/model/NFTSendModel.kt index 4e496e2810..ecd6c008dd 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/model/NFTSendModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/model/NFTSendModel.kt @@ -19,7 +19,7 @@ import com.tangem.domain.feedback.SendFeedbackEmailUseCase import com.tangem.domain.feedback.models.BlockchainErrorInfo import com.tangem.domain.feedback.models.FeedbackEmailType import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase -import com.tangem.domain.tokens.GetCurrencyStatusUpdatesUseCase +import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -60,7 +60,7 @@ internal class NFTSendModel @Inject constructor( private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val getUserWalletUseCase: GetUserWalletUseCase, private val getCryptoCurrenciesUseCase: GetCryptoCurrenciesUseCase, - private val getCurrencyStatusUpdatesUseCase: GetCurrencyStatusUpdatesUseCase, + private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase, private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase, private val createNFTTransferTransactionUseCase: CreateNFTTransferTransactionUseCase, private val getFeeUseCase: GetFeeUseCase, @@ -183,7 +183,7 @@ internal class NFTSendModel @Inject constructor( } private fun getCurrenciesStatusUpdates(isSingleWalletWithToken: Boolean) { - getCurrencyStatusUpdatesUseCase( + getSingleCryptoCurrencyStatusUseCase.invokeMultiWallet( userWalletId = userWalletId, currencyId = cryptoCurrency.id, isSingleWalletWithTokens = isSingleWalletWithToken, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/model/SendModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/model/SendModel.kt index be16dae8cd..02f0478ef5 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/model/SendModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/model/SendModel.kt @@ -84,7 +84,7 @@ import kotlin.properties.Delegates internal class SendModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, private val getUserWalletUseCase: GetUserWalletUseCase, - private val getCryptoCurrencyStatusSyncUseCase: GetCryptoCurrencyStatusSyncUseCase, + private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val getWalletsUseCase: GetWalletsUseCase, private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase, @@ -325,13 +325,13 @@ internal class SendModel @Inject constructor( isMultiCurrency: Boolean, ): Either { return if (isMultiCurrency) { - getCryptoCurrencyStatusSyncUseCase( + getSingleCryptoCurrencyStatusUseCase.invokeMultiWalletSync( userWalletId = userWalletId, cryptoCurrencyId = cryptoCurrency.id, isSingleWalletWithTokens = isSingleWalletWithToken, ) } else { - getCryptoCurrencyStatusSyncUseCase(userWalletId = userWalletId) + getSingleCryptoCurrencyStatusUseCase.invokeSingleWalletSync(userWalletId = userWalletId) } } @@ -1048,7 +1048,6 @@ internal class SendModel @Inject constructor( userWalletId = userWallet.walletId, network = cryptoCurrency.network, delayMillis = BALANCE_UPDATE_DELAY, - refresh = true, ) }, ).awaitAll() diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt index d122cd48bf..c8eb096676 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt @@ -93,7 +93,7 @@ internal class StakingModel @Inject constructor( private val stateController: StakingStateController, override val dispatchers: CoroutineDispatcherProvider, private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, - private val getCurrencyStatusUpdatesUseCase: GetCurrencyStatusUpdatesUseCase, + private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase, private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase, private val getMinimumTransactionAmountSyncUseCase: GetMinimumTransactionAmountSyncUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, @@ -920,7 +920,7 @@ internal class StakingModel @Inject constructor( ) }, ) - getCurrencyStatusUpdatesUseCase( + getSingleCryptoCurrencyStatusUseCase.invokeMultiWallet( userWalletId = userWalletId, currencyId = cryptoCurrencyId, isSingleWalletWithTokens = false, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt index 02db8ad802..88d2974d5c 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt @@ -83,7 +83,6 @@ internal class StakingBalanceUpdater @AssistedInject constructor( userWalletId = userWallet.walletId, network = cryptoCurrencyStatus.currency.network, delayMillis = delay, - refresh = true, ) } diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 6d119df85d..1e45b523de 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -18,7 +18,7 @@ import com.tangem.domain.appcurrency.repository.AppCurrencyRepository import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.quotes.QuotesRepositoryV2 import com.tangem.domain.tokens.FetchCurrencyStatusUseCase -import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase +import com.tangem.domain.tokens.GetMultiCryptoCurrencyStatusUseCase import com.tangem.domain.tokens.GetCurrencyCheckUseCase import com.tangem.domain.tokens.TokensFeatureToggles import com.tangem.domain.tokens.model.* @@ -54,7 +54,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( private val userWalletManager: UserWalletManager, private val repository: SwapRepository, private val allowPermissionsHandler: AllowPermissionsHandler, - private val getMultiCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusesSyncUseCase, + private val getMultiCryptoCurrencyStatusUseCase: GetMultiCryptoCurrencyStatusUseCase, private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase, private val sendTransactionUseCase: SendTransactionUseCase, private val createTransactionUseCase: CreateTransactionUseCase, @@ -92,7 +92,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( } override suspend fun getTokensDataState(currency: CryptoCurrency): TokensDataStateExpress { - val walletCurrencyStatuses = getMultiCryptoCurrencyStatusUseCase(userWalletId) + val walletCurrencyStatuses = getMultiCryptoCurrencyStatusUseCase.invokeMultiWalletSync(userWalletId) .getOrElse { emptyList() } val walletCurrencyStatusesExceptInitial = walletCurrencyStatuses diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt index 610aa556a8..7a7e89069d 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt @@ -1,6 +1,6 @@ package com.tangem.feature.swap.domain.di -import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase +import com.tangem.domain.tokens.GetMultiCryptoCurrencyStatusUseCase import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations import com.tangem.feature.swap.domain.* import dagger.Module @@ -28,8 +28,8 @@ internal class SwapDomainModule { @Singleton fun providesGetCryptoCurrencyStatusUseCase( currencyStatusOperations: BaseCurrencyStatusOperations, - ): GetCryptoCurrencyStatusesSyncUseCase { - return GetCryptoCurrencyStatusesSyncUseCase(currencyStatusOperations) + ): GetMultiCryptoCurrencyStatusUseCase { + return GetMultiCryptoCurrencyStatusUseCase(currencyStatusOperations) } @Provides diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt index 0b3a1eb4b5..2c54085052 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt @@ -34,7 +34,10 @@ import com.tangem.domain.promo.models.StoryContentIds import com.tangem.domain.settings.usercountry.GetUserCountryUseCase import com.tangem.domain.settings.usercountry.models.UserCountry import com.tangem.domain.settings.usercountry.models.needApplyFCARestrictions -import com.tangem.domain.tokens.* +import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase +import com.tangem.domain.tokens.GetMinimumTransactionAmountSyncUseCase +import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase +import com.tangem.domain.tokens.UpdateDelayedNetworkStatusUseCase import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.Network @@ -86,9 +89,8 @@ internal class SwapModel @Inject constructor( private val analyticsEventHandler: AnalyticsEventHandler, private val analyticsErrorEventHandler: AnalyticsErrorHandler, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, - private val getCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusSyncUseCase, private val updateDelayedCurrencyStatusUseCase: UpdateDelayedNetworkStatusUseCase, - private val getCurrencyStatusUpdatesUseCase: GetCurrencyStatusUpdatesUseCase, + private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase, private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase, private val getUserWalletUseCase: GetUserWalletUseCase, private val getCardInfoUseCase: GetCardInfoUseCase, @@ -98,7 +100,7 @@ internal class SwapModel @Inject constructor( private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase, private val shouldShowStoriesUseCase: ShouldShowStoriesUseCase, private val getStoryContentUseCase: GetStoryContentUseCase, - private val getUserCountryUseCase: GetUserCountryUseCase, + getUserCountryUseCase: GetUserCountryUseCase, getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, swapInteractorFactory: SwapInteractor.Factory, private val urlOpener: UrlOpener, @@ -176,8 +178,12 @@ internal class SwapModel @Inject constructor( } modelScope.launch(dispatchers.io) { - val fromStatus = getCryptoCurrencyStatusUseCase(userWalletId, initialCurrencyFrom.id).getOrNull() - val toStatus = initialCurrencyTo?.let { getCryptoCurrencyStatusUseCase(userWalletId, it.id).getOrNull() } + val fromStatus = + getSingleCryptoCurrencyStatusUseCase.invokeMultiWalletSync(userWalletId, initialCurrencyFrom.id) + .getOrNull() + val toStatus = initialCurrencyTo?.let { + getSingleCryptoCurrencyStatusUseCase.invokeMultiWalletSync(userWalletId, it.id).getOrNull() + } val wallet = getUserWalletUseCase(userWalletId).getOrNull() if (fromStatus == null || wallet == null) { @@ -861,7 +867,7 @@ internal class SwapModel @Inject constructor( ) { Timber.d("Subscribe to ${coin.id} balance updates") - getCurrencyStatusUpdatesUseCase( + getSingleCryptoCurrencyStatusUseCase.invokeMultiWallet( userWalletId = userWalletId, currencyId = coin.id, isSingleWalletWithTokens = false, @@ -1311,7 +1317,6 @@ internal class SwapModel @Inject constructor( userWalletId = userWalletId, network = network, delayMillis = UPDATE_BALANCE_DELAY_MILLIS, - refresh = true, ) } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt index 4d4b8bc5e7..669086fb61 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt @@ -91,7 +91,7 @@ import javax.inject.Inject @ModelScoped internal class TokenDetailsModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, - private val getCurrencyStatusUpdatesUseCase: GetCurrencyStatusUpdatesUseCase, + private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase, private val txHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase, @@ -117,7 +117,6 @@ internal class TokenDetailsModel @Inject constructor( private val analyticsEventsHandler: AnalyticsEventHandler, private val vibratorHapticManager: VibratorHapticManager, private val clipboardManager: ClipboardManager, - private val getCryptoCurrencySyncUseCase: GetCryptoCurrencyStatusSyncUseCase, private val onrampFeatureToggles: OnrampFeatureToggles, private val shareManager: ShareManager, @GlobalUiMessageSender private val uiMessageSender: UiMessageSender, @@ -219,7 +218,7 @@ internal class TokenDetailsModel @Inject constructor( private fun initButtons() { // we need also init buttons before start all loading to avoid buttons blocking modelScope.launch { - val currentCryptoCurrencyStatus = getCryptoCurrencySyncUseCase.invoke( + val currentCryptoCurrencyStatus = getSingleCryptoCurrencyStatusUseCase.invokeMultiWalletSync( userWalletId = userWalletId, cryptoCurrencyId = cryptoCurrency.id, isSingleWalletWithTokens = false, @@ -300,7 +299,7 @@ internal class TokenDetailsModel @Inject constructor( private fun subscribeOnCurrencyStatusUpdates() { modelScope.launch(dispatchers.main) { - getCurrencyStatusUpdatesUseCase( + getSingleCryptoCurrencyStatusUseCase.invokeMultiWallet( userWalletId = userWalletId, currencyId = cryptoCurrency.id, isSingleWalletWithTokens = userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken(), @@ -364,7 +363,6 @@ internal class TokenDetailsModel @Inject constructor( updateDelayedCurrencyStatusUseCase( userWalletId = userWalletId, network = toCryptoCurrency.network, - refresh = true, ) } } diff --git a/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/model/TxHistoryModel.kt b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/model/TxHistoryModel.kt index fc7568fafc..13948922c0 100644 --- a/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/model/TxHistoryModel.kt +++ b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/model/TxHistoryModel.kt @@ -8,7 +8,7 @@ import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.navigation.url.UrlOpener import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.common.util.cardTypesResolver -import com.tangem.domain.tokens.GetCurrencyStatusUpdatesUseCase +import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.txhistory.models.TxHistoryStateError @@ -39,7 +39,7 @@ internal class TxHistoryModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, private val txHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase, - private val getCurrencyStatusUpdatesUseCase: GetCurrencyStatusUpdatesUseCase, + private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase, private val getUserWalletUseCase: GetUserWalletUseCase, private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase, private val urlOpener: UrlOpener, @@ -165,7 +165,7 @@ internal class TxHistoryModel @Inject constructor( val userWallet: UserWallet = requireNotNull(getUserWalletUseCase(params.userWalletId).getOrNull()) { "User wallet not found" } - getCurrencyStatusUpdatesUseCase( + getSingleCryptoCurrencyStatusUseCase.invokeMultiWallet( userWalletId = params.userWalletId, currencyId = params.currency.id, isSingleWalletWithTokens = userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken(), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt index f9790aaa18..4695f54f1a 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt @@ -8,7 +8,7 @@ import com.tangem.domain.nft.analytics.NFTAnalyticsEvent import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.settings.ShouldShowMarketsTooltipUseCase import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase -import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase +import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase import com.tangem.domain.tokens.TokensAction import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.TokenActionsState @@ -67,7 +67,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( private val walletWarningsClickIntents: WalletWarningsClickIntentsImplementor, private val onrampStatusFactory: OnrampStatusFactory, private val getUserWalletUseCase: GetUserWalletUseCase, - private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase, + private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase, private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCase, private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase, private val shouldShowMarketsTooltipUseCase: ShouldShowMarketsTooltipUseCase, @@ -162,7 +162,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( override fun onTransactionClick(txHash: String) { modelScope.launch(dispatchers.main) { - val currency = getPrimaryCurrencyStatusUpdatesUseCase.unwrap( + val currency = getSingleCryptoCurrencyStatusUseCase.unwrap( userWalletId = stateHolder.getSelectedWalletId(), ) ?.currency diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletCurrencyActionsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletCurrencyActionsClickIntents.kt index be96aa27a4..c4d18c7023 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletCurrencyActionsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletCurrencyActionsClickIntents.kt @@ -34,7 +34,7 @@ import com.tangem.domain.promo.GetStoryContentUseCase import com.tangem.domain.promo.models.StoryContentIds import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.staking.model.stakekit.Yield -import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase +import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase import com.tangem.domain.tokens.IsCryptoCurrencyCoinCouldHideUseCase import com.tangem.domain.tokens.RemoveCurrencyUseCase import com.tangem.domain.tokens.legacy.TradeCryptoAction @@ -110,7 +110,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, private val walletManagersFacade: WalletManagersFacade, private val isDemoCardUseCase: IsDemoCardUseCase, - private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase, + private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase, private val isCryptoCurrencyCoinCouldHide: IsCryptoCurrencyCoinCouldHideUseCase, private val removeCurrencyUseCase: RemoveCurrencyUseCase, private val getExploreUrlUseCase: GetExploreUrlUseCase, @@ -489,7 +489,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( val userWalletId = stateHolder.getSelectedWalletId() modelScope.launch(dispatchers.main) { - val currencyStatus = getPrimaryCurrencyStatusUpdatesUseCase.unwrap(userWalletId) ?: return@launch + val currencyStatus = getSingleCryptoCurrencyStatusUseCase.unwrap(userWalletId) ?: return@launch when (val addresses = currencyStatus.value.networkAddress) { is NetworkAddress.Selectable -> { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt index fbe13e7f90..bdf8d3095b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt @@ -19,9 +19,6 @@ import com.tangem.domain.promo.ShouldShowPromoWalletUseCase import com.tangem.domain.promo.models.PromoId import com.tangem.domain.settings.NeverToSuggestRateAppUseCase import com.tangem.domain.settings.RemindToRateAppLaterUseCase -import com.tangem.domain.tokens.FetchTokenListUseCase -import com.tangem.domain.tokens.FetchTokenListUseCase.RefreshMode -import com.tangem.domain.tokens.TokensFeatureToggles import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.analytics.TokenSwapPromoAnalyticsEvent import com.tangem.domain.wallets.legacy.UserWalletsListManager.Lockable.UnlockType @@ -91,7 +88,6 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( private val stateHolder: WalletStateController, private val walletEventSender: WalletEventSender, private val derivePublicKeysUseCase: DerivePublicKeysUseCase, - private val fetchTokenListUseCase: FetchTokenListUseCase, private val setCardWasScannedUseCase: SetCardWasScannedUseCase, private val neverToSuggestRateAppUseCase: NeverToSuggestRateAppUseCase, private val remindToRateAppLaterUseCase: RemindToRateAppLaterUseCase, @@ -105,7 +101,6 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase, private val seedPhraseNotificationUseCase: SeedPhraseNotificationUseCase, private val urlOpener: UrlOpener, - private val tokensFeatureToggles: TokensFeatureToggles, private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher, private val appRouter: AppRouter, ) : BaseWalletClickIntents(), WalletWarningsClickIntents { @@ -148,22 +143,13 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( ).fold( ifLeft = { Timber.e(it, "Failed to derive public keys") }, ifRight = { - if (tokensFeatureToggles.isNetworksLoadingRefactoringEnabled) { - multiNetworkStatusFetcher( - params = MultiNetworkStatusFetcher.Params( - userWalletId = userWallet.walletId, - networks = missedAddressCurrencies.map(CryptoCurrency::network).toSet(), - ), - ) - } else { - fetchTokenListUseCase( + multiNetworkStatusFetcher( + params = MultiNetworkStatusFetcher.Params( userWalletId = userWallet.walletId, - mode = RefreshMode.SKIP_CURRENCIES, - currencies = missedAddressCurrencies, - ).onLeft { - Timber.e("Unable to refresh token list: $it") - } - } + networks = missedAddressCurrencies.map(CryptoCurrency::network).toSet(), + ), + ) + .onLeft { Timber.e("Unable to refresh token list: $it") } }, ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetSingleWalletWarningsFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetSingleWalletWarningsFactory.kt index b59d4bd433..e8b3b2aacb 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetSingleWalletWarningsFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetSingleWalletWarningsFactory.kt @@ -7,7 +7,7 @@ import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.models.StatusSource import com.tangem.domain.settings.IsReadyToShowRateAppUseCase -import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase +import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.wallets.models.UserWallet @@ -22,7 +22,7 @@ import javax.inject.Inject @ModelScoped internal class GetSingleWalletWarningsFactory @Inject constructor( - private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase, + private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase, private val isDemoCardUseCase: IsDemoCardUseCase, private val isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase, private val isNeedToBackupUseCase: IsNeedToBackupUseCase, @@ -36,7 +36,7 @@ internal class GetSingleWalletWarningsFactory @Inject constructor( val cardTypesResolver = userWallet.scanResponse.cardTypesResolver return combine( - flow = getPrimaryCurrencyStatusUpdatesUseCase(userWallet.walletId), + flow = getSingleCryptoCurrencyStatusUseCase.invokeSingleWallet(userWallet.walletId), flow2 = isReadyToShowRateAppUseCase().conflate(), flow3 = isNeedToBackupUseCase(userWallet.walletId).conflate(), flow4 = getWalletsUseCase().conflate(), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/UseCaseExt.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/UseCaseExt.kt index 1ab1765968..d21c569563 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/UseCaseExt.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/UseCaseExt.kt @@ -1,7 +1,7 @@ package com.tangem.feature.wallet.presentation.wallet.domain import arrow.core.Either -import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase +import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.wallets.models.UserWallet @@ -20,8 +20,8 @@ internal fun GetSelectedWalletSyncUseCase.unwrap(): UserWallet? { ) } -internal suspend fun GetPrimaryCurrencyStatusUpdatesUseCase.unwrap(userWalletId: UserWalletId): CryptoCurrencyStatus? { - return this(userWalletId) +internal suspend fun GetSingleCryptoCurrencyStatusUseCase.unwrap(userWalletId: UserWalletId): CryptoCurrencyStatus? { + return invokeSingleWallet(userWalletId) .conflate() .distinctUntilChanged() .filter(Either::isRight) @@ -35,11 +35,11 @@ internal suspend fun GetPrimaryCurrencyStatusUpdatesUseCase.unwrap(userWalletId: ) } -internal suspend fun GetPrimaryCurrencyStatusUpdatesUseCase.collectLatest( +internal suspend fun GetSingleCryptoCurrencyStatusUseCase.collectLatest( userWalletId: UserWalletId, onRight: suspend (CryptoCurrencyStatus) -> Unit, ) { - this(userWalletId = userWalletId) + invokeSingleWallet(userWalletId = userWalletId) .conflate() .distinctUntilChanged() .collectLatest { maybeStatus -> diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoader.kt index e4167b9c20..81601a2e31 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoader.kt @@ -6,7 +6,7 @@ import com.tangem.domain.onramp.GetOnrampTransactionsUseCase import com.tangem.domain.onramp.OnrampRemoveTransactionUseCase import com.tangem.domain.settings.SetWalletWithFundsFoundUseCase import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase -import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase +import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase import com.tangem.domain.wallets.models.UserWallet @@ -29,7 +29,7 @@ internal class SingleWalletContentLoader( private val clickIntents: WalletClickIntents, private val isRefresh: Boolean, private val stateHolder: WalletStateController, - private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase, + private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase, private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCase, private val getSingleWalletWarningsFactory: GetSingleWalletWarningsFactory, private val setWalletWithFundsFoundUseCase: SetWalletWithFundsFoundUseCase, @@ -48,7 +48,7 @@ internal class SingleWalletContentLoader( PrimaryCurrencySubscriber( userWallet = userWallet, stateHolder = stateHolder, - getPrimaryCurrencyStatusUpdatesUseCase = getPrimaryCurrencyStatusUpdatesUseCase, + getSingleCryptoCurrencyStatusUseCase = getSingleCryptoCurrencyStatusUseCase, setWalletWithFundsFoundUseCase = setWalletWithFundsFoundUseCase, getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, analyticsEventHandler = analyticsEventHandler, @@ -57,7 +57,7 @@ internal class SingleWalletContentLoader( userWallet = userWallet, stateHolder = stateHolder, clickIntents = clickIntents, - getPrimaryCurrencyStatusUpdatesUseCase = getPrimaryCurrencyStatusUpdatesUseCase, + getSingleCryptoCurrencyStatusUseCase = getSingleCryptoCurrencyStatusUseCase, getCryptoCurrencyActionsUseCase = getCryptoCurrencyActionsUseCase, ), SingleWalletNotificationsSubscriber( @@ -78,7 +78,7 @@ internal class SingleWalletContentLoader( clickIntents = clickIntents, getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, analyticsEventHandler = analyticsEventHandler, - getPrimaryCurrencyStatusUpdatesUseCase = getPrimaryCurrencyStatusUpdatesUseCase, + getSingleCryptoCurrencyStatusUseCase = getSingleCryptoCurrencyStatusUseCase, getOnrampTransactionsUseCase = getOnrampTransactionsUseCase, onrampRemoveTransactionUseCase = onrampRemoveTransactionUseCase, ), @@ -87,7 +87,7 @@ internal class SingleWalletContentLoader( isRefresh = isRefresh, stateHolder = stateHolder, clickIntents = clickIntents, - getPrimaryCurrencyStatusUpdatesUseCase = getPrimaryCurrencyStatusUpdatesUseCase, + getSingleCryptoCurrencyStatusUseCase = getSingleCryptoCurrencyStatusUseCase, txHistoryItemsCountUseCase = txHistoryItemsCountUseCase, txHistoryItemsUseCase = txHistoryItemsUseCase, ), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoaderFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoaderFactory.kt index 6e8e164e92..22f8884acd 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoaderFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoaderFactory.kt @@ -7,7 +7,7 @@ import com.tangem.domain.onramp.GetOnrampTransactionsUseCase import com.tangem.domain.onramp.OnrampRemoveTransactionUseCase import com.tangem.domain.settings.SetWalletWithFundsFoundUseCase import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase -import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase +import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase import com.tangem.domain.wallets.models.UserWallet @@ -22,7 +22,7 @@ import javax.inject.Inject @Suppress("LongParameterList") internal class SingleWalletContentLoaderFactory @Inject constructor( private val stateHolder: WalletStateController, - private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase, + private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase, private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCase, private val getSingleWalletWarningsFactory: GetSingleWalletWarningsFactory, private val setWalletWithFundsFoundUseCase: SetWalletWithFundsFoundUseCase, @@ -42,7 +42,7 @@ internal class SingleWalletContentLoaderFactory @Inject constructor( clickIntents = clickIntents, isRefresh = isRefresh, stateHolder = stateHolder, - getPrimaryCurrencyStatusUpdatesUseCase = getPrimaryCurrencyStatusUpdatesUseCase, + getSingleCryptoCurrencyStatusUseCase = getSingleCryptoCurrencyStatusUseCase, getCryptoCurrencyActionsUseCase = getCryptoCurrencyActionsUseCase, getSingleWalletWarningsFactory = getSingleWalletWarningsFactory, setWalletWithFundsFoundUseCase = setWalletWithFundsFoundUseCase, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/PrimaryCurrencySubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/PrimaryCurrencySubscriber.kt index 855300bc2c..c5fd60f86a 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/PrimaryCurrencySubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/PrimaryCurrencySubscriber.kt @@ -8,7 +8,7 @@ import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.settings.SetWalletWithFundsFoundUseCase -import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase +import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.wallets.models.UserWallet @@ -23,7 +23,7 @@ import java.math.BigDecimal internal class PrimaryCurrencySubscriber( private val userWallet: UserWallet, private val stateHolder: WalletStateController, - private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase, + private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase, private val setWalletWithFundsFoundUseCase: SetWalletWithFundsFoundUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val analyticsEventHandler: AnalyticsEventHandler, @@ -33,7 +33,7 @@ internal class PrimaryCurrencySubscriber( coroutineScope: CoroutineScope, ): Flow, AppCurrency>> { return combine( - flow = getPrimaryCurrencyStatusUpdatesUseCase(userWallet.walletId) + flow = getSingleCryptoCurrencyStatusUseCase.invokeSingleWallet(userWallet.walletId) .conflate() .distinctUntilChanged(), flow2 = getSelectedAppCurrencyUseCase() diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletButtonsSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletButtonsSubscriber.kt index 3404216570..2066f30975 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletButtonsSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletButtonsSubscriber.kt @@ -1,7 +1,7 @@ package com.tangem.feature.wallet.presentation.wallet.subscribers import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase -import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase +import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase import com.tangem.domain.tokens.model.TokenActionsState import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents @@ -15,13 +15,13 @@ internal class SingleWalletButtonsSubscriber( private val userWallet: UserWallet, private val stateHolder: WalletStateController, private val clickIntents: WalletClickIntents, - private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase, + private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase, private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCase, ) : WalletSubscriber() { override fun create(coroutineScope: CoroutineScope): Flow { return channelFlow { - getPrimaryCurrencyStatusUpdatesUseCase.collectLatest(userWalletId = userWallet.walletId) { status -> + getSingleCryptoCurrencyStatusUseCase.collectLatest(userWalletId = userWallet.walletId) { status -> getCryptoCurrencyActionsUseCase(userWallet = userWallet, cryptoCurrencyStatus = status) .conflate() .distinctUntilChanged() diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletExpressStatusesSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletExpressStatusesSubscriber.kt index fd21c385d1..8f46c558d4 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletExpressStatusesSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletExpressStatusesSubscriber.kt @@ -8,7 +8,7 @@ import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.onramp.GetOnrampTransactionsUseCase import com.tangem.domain.onramp.OnrampRemoveTransactionUseCase import com.tangem.domain.onramp.model.cache.OnrampTransaction -import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase +import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.wallets.models.UserWallet @@ -26,7 +26,7 @@ internal class SingleWalletExpressStatusesSubscriber( private val clickIntents: WalletClickIntents, private val analyticsEventHandler: AnalyticsEventHandler, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, - private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase, + private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase, private val getOnrampTransactionsUseCase: GetOnrampTransactionsUseCase, private val onrampRemoveTransactionUseCase: OnrampRemoveTransactionUseCase, ) : WalletSubscriber() { @@ -35,7 +35,7 @@ internal class SingleWalletExpressStatusesSubscriber( coroutineScope: CoroutineScope, ): Flow, AppCurrency>> { return combine( - flow = getPrimaryCurrencyStatusUpdatesUseCase(userWalletId = userWallet.walletId) + flow = getSingleCryptoCurrencyStatusUseCase.invokeSingleWallet(userWalletId = userWallet.walletId) .conflate() .distinctUntilChanged(), flow2 = getSelectedAppCurrencyUseCase() diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TxHistorySubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TxHistorySubscriber.kt index ad67739b99..3058397289 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TxHistorySubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TxHistorySubscriber.kt @@ -5,7 +5,7 @@ import androidx.paging.cachedIn import androidx.paging.map import arrow.core.Either import com.tangem.domain.common.util.cardTypesResolver -import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase +import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.domain.txhistory.models.TxHistoryListError @@ -35,14 +35,14 @@ internal class TxHistorySubscriber( private val isRefresh: Boolean, private val stateHolder: WalletStateController, private val clickIntents: WalletClickIntents, - private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase, + private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase, private val txHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase, private val txHistoryItemsUseCase: GetTxHistoryItemsUseCase, ) : WalletSubscriber() { override fun create(coroutineScope: CoroutineScope): Flow> { return flow { - getPrimaryCurrencyStatusUpdatesUseCase.collectLatest(userWalletId = userWallet.walletId) { status -> + getSingleCryptoCurrencyStatusUseCase.collectLatest(userWalletId = userWallet.walletId) { status -> val maybeTxHistoryItemCount = txHistoryItemsCountUseCase( userWalletId = userWallet.walletId, currency = status.currency, diff --git a/features/walletconnect/api/src/main/kotlin/com/tangem/features/walletconnect/components/WalletConnectEntryComponent.kt b/features/walletconnect/api/src/main/kotlin/com/tangem/features/walletconnect/components/WalletConnectEntryComponent.kt index c7d60d1091..2b584ceaae 100644 --- a/features/walletconnect/api/src/main/kotlin/com/tangem/features/walletconnect/components/WalletConnectEntryComponent.kt +++ b/features/walletconnect/api/src/main/kotlin/com/tangem/features/walletconnect/components/WalletConnectEntryComponent.kt @@ -2,7 +2,9 @@ package com.tangem.features.walletconnect.components import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.domain.wallets.models.UserWalletId interface WalletConnectEntryComponent : ComposableContentComponent { - interface Factory : ComponentFactory + data class Params(val userWalletId: UserWalletId) + interface Factory : ComponentFactory } \ No newline at end of file diff --git a/features/walletconnect/impl/build.gradle.kts b/features/walletconnect/impl/build.gradle.kts index 02b082fd94..77ae59129c 100644 --- a/features/walletconnect/impl/build.gradle.kts +++ b/features/walletconnect/impl/build.gradle.kts @@ -12,9 +12,8 @@ android { } dependencies { + implementation(projects.features.wallet.api) implementation(projects.features.walletconnect.api) - implementation(projects.domain.walletConnect) - implementation(projects.domain.walletConnect.models) /** Core */ implementation(projects.core.configToggles) @@ -24,13 +23,22 @@ dependencies { implementation(projects.common.ui) /** Domain models */ + implementation(projects.domain.appCurrency.models) + implementation(projects.domain.balanceHiding.models) implementation(projects.domain.blockaid.models) + implementation(projects.domain.models) implementation(projects.domain.qrScanning.models) implementation(projects.domain.tokens.models) implementation(projects.domain.wallets.models) + implementation(projects.domain.walletConnect) + implementation(projects.domain.walletConnect.models) /** Domain */ + implementation(projects.domain.appCurrency) + implementation(projects.domain.balanceHiding) implementation(projects.domain.qrScanning) + implementation(projects.domain.tokens) + implementation(projects.domain.wallets) /** DI */ implementation(deps.hilt.android) diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/ConnectionsComponent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/ConnectionsComponent.kt index 6c6a74e65a..45e927da97 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/ConnectionsComponent.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/ConnectionsComponent.kt @@ -13,15 +13,17 @@ import com.tangem.core.decompose.context.childByContext import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.ui.decompose.ComposableBottomSheetComponent import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.domain.wallets.models.UserWalletId import com.tangem.features.walletconnect.connections.model.WcConnectionsModel import com.tangem.features.walletconnect.connections.routes.WcConnectionsBottomSheetConfig import com.tangem.features.walletconnect.connections.ui.WcConnectionsContent internal class ConnectionsComponent( appComponentContext: AppComponentContext, + params: Params, ) : AppComponentContext by appComponentContext, ComposableContentComponent { - private val model: WcConnectionsModel = getOrCreateModel() + private val model: WcConnectionsModel = getOrCreateModel(params) private val bottomSheetSlot = childSlot( source = model.bottomSheetNavigation, serializer = null, @@ -52,4 +54,6 @@ internal class ConnectionsComponent( ) } } + + data class Params(val userWalletId: UserWalletId) } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/DefaultWalletConnectEntryComponent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/DefaultWalletConnectEntryComponent.kt index 3f3ee812d0..f6cce51fd1 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/DefaultWalletConnectEntryComponent.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/DefaultWalletConnectEntryComponent.kt @@ -22,7 +22,7 @@ import dagger.assisted.AssistedInject @Suppress("UnusedPrivateMember") internal class DefaultWalletConnectEntryComponent @AssistedInject constructor( @Assisted private val appComponentContext: AppComponentContext, - @Assisted params: Unit, + @Assisted private val params: WalletConnectEntryComponent.Params, ) : AppComponentContext by appComponentContext, WalletConnectEntryComponent { private val contentNavigation = StackNavigation() @@ -47,12 +47,18 @@ internal class DefaultWalletConnectEntryComponent @AssistedInject constructor( config: ConnectionsInnerRoute, componentContext: ComponentContext, ): ComposableContentComponent = when (config) { - ConnectionsInnerRoute.Connections -> ConnectionsComponent(childByContext(componentContext)) + ConnectionsInnerRoute.Connections -> ConnectionsComponent( + appComponentContext = childByContext(componentContext), + params = ConnectionsComponent.Params(params.userWalletId), + ) ConnectionsInnerRoute.QrScan -> TODO("[REDACTED_JIRA]") } @AssistedFactory interface Factory : WalletConnectEntryComponent.Factory { - override fun create(context: AppComponentContext, params: Unit): DefaultWalletConnectEntryComponent + override fun create( + context: AppComponentContext, + params: WalletConnectEntryComponent.Params, + ): DefaultWalletConnectEntryComponent } } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/WcAppInfoContainerComponent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/WcAppInfoContainerComponent.kt index bcfbcc79ea..be95b71cd3 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/WcAppInfoContainerComponent.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/WcAppInfoContainerComponent.kt @@ -1,13 +1,12 @@ package com.tangem.features.walletconnect.connections.components +import androidx.compose.animation.animateContentSize import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.extensions.compose.stack.Children -import com.arkivanov.decompose.extensions.compose.stack.animation.stackAnimation import com.arkivanov.decompose.extensions.compose.subscribeAsState -import com.arkivanov.decompose.router.stack.StackNavigation import com.arkivanov.decompose.router.stack.childStack import com.arkivanov.decompose.router.stack.pop import com.tangem.core.decompose.context.AppComponentContext @@ -18,8 +17,10 @@ import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.TangemTheme import com.tangem.domain.walletconnect.model.WcPairRequest +import com.tangem.domain.wallets.models.UserWalletId import com.tangem.features.walletconnect.connections.model.WcAppInfoModel import com.tangem.features.walletconnect.connections.routes.WcAppInfoRoutes import com.tangem.features.walletconnect.impl.R @@ -30,9 +31,8 @@ internal class WcAppInfoContainerComponent( ) : AppComponentContext by appComponentContext, ComposableContentComponent { private val model: WcAppInfoModel = getOrCreateModel(params = params) - private val contentNavigation = StackNavigation() private val contentStack = childStack( - source = contentNavigation, + source = model.contentNavigation, serializer = WcAppInfoRoutes.serializer(), initialConfiguration = WcAppInfoRoutes.AppInfo, childFactory = ::screenChild, @@ -51,10 +51,17 @@ internal class WcAppInfoContainerComponent( onDismissRequest = ::dismiss, content = content.active.configuration, ), - containerColor = TangemTheme.colors.background.tertiary, + onBack = ::contentBack, + containerColor = when (content.active.configuration) { + is WcAppInfoRoutes.Alert, + is WcAppInfoRoutes.SelectNetworks, + is WcAppInfoRoutes.AppInfo, + -> TangemTheme.colors.background.tertiary + is WcAppInfoRoutes.SelectWallet -> TangemTheme.colors.background.primary + }, title = { config -> Title(route = config) }, content = { - Children(stack = content, animation = stackAnimation()) { child -> + Children(modifier = Modifier.animateContentSize(), stack = content) { child -> child.instance.Content(modifier = Modifier) } }, @@ -64,7 +71,12 @@ internal class WcAppInfoContainerComponent( @Composable private fun Title(route: WcAppInfoRoutes) { TangemModalBottomSheetTitle( - title = resourceReference(R.string.wc_wallet_connect), + title = when (route) { + is WcAppInfoRoutes.Alert -> null + WcAppInfoRoutes.AppInfo -> resourceReference(R.string.wc_wallet_connect) + WcAppInfoRoutes.SelectNetworks -> TODO("[REDACTED_TASK_KEY]") + WcAppInfoRoutes.SelectWallet -> stringReference("Choose wallet") + }, startIconRes = when (route) { is WcAppInfoRoutes.AppInfo, is WcAppInfoRoutes.Alert -> null is WcAppInfoRoutes.SelectNetworks, is WcAppInfoRoutes.SelectWallet -> R.drawable.ic_back_24 @@ -88,19 +100,25 @@ internal class WcAppInfoContainerComponent( } private fun contentBack() { - contentNavigation.pop() + if (contentStack.value.active.configuration == WcAppInfoRoutes.AppInfo) { + dismiss() + } else { + model.contentNavigation.pop() + } } - private fun screenChild(config: WcAppInfoRoutes, componentContext: ComponentContext): ComposableContentComponent = - when (config) { - is WcAppInfoRoutes.AppInfo -> WcAppInfoComponent( - appComponentContext = childByContext(componentContext), - model = model, - ) + private fun screenChild(config: WcAppInfoRoutes, componentContext: ComponentContext): ComposableContentComponent { + val appComponentContext = childByContext(componentContext) + return when (config) { + is WcAppInfoRoutes.AppInfo -> WcAppInfoComponent(appComponentContext = appComponentContext, model = model) is WcAppInfoRoutes.Alert -> TODO() WcAppInfoRoutes.SelectNetworks -> TODO() - WcAppInfoRoutes.SelectWallet -> TODO() + WcAppInfoRoutes.SelectWallet -> WcSelectWalletComponent( + appComponentContext = appComponentContext, + model = model, + ) } + } - data class Params(val wcUrl: String, val source: WcPairRequest.Source) + data class Params(val userWalletId: UserWalletId, val wcUrl: String, val source: WcPairRequest.Source) } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/WcSelectWalletComponent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/WcSelectWalletComponent.kt new file mode 100644 index 0000000000..d6c7e19964 --- /dev/null +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/WcSelectWalletComponent.kt @@ -0,0 +1,130 @@ +package com.tangem.features.walletconnect.connections.components + +import android.content.res.Configuration +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.key +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.util.fastForEach +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.tangem.common.ui.userwallet.UserWalletItem +import com.tangem.common.ui.userwallet.state.UserWalletItemUM +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.ui.components.block.TangemBlockCardColors +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.features.walletconnect.connections.model.WcAppInfoModel +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf + +internal class WcSelectWalletComponent( + appComponentContext: AppComponentContext, + private val model: WcAppInfoModel, +) : AppComponentContext by appComponentContext, ComposableContentComponent { + + @Composable + override fun Content(modifier: Modifier) { + val walletsState by model.walletsUiState.collectAsStateWithLifecycle() + WcSelectWalletContent( + modifier = modifier.padding(start = 16.dp, end = 16.dp, bottom = 16.dp), + wallets = walletsState.wallets, + selectedWalletId = walletsState.selectedUserWallet.walletId, + ) + } +} + +@Composable +private fun WcSelectWalletContent( + wallets: ImmutableList, + selectedWalletId: UserWalletId, + modifier: Modifier = Modifier, +) { + Column(modifier = modifier) { + wallets.fastForEach { state -> + key(state.id) { + val baseModifier = Modifier + .clip(RoundedCornerShape(14.dp)) + .clickable(onClick = state.onClick) + val itemModifier = if (state.id == selectedWalletId) { + baseModifier.border( + width = 1.dp, + color = TangemTheme.colors.text.accent, + shape = RoundedCornerShape(14.dp), + ) + } else { + baseModifier + } + UserWalletItem( + modifier = itemModifier, + state = state, + blockColors = TangemBlockCardColors.copy( + containerColor = Color.Unspecified, + disabledContainerColor = Color.Unspecified, + ), + ) + } + } + } +} + +@Composable +@Preview(showBackground = true, device = Devices.PIXEL_7_PRO) +@Preview(showBackground = true, device = Devices.PIXEL_7_PRO, uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun WcSelectWalletContent_Preview() { + TangemThemePreview { + WcSelectWalletContent( + modifier = Modifier + .background(TangemTheme.colors.background.tertiary) + .padding(start = 16.dp, end = 16.dp, bottom = 16.dp), + selectedWalletId = UserWalletId("user_wallet_1".encodeToByteArray()), + wallets = persistentListOf( + UserWalletItemUM( + id = UserWalletId("user_wallet_1".encodeToByteArray()), + name = stringReference("Tangem 2.0"), + information = stringReference("42 tokens"), + balance = UserWalletItemUM.Balance.Loaded("1 496,34 \$", isFlickering = false), + isEnabled = true, + onClick = {}, + ), + UserWalletItemUM( + id = UserWalletId("user_wallet_2".encodeToByteArray()), + name = stringReference("Tangem White"), + information = stringReference("24 tokens"), + balance = UserWalletItemUM.Balance.Loaded("1 496,34 \$", isFlickering = false), + isEnabled = true, + onClick = {}, + ), + UserWalletItemUM( + id = UserWalletId("user_wallet_3".encodeToByteArray()), + name = stringReference("Bitcoin"), + information = stringReference("1 token"), + balance = UserWalletItemUM.Balance.Loaded("1 496,34 \$", isFlickering = false), + isEnabled = true, + onClick = {}, + ), + UserWalletItemUM( + id = UserWalletId("user_wallet_4".encodeToByteArray()), + name = stringReference("Tangem 1.0"), + information = stringReference("21 tokens"), + balance = UserWalletItemUM.Balance.Loaded("1 496,34 \$", isFlickering = false), + isEnabled = true, + onClick = {}, + ), + ), + ) + } +} \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/entity/WcAppInfoUM.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/entity/WcAppInfoUM.kt index f7d17a2af5..92e385a3fa 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/entity/WcAppInfoUM.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/entity/WcAppInfoUM.kt @@ -26,6 +26,7 @@ internal sealed class WcAppInfoUM : TangemBottomSheetConfigContent { val appSubtitle: String, val notification: WcAppInfoSecurityNotification?, val walletName: String, + val onWalletClick: () -> Unit, val networksInfo: WcNetworksInfo, override val connectButtonConfig: WcPrimaryButtonConfig, override val onDismiss: () -> Unit, diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/entity/WcAppInfoWalletUM.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/entity/WcAppInfoWalletUM.kt new file mode 100644 index 0000000000..0c682702a0 --- /dev/null +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/entity/WcAppInfoWalletUM.kt @@ -0,0 +1,10 @@ +package com.tangem.features.walletconnect.connections.entity + +import com.tangem.common.ui.userwallet.state.UserWalletItemUM +import com.tangem.domain.wallets.models.UserWallet +import kotlinx.collections.immutable.ImmutableList + +internal data class WcAppInfoWalletUM( + val wallets: ImmutableList, + val selectedUserWallet: UserWallet, +) \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcAppInfoModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcAppInfoModel.kt index 3a6e4520ba..fe9348ef4d 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcAppInfoModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcAppInfoModel.kt @@ -1,47 +1,98 @@ package com.tangem.features.walletconnect.connections.model import androidx.compose.runtime.Stable +import com.arkivanov.decompose.router.stack.StackNavigation +import com.arkivanov.decompose.router.stack.pop +import com.arkivanov.decompose.router.stack.pushNew +import com.tangem.common.ui.userwallet.state.UserWalletItemUM import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.navigation.Router +import com.tangem.core.decompose.ui.UiMessageSender +import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase +import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase +import com.tangem.domain.tokens.GetWalletTotalBalanceUseCase import com.tangem.domain.walletconnect.model.WcPairRequest import com.tangem.domain.walletconnect.model.WcSessionApprove import com.tangem.domain.walletconnect.model.WcSessionProposal import com.tangem.domain.walletconnect.usecase.pair.WcPairState import com.tangem.domain.walletconnect.usecase.pair.WcPairUseCase import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.wallets.usecase.GetCardImageUseCase +import com.tangem.domain.wallets.usecase.GetWalletsUseCase import com.tangem.features.walletconnect.connections.components.WcAppInfoContainerComponent import com.tangem.features.walletconnect.connections.entity.WcAppInfoUM +import com.tangem.features.walletconnect.connections.entity.WcAppInfoWalletUM import com.tangem.features.walletconnect.connections.entity.WcPrimaryButtonConfig import com.tangem.features.walletconnect.connections.model.transformers.WcAppInfoTransformer +import com.tangem.features.walletconnect.connections.model.transformers.WcAppInfoWalletChangedTransformer import com.tangem.features.walletconnect.connections.model.transformers.WcConnectButtonProgressTransformer +import com.tangem.features.walletconnect.connections.routes.WcAppInfoRoutes +import com.tangem.features.walletconnect.connections.utils.WcUserWalletsFetcher import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.flow.* import javax.inject.Inject import kotlin.properties.Delegates import com.tangem.utils.transformer.update as transformerUpdate +@Suppress("LongParameterList") @Stable @ModelScoped internal class WcAppInfoModel @Inject constructor( + private val router: Router, override val dispatchers: CoroutineDispatcherProvider, wcPairUseCaseFactory: WcPairUseCase.Factory, - private val router: Router, + getWalletsUseCase: GetWalletsUseCase, + getWalletTotalBalanceUseCase: GetWalletTotalBalanceUseCase, + getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, + getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, + messageSender: UiMessageSender, + getCardImageUseCase: GetCardImageUseCase, paramsContainer: ParamsContainer, ) : Model() { private val params: WcAppInfoContainerComponent.Params = paramsContainer.require() - private val wcPairUseCase = wcPairUseCaseFactory.create(WcPairRequest(uri = params.wcUrl, source = params.source)) + private val wcPairUseCase = wcPairUseCaseFactory.create( + WcPairRequest( + userWalletId = params.userWalletId, + uri = params.wcUrl, + source = params.source, + ), + ) + private val userWalletsFetcher = WcUserWalletsFetcher( + getWalletsUseCase = getWalletsUseCase, + getWalletTotalBalanceUseCase = getWalletTotalBalanceUseCase, + getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, + getBalanceHidingSettingsUseCase = getBalanceHidingSettingsUseCase, + messageSender = messageSender, + getCardImageUseCase = getCardImageUseCase, + onWalletSelected = ::onWalletSelected, + ) + + private val selectedUserWalletFlow = + MutableStateFlow(getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId }) + // TODO(wc) Doston: Temp solution, will be fixed in next PR`s + private var proposalNetwork by Delegates.notNull() + internal var sessionProposal by Delegates.notNull() + + // UI states + internal val contentNavigation = StackNavigation() val appInfoUiState: StateFlow field = MutableStateFlow(createLoadingState()) - - // TODO(wc) Doston: Temp solution, will be fixed in next PR`s - private var userWallet by Delegates.notNull() - private var proposalNetwork by Delegates.notNull() + val walletsUiState: StateFlow + field = MutableStateFlow(WcAppInfoWalletUM(persistentListOf(), selectedUserWalletFlow.value)) init { loadDAppInfo() + combine( + flow = userWalletsFetcher.userWallets, + flow2 = selectedUserWalletFlow, + transform = ::updateWalletsState, + ).launchIn(modelScope) } private fun loadDAppInfo() { @@ -62,14 +113,15 @@ internal class WcAppInfoModel @Inject constructor( } is WcPairState.Loading -> appInfoUiState.update { createLoadingState() } is WcPairState.Proposal -> { - userWallet = state.dAppSession.proposalNetwork.keys.first { !it.isLocked } - proposalNetwork = state.dAppSession.proposalNetwork.getValue(userWallet) + sessionProposal = state.dAppSession + proposalNetwork = sessionProposal.proposalNetwork.getValue(selectedUserWalletFlow.value) appInfoUiState.transformerUpdate( WcAppInfoTransformer( dAppSession = state.dAppSession, onDismiss = ::dismiss, onConnect = ::onConnect, - userWallet = userWallet, + onWalletClick = { contentNavigation.pushNew(WcAppInfoRoutes.SelectWallet) }, + userWallet = selectedUserWalletFlow.value, proposalNetwork = proposalNetwork, ), ) @@ -87,13 +139,24 @@ internal class WcAppInfoModel @Inject constructor( private fun onConnect() { wcPairUseCase.approve( WcSessionApprove( - wallet = userWallet, - network = - proposalNetwork.required.plus(proposalNetwork.available).toList(), + wallet = selectedUserWalletFlow.value, + network = proposalNetwork.required.plus(proposalNetwork.available).toList(), ), ) } + private fun onWalletSelected(userWalletId: UserWalletId) { + val selectedUserWallet = sessionProposal.proposalNetwork.keys.first { it.walletId == userWalletId } + selectedUserWalletFlow.update { selectedUserWallet } + proposalNetwork = sessionProposal.proposalNetwork.getValue(selectedUserWallet) + appInfoUiState.transformerUpdate(WcAppInfoWalletChangedTransformer(selectedUserWallet, proposalNetwork)) + contentNavigation.pop() + } + + private fun updateWalletsState(items: ImmutableList, userWallet: UserWallet) { + walletsUiState.update { state -> state.copy(wallets = items, selectedUserWallet = userWallet) } + } + private fun createLoadingState(): WcAppInfoUM.Loading { return WcAppInfoUM.Loading( onDismiss = ::dismiss, diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcConnectedAppInfoModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcConnectedAppInfoModel.kt index 6126b5f404..a751963826 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcConnectedAppInfoModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcConnectedAppInfoModel.kt @@ -6,6 +6,7 @@ import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.ui.UiMessageSender +import com.tangem.core.ui.extensions.iconResId import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.message.SnackbarMessage import com.tangem.domain.walletconnect.model.WcSession @@ -13,9 +14,10 @@ import com.tangem.domain.walletconnect.usecase.WcSessionsUseCase import com.tangem.domain.walletconnect.usecase.disconnect.WcDisconnectUseCase import com.tangem.features.walletconnect.connections.components.WcConnectedAppInfoComponent import com.tangem.features.walletconnect.connections.entity.WcConnectedAppInfoUM +import com.tangem.features.walletconnect.connections.entity.WcNetworkInfoItem import com.tangem.features.walletconnect.connections.entity.WcPrimaryButtonConfig import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update @@ -56,7 +58,15 @@ internal class WcConnectedAppInfoModel @Inject constructor( isVerified = session.securityStatus == CheckDAppResult.SAFE, appSubtitle = session.sdkModel.appMetaData.description, walletName = session.wallet.name, - networks = persistentListOf(), // TODO(wc): Nikolai & Doston: Where to find networks??? + networks = session.networks + .map { + WcNetworkInfoItem( + id = it.id.value, + icon = it.iconResId, + name = it.name, + symbol = it.currencySymbol, + ) + }.toImmutableList(), disconnectButtonConfig = WcPrimaryButtonConfig( showProgress = false, enabled = true, diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcConnectionsModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcConnectionsModel.kt index 49062a9de9..96d5df7e28 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcConnectionsModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcConnectionsModel.kt @@ -6,6 +6,7 @@ import com.arkivanov.decompose.router.slot.activate import com.tangem.common.routing.AppRoute import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.navigation.Router import com.tangem.core.decompose.ui.UiMessageSender import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM @@ -16,11 +17,12 @@ import com.tangem.core.ui.message.EventMessageAction import com.tangem.core.ui.res.TangemTheme import com.tangem.domain.qrscanning.models.SourceType import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase -import com.tangem.domain.walletconnect.model.WcSession import com.tangem.domain.walletconnect.WcPairService import com.tangem.domain.walletconnect.model.WcPairRequest +import com.tangem.domain.walletconnect.model.WcSession import com.tangem.domain.walletconnect.usecase.WcSessionsUseCase import com.tangem.domain.walletconnect.usecase.disconnect.WcDisconnectUseCase +import com.tangem.features.walletconnect.connections.components.ConnectionsComponent import com.tangem.features.walletconnect.connections.entity.WcConnectionsState import com.tangem.features.walletconnect.connections.entity.WcConnectionsTopAppBarConfig import com.tangem.features.walletconnect.connections.model.transformers.WcSessionsTransformer @@ -44,8 +46,10 @@ internal class WcConnectionsModel @Inject constructor( private val wcDisconnectUseCase: WcDisconnectUseCase, private val wcPairService: WcPairService, override val dispatchers: CoroutineDispatcherProvider, + paramsContainer: ParamsContainer, ) : Model() { + private val params = paramsContainer.require() val uiState: StateFlow field = MutableStateFlow(getInitialState()) val bottomSheetNavigation: SlotNavigation = SlotNavigation() @@ -58,7 +62,15 @@ internal class WcConnectionsModel @Inject constructor( private fun listenQrUpdates() { listenToQrScanningUseCase(SourceType.WALLET_CONNECT) .getOrElse { emptyFlow() } - .onEach { wcUrl -> wcPairService.pair(WcPairRequest(wcUrl, WcPairRequest.Source.QR)) } + .onEach { wcUrl -> + wcPairService.pair( + WcPairRequest( + userWalletId = params.userWalletId, + uri = wcUrl, + source = WcPairRequest.Source.QR, + ), + ) + } .launchIn(modelScope) } diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/transformers/WcAppInfoTransformer.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/transformers/WcAppInfoTransformer.kt index 134338c410..2249504df5 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/transformers/WcAppInfoTransformer.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/transformers/WcAppInfoTransformer.kt @@ -1,17 +1,16 @@ package com.tangem.features.walletconnect.connections.model.transformers import com.domain.blockaid.models.dapp.CheckDAppResult -import com.tangem.core.ui.extensions.iconResId import com.tangem.domain.walletconnect.model.WcSessionProposal import com.tangem.domain.wallets.models.UserWallet import com.tangem.features.walletconnect.connections.entity.* import com.tangem.utils.transformer.Transformer -import kotlinx.collections.immutable.toImmutableList internal class WcAppInfoTransformer( private val dAppSession: WcSessionProposal, private val onDismiss: () -> Unit, private val onConnect: () -> Unit, + private val onWalletClick: () -> Unit, private val userWallet: UserWallet, private val proposalNetwork: WcSessionProposal.ProposalNetwork, ) : Transformer { @@ -24,7 +23,8 @@ internal class WcAppInfoTransformer( appSubtitle = dAppSession.dAppMetaData.description, notification = createNotification(dAppSession.securityStatus), walletName = userWallet.name, - networksInfo = convertNetworksInfo(proposalNetwork), + onWalletClick = onWalletClick, + networksInfo = WcNetworksInfoConverter.convert(proposalNetwork), connectButtonConfig = WcPrimaryButtonConfig( showProgress = false, enabled = proposalNetwork.missingRequired.isEmpty(), @@ -41,26 +41,4 @@ internal class WcAppInfoTransformer( CheckDAppResult.FAILED_TO_VERIFY -> WcAppInfoSecurityNotification.UnknownDomain } } - - private fun convertNetworksInfo(proposalNetwork: WcSessionProposal.ProposalNetwork): WcNetworksInfo { - return if (proposalNetwork.missingRequired.isNotEmpty()) { - WcNetworksInfo.MissingRequiredNetworkInfo( - networks = proposalNetwork.missingRequired - .joinToString { it.name }, - ) - } else { - WcNetworksInfo.ContainsAllRequiredNetworks( - items = (proposalNetwork.required + proposalNetwork.available) - .map { - WcNetworkInfoItem( - id = it.id.value, - icon = it.iconResId, - name = it.name, - symbol = it.currencySymbol, - ) - } - .toImmutableList(), - ) - } - } } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/transformers/WcAppInfoWalletChangedTransformer.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/transformers/WcAppInfoWalletChangedTransformer.kt new file mode 100644 index 0000000000..453ab9b608 --- /dev/null +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/transformers/WcAppInfoWalletChangedTransformer.kt @@ -0,0 +1,22 @@ +package com.tangem.features.walletconnect.connections.model.transformers + +import com.tangem.domain.walletconnect.model.WcSessionProposal +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.features.walletconnect.connections.entity.WcAppInfoUM +import com.tangem.utils.transformer.Transformer + +internal class WcAppInfoWalletChangedTransformer( + private val selectedUserWallet: UserWallet, + private val proposalNetwork: WcSessionProposal.ProposalNetwork, +) : Transformer { + override fun transform(prevState: WcAppInfoUM): WcAppInfoUM { + val contentState = prevState as? WcAppInfoUM.Content ?: return prevState + return contentState.copy( + walletName = selectedUserWallet.name, + networksInfo = WcNetworksInfoConverter.convert(proposalNetwork), + connectButtonConfig = prevState.connectButtonConfig.copy( + enabled = proposalNetwork.missingRequired.isEmpty(), + ), + ) + } +} \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/transformers/WcNetworksInfoConverter.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/transformers/WcNetworksInfoConverter.kt new file mode 100644 index 0000000000..8bf63c01cf --- /dev/null +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/transformers/WcNetworksInfoConverter.kt @@ -0,0 +1,32 @@ +package com.tangem.features.walletconnect.connections.model.transformers + +import com.tangem.core.ui.extensions.iconResId +import com.tangem.domain.walletconnect.model.WcSessionProposal +import com.tangem.features.walletconnect.connections.entity.WcNetworkInfoItem +import com.tangem.features.walletconnect.connections.entity.WcNetworksInfo +import com.tangem.utils.converter.Converter +import kotlinx.collections.immutable.toImmutableList + +internal object WcNetworksInfoConverter : Converter { + override fun convert(value: WcSessionProposal.ProposalNetwork): WcNetworksInfo { + return if (value.missingRequired.isNotEmpty()) { + WcNetworksInfo.MissingRequiredNetworkInfo( + networks = value.missingRequired + .joinToString { it.name }, + ) + } else { + WcNetworksInfo.ContainsAllRequiredNetworks( + items = (value.required + value.available) + .map { + WcNetworkInfoItem( + id = it.id.value, + icon = it.iconResId, + name = it.name, + symbol = it.currencySymbol, + ) + } + .toImmutableList(), + ) + } + } +} \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/DefaultWcRoutingComponent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/DefaultWcRoutingComponent.kt index cfe15122ed..45ccb328ab 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/DefaultWcRoutingComponent.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/DefaultWcRoutingComponent.kt @@ -16,7 +16,7 @@ import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.features.walletconnect.components.WcRoutingComponent import com.tangem.features.walletconnect.connections.components.WcAppInfoContainerComponent -import com.tangem.features.walletconnect.transaction.components.WcSignTransactionComponent +import com.tangem.features.walletconnect.transaction.components.WcSignTransactionContainerComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -55,13 +55,17 @@ internal class DefaultWcRoutingComponent @AssistedInject constructor( router = model.innerRouter, ) return when (config) { - is WcInnerRoute.SignMessage -> WcSignTransactionComponent( + is WcInnerRoute.SignMessage -> WcSignTransactionContainerComponent( childContext, - params = WcSignTransactionComponent.Params(config.rawRequest), + params = WcSignTransactionContainerComponent.Params(config.rawRequest), ) is WcInnerRoute.Pair -> WcAppInfoContainerComponent( childContext, - WcAppInfoContainerComponent.Params(config.request.uri, config.request.source), + WcAppInfoContainerComponent.Params( + userWalletId = config.request.userWalletId, + wcUrl = config.request.uri, + source = config.request.source, + ), ) } } diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/WcAppInfoContent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/WcAppInfoContent.kt index f248d6323c..136d5ac26b 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/WcAppInfoContent.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/WcAppInfoContent.kt @@ -220,7 +220,12 @@ private fun WcAppInfoSecondBlock(state: WcAppInfoUM.Content, modifier: Modifier val itemsModifier = Modifier .fillMaxWidth() .padding(TangemTheme.dimens.spacing12) - WalletRowItem(modifier = itemsModifier, walletName = state.walletName) + WalletRowItem( + modifier = Modifier + .clickable(onClick = state.onWalletClick) + .then(itemsModifier), + walletName = state.walletName, + ) HorizontalDivider(thickness = 1.dp, color = TangemTheme.colors.stroke.primary) SelectNetworksBlock( networksInfo = state.networksInfo, @@ -565,6 +570,7 @@ private class WcAppInfoStateProvider : CollectionPreviewParameterProvider Unit, +) { + + private var loadedArtworks: HashMap = hashMapOf() + + @OptIn(ExperimentalCoroutinesApi::class) + val userWallets: Flow> = getWalletsUseCase().transformLatest { wallets -> + val uiModels = UserWalletItemUMConverter(onClick = onWalletSelected) + .convertList(wallets) + .toImmutableList() + + emit(uiModels) + combine( + flow = getSelectedAppCurrencyUseCase().distinctUntilChanged(), + flow2 = getBalanceHidingSettingsUseCase().distinctUntilChanged(), + flow3 = getWalletTotalBalanceUseCase(wallets.map(UserWallet::walletId)).distinctUntilChanged(), + flow4 = loadArtworks(wallets), + ) { maybeAppCurrency, balanceHidingSettings, maybeBalances, artworks -> + val models = createUiModels( + wallets = wallets, + maybeAppCurrency = maybeAppCurrency, + maybeBalances = maybeBalances, + balanceHidingSettings = balanceHidingSettings, + artworks = artworks, + ).getOrElse( + ifLoading = { return@combine }, + ifError = { + val message = resourceReference(R.string.common_unknown_error) + messageSender.send(SnackbarMessage(message)) + + return@combine + }, + ) + + emit(models) + }.collect() + } + + private fun loadArtworks(wallets: List): Flow> { + return flow { + emit(hashMapOf()) // emits right away so the transform doesn't wait for the images' loading to finish + wallets.forEach { wallet -> + val card = wallet.scanResponse.card + val artwork = getCardImageUseCase( + cardId = wallet.cardId, + cardPublicKey = card.cardPublicKey, + size = ArtworkSize.SMALL, + manufacturerName = card.manufacturer.name, + firmwareVersion = card.firmwareVersion.toSdkFirmwareVersion(), + ) + loadedArtworks[wallet.walletId] = artwork + emit(loadedArtworks) + } + } + } + + private fun createUiModels( + wallets: List, + maybeAppCurrency: Either, + maybeBalances: Lce>, + balanceHidingSettings: BalanceHidingSettings, + artworks: HashMap, + ): Lce> = lce { + val balances = withError( + transform = { Error.UnableToGetBalances }, + block = { + maybeBalances.bindOrNull().orEmpty() + .filterKeys { userWalletId -> wallets.any { it.walletId == userWalletId } } + .mapKeys { entry -> wallets.first { it.walletId == entry.key } } + }, + ) + + val appCurrency = withError( + transform = { Error.UnableToGetAppCurrency }, + block = { maybeAppCurrency.toLce().bind() }, + ) + + balances + .map { (userWallet, balance) -> + UserWalletItemUMConverter( + onClick = onWalletSelected, + appCurrency = appCurrency, + balance = balance, + isBalanceHidden = balanceHidingSettings.isBalanceHidden, + artwork = artworks[userWallet.walletId], + ) + .convert(userWallet) + } + .toImmutableList() + } + + sealed class Error { + + data object UnableToGetAppCurrency : Error() + + data object UnableToGetBalances : Error() + } +} \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/WcSignTransactionComponent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/WcSignTransactionComponent.kt index 9ec9f43b8f..9ffe0a28d5 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/WcSignTransactionComponent.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/WcSignTransactionComponent.kt @@ -1,37 +1,28 @@ package com.tangem.features.walletconnect.transaction.components import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.tangem.core.decompose.context.AppComponentContext -import com.tangem.core.decompose.model.getOrCreateModel -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.decompose.ComposableContentComponent -import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest import com.tangem.features.walletconnect.transaction.model.WcSignTransactionModel -import com.tangem.features.walletconnect.transaction.ui.sign.WcSignTransactionModalBottomSheet +import com.tangem.features.walletconnect.transaction.ui.sign.WcSignTransactionModalBottomSheetContent internal class WcSignTransactionComponent( - appComponentContext: AppComponentContext, - params: Params, + private val appComponentContext: AppComponentContext, + private val model: WcSignTransactionModel, + private val transactionInfoOnClick: () -> Unit, ) : AppComponentContext by appComponentContext, ComposableContentComponent { - private val model: WcSignTransactionModel = getOrCreateModel(params) - @Composable override fun Content(modifier: Modifier) { - val content by model.uiState.collectAsStateWithLifecycle() - content?.let { - WcSignTransactionModalBottomSheet( - config = TangemBottomSheetConfig( - isShown = true, - onDismissRequest = it.actions.onDismiss, - content = it, - ), + val content = model.uiState.collectAsStateWithLifecycle().value + + if (content != null) { + WcSignTransactionModalBottomSheetContent( + state = content, + onClickTransactionRequest = transactionInfoOnClick, ) } } - - data class Params(val rawRequest: WcSdkSessionRequest) } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/WcSignTransactionContainerComponent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/WcSignTransactionContainerComponent.kt new file mode 100644 index 0000000000..7bfc3e7738 --- /dev/null +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/WcSignTransactionContainerComponent.kt @@ -0,0 +1,123 @@ +package com.tangem.features.walletconnect.transaction.components + +import androidx.compose.animation.animateContentSize +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import com.arkivanov.decompose.ComponentContext +import com.arkivanov.decompose.extensions.compose.stack.Children +import com.arkivanov.decompose.extensions.compose.subscribeAsState +import com.arkivanov.decompose.router.stack.StackNavigation +import com.arkivanov.decompose.router.stack.childStack +import com.arkivanov.decompose.router.stack.navigate +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.context.childByContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet +import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest +import com.tangem.features.walletconnect.impl.R +import com.tangem.features.walletconnect.transaction.model.WcSignTransactionModel +import com.tangem.features.walletconnect.transaction.routes.WcSignTransactionRoutes + +internal class WcSignTransactionContainerComponent( + private val appComponentContext: AppComponentContext, + params: Params, +) : AppComponentContext by appComponentContext, ComposableContentComponent { + + private val model: WcSignTransactionModel = getOrCreateModel(params = params) + private val contentNavigation = StackNavigation() + private val contentStack = childStack( + source = contentNavigation, + serializer = WcSignTransactionRoutes.serializer(), + initialConfiguration = WcSignTransactionRoutes.Transaction, + childFactory = ::screenChild, + ) + + private fun dismiss() { + model.dismiss() + } + + @Composable + override fun Content(modifier: Modifier) { + val content by contentStack.subscribeAsState() + TangemModalBottomSheet( + config = TangemBottomSheetConfig( + isShown = true, + onDismissRequest = ::dismiss, + content = content.active.configuration, + ), + containerColor = TangemTheme.colors.background.tertiary, + title = { config -> Title(route = config) }, + content = { + Box( + modifier = Modifier + .fillMaxWidth() + .animateContentSize(), + ) { + Children(stack = content) { child -> + child.instance.Content(modifier = Modifier) + } + } + }, + ) + } + + @Composable + private fun Title(route: WcSignTransactionRoutes) { + TangemModalBottomSheetTitle( + title = resourceReference(R.string.wc_wallet_connect), + startIconRes = when (route) { + is WcSignTransactionRoutes.Transaction -> null + else -> R.drawable.ic_back_24 + }, + onStartClick = when (route) { + is WcSignTransactionRoutes.Transaction -> null + else -> ::toTransaction + }, + endIconRes = when (route) { + is WcSignTransactionRoutes.Transaction -> R.drawable.ic_close_24 + else -> null + }, + onEndClick = when (route) { + is WcSignTransactionRoutes.Transaction -> ::dismiss + else -> null + }, + ) + } + + private fun toTransactionRequestInfo() { + contentNavigation.navigate { + listOf(WcSignTransactionRoutes.TransactionRequestInfo) + } + } + + private fun toTransaction() { + contentNavigation.navigate { + listOf(WcSignTransactionRoutes.Transaction) + } + } + + private fun screenChild( + config: WcSignTransactionRoutes, + componentContext: ComponentContext, + ): ComposableContentComponent = when (config) { + is WcSignTransactionRoutes.Transaction -> WcSignTransactionComponent( + appComponentContext = childByContext(componentContext), + model = model, + transactionInfoOnClick = ::toTransactionRequestInfo, + ) + is WcSignTransactionRoutes.TransactionRequestInfo -> WcSignTransactionRequestInfoComponent( + appComponentContext = childByContext(componentContext), + model = model, + ) + } + + data class Params(val rawRequest: WcSdkSessionRequest) +} \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/WcSignTransactionRequestInfoComponent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/WcSignTransactionRequestInfoComponent.kt new file mode 100644 index 0000000000..7563b8a646 --- /dev/null +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/WcSignTransactionRequestInfoComponent.kt @@ -0,0 +1,24 @@ +package com.tangem.features.walletconnect.transaction.components + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.features.walletconnect.transaction.model.WcSignTransactionModel +import com.tangem.features.walletconnect.transaction.ui.common.TransactionRequestInfoContent + +internal class WcSignTransactionRequestInfoComponent( + private val appComponentContext: AppComponentContext, + private val model: WcSignTransactionModel, +) : AppComponentContext by appComponentContext, ComposableContentComponent { + + @Composable + override fun Content(modifier: Modifier) { + val content = model.uiState.collectAsStateWithLifecycle().value + + if (content != null) { + TransactionRequestInfoContent(content) + } + } +} \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/WcSignTransactionUM.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/WcSignTransactionUM.kt index 3f796ce6b1..77379d44e1 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/WcSignTransactionUM.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/WcSignTransactionUM.kt @@ -8,19 +8,10 @@ import kotlinx.collections.immutable.ImmutableList @Immutable internal data class WcSignTransactionUM( - @DrawableRes val startIconRes: Int, - @DrawableRes val endIconRes: Int, - @DrawableRes val transactionIconRes: Int, val actions: WcTransactionActionsUM, - val state: State = State.TRANSACTION, val transaction: WcTransactionUM, val transactionRequestInfo: WcTransactionRequestInfoUM, -) : TangemBottomSheetConfigContent { - - enum class State { - TRANSACTION, TRANSACTION_REQUEST_INFO - } -} +) : TangemBottomSheetConfigContent @Immutable internal data class WcTransactionUM( @@ -30,26 +21,31 @@ internal data class WcTransactionUM( val appSubtitle: String, val walletName: String, val networkInfo: WcNetworkInfoUM, + val addressText: String? = null, + val networkFee: String? = null, val isLoading: Boolean = false, ) @Immutable internal data class WcTransactionRequestInfoUM( + val blocks: ImmutableList, +) + +@Immutable +internal data class WcTransactionRequestBlockUM( val info: ImmutableList, ) @Immutable internal data class WcTransactionRequestInfoItemUM( val title: TextReference, - val description: String, + val description: String = "", ) @Immutable internal data class WcTransactionActionsUM( - val transactionRequestOnClick: () -> Unit, val onDismiss: () -> Unit, val onSign: () -> Unit, - val onBack: () -> Unit, val onCopy: () -> Unit, ) diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSignTransactionModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSignTransactionModel.kt index e5b5c90b30..ed6ea36064 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSignTransactionModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSignTransactionModel.kt @@ -10,7 +10,7 @@ import com.tangem.domain.walletconnect.WcRequestUseCaseFactory import com.tangem.domain.walletconnect.usecase.method.WcSignState import com.tangem.domain.walletconnect.usecase.method.WcSignStep import com.tangem.domain.walletconnect.usecase.method.WcSignUseCase -import com.tangem.features.walletconnect.transaction.components.WcSignTransactionComponent +import com.tangem.features.walletconnect.transaction.components.WcSignTransactionContainerComponent import com.tangem.features.walletconnect.transaction.entity.WcSignTransactionUM import com.tangem.features.walletconnect.transaction.entity.WcTransactionActionsUM import com.tangem.features.walletconnect.transaction.utils.toUM @@ -35,7 +35,7 @@ internal class WcSignTransactionModel @Inject constructor( private val _uiState = MutableStateFlow(null) val uiState: StateFlow = _uiState - private val params = paramsContainer.require() + private val params = paramsContainer.require() init { modelScope.launch { @@ -47,10 +47,8 @@ internal class WcSignTransactionModel @Inject constructor( signState = signState, actions = WcTransactionActionsUM( onDismiss = { cancel(useCase) }, - onBack = ::showTransactionState, onSign = useCase::sign, onCopy = { copyData(useCase.rawSdkRequest.request.params) }, - transactionRequestOnClick = ::showTransactionRequestState, ), ) _uiState.emit(signTransactionUM) @@ -59,6 +57,10 @@ internal class WcSignTransactionModel @Inject constructor( } } + fun dismiss() { + _uiState.value?.actions?.onDismiss?.invoke() ?: router.pop() + } + private fun signingIsDone(signState: WcSignState<*>): Boolean { (signState.domainStep as? WcSignStep.Result)?.result?.let { router.pop() @@ -75,12 +77,4 @@ internal class WcSignTransactionModel @Inject constructor( private fun copyData(text: String) { clipboardManager.setText(text = text, isSensitive = true) } - - private fun showTransactionRequestState() { - _uiState.value = _uiState.value?.copy(state = WcSignTransactionUM.State.TRANSACTION_REQUEST_INFO) - } - - private fun showTransactionState() { - _uiState.value = _uiState.value?.copy(state = WcSignTransactionUM.State.TRANSACTION) - } } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/routes/WcSignTransactionRoutes.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/routes/WcSignTransactionRoutes.kt new file mode 100644 index 0000000000..6c5c1d9fa8 --- /dev/null +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/routes/WcSignTransactionRoutes.kt @@ -0,0 +1,15 @@ +package com.tangem.features.walletconnect.transaction.routes + +import androidx.compose.runtime.Immutable +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import kotlinx.serialization.Serializable + +@Serializable +@Immutable +internal sealed class WcSignTransactionRoutes : TangemBottomSheetConfigContent { + @Serializable + data object Transaction : WcSignTransactionRoutes() + + @Serializable + data object TransactionRequestInfo : WcSignTransactionRoutes() +} \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/TransactionRequestInfoContent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/TransactionRequestInfoContent.kt index d7c61d3af8..382da28e98 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/TransactionRequestInfoContent.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/TransactionRequestInfoContent.kt @@ -1,53 +1,94 @@ package com.tangem.features.walletconnect.transaction.ui.common +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.SecondaryButtonIconEnd +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet +import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.walletconnect.impl.R +import com.tangem.features.walletconnect.transaction.entity.* +import com.tangem.features.walletconnect.transaction.entity.WcNetworkInfoUM import com.tangem.features.walletconnect.transaction.entity.WcSignTransactionUM +import com.tangem.features.walletconnect.transaction.entity.WcTransactionActionsUM +import com.tangem.features.walletconnect.transaction.entity.WcTransactionRequestInfoUM +import com.tangem.features.walletconnect.transaction.entity.WcTransactionUM +import kotlinx.collections.immutable.persistentListOf + +private const val MIN_HEIGHT_SCREEN_PERCENT = 0.35f +private const val MAX_HEIGHT_SCREEN_PERCENT = 0.75f @Composable internal fun TransactionRequestInfoContent(state: WcSignTransactionUM) { + val screenHeight = LocalConfiguration.current.screenHeightDp + val minHeight = (screenHeight * MIN_HEIGHT_SCREEN_PERCENT).dp + val maxHeight = (screenHeight * MAX_HEIGHT_SCREEN_PERCENT).dp + Box( modifier = Modifier .fillMaxWidth() - .heightIn(min = 310.dp) + .heightIn(min = minHeight, max = maxHeight) .padding(horizontal = TangemTheme.dimens.spacing16), ) { - Column( + LazyColumn( modifier = Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(TangemTheme.dimens.radius16)) - .background(TangemTheme.colors.background.action) - .padding( - start = TangemTheme.dimens.spacing16, - end = TangemTheme.dimens.spacing16, - bottom = TangemTheme.dimens.spacing20, - ), + .fillMaxWidth(), + contentPadding = PaddingValues(top = TangemTheme.dimens.spacing8, bottom = TangemTheme.dimens.spacing70), ) { - state.transactionRequestInfo.info.forEach { item -> - Text( - text = item.title.resolveReference(), - modifier = Modifier.padding(top = TangemTheme.dimens.spacing20), - style = TangemTheme.typography.subtitle2, - color = TangemTheme.colors.text.tertiary, - ) - Text( - text = item.description, - modifier = Modifier.padding(top = TangemTheme.dimens.spacing4), - style = TangemTheme.typography.body2, - color = TangemTheme.colors.text.primary1, - ) + items(state.transactionRequestInfo.blocks) { block -> + Column( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(TangemTheme.dimens.radius16)) + .background(TangemTheme.colors.background.action) + .padding( + start = TangemTheme.dimens.spacing16, + end = TangemTheme.dimens.spacing16, + top = TangemTheme.dimens.spacing20, + ), + ) { + block.info.forEach { item -> + Text( + text = item.title.resolveReference(), + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.tertiary, + ) + if (item.description.isNotEmpty()) { + Text( + text = item.description, + modifier = Modifier.padding( + top = TangemTheme.dimens.spacing4, + bottom = TangemTheme.dimens.spacing20, + ), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.primary1, + ) + } else { + Spacer(modifier = Modifier.size(TangemTheme.dimens.size12)) + } + } + } + Spacer(modifier = Modifier.size(TangemTheme.dimens.size20)) } } @@ -61,4 +102,118 @@ internal fun TransactionRequestInfoContent(state: WcSignTransactionUM) { iconResId = R.drawable.ic_copy_24, ) } -} \ No newline at end of file +} + +@Composable +@Preview(showBackground = true, device = Devices.PIXEL_7_PRO) +@Preview(showBackground = true, device = Devices.PIXEL_7_PRO, uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun WcSignTransactionRequestInfoBottomSheetPreview( + @PreviewParameter(WcSignTransactionStateProvider::class) state: WcSignTransactionUM, +) { + TangemThemePreview { + TangemModalBottomSheet( + config = TangemBottomSheetConfig( + isShown = true, + onDismissRequest = {}, + content = state, + ), + containerColor = TangemTheme.colors.background.tertiary, + title = { + TangemModalBottomSheetTitle( + title = resourceReference(R.string.wc_transaction_request_title), + endIconRes = null, + onEndClick = {}, + startIconRes = R.drawable.ic_back_24, + onStartClick = {}, + ) + }, + content = { + TransactionRequestInfoContent(state = state) + }, + ) + } +} + +private class WcSignTransactionStateProvider : CollectionPreviewParameterProvider( + listOf( + WcSignTransactionUM( + transaction = WcTransactionUM( + appName = "React App", + appIcon = "", + isVerified = true, + appSubtitle = "react-app.walletconnect.com", + walletName = "Tangem 2.0", + networkInfo = WcNetworkInfoUM(name = "Ethereum", iconRes = R.drawable.img_eth_22), + ), + transactionRequestInfo = WcTransactionRequestInfoUM( + blocks = persistentListOf( + WcTransactionRequestBlockUM( + persistentListOf( + WcTransactionRequestInfoItemUM( + title = resourceReference(R.string.wc_signature_type), + description = "personal_sign", + ), + WcTransactionRequestInfoItemUM( + title = resourceReference(R.string.wc_contents), + description = "Hello! My name is John Dow. test@tange.com", + ), + ), + ), + ), + ), + actions = WcTransactionActionsUM( + onDismiss = {}, + onSign = {}, + onCopy = {}, + ), + ), + WcSignTransactionUM( + transaction = WcTransactionUM( + appName = "React App", + appIcon = "", + isVerified = true, + appSubtitle = "react-app.walletconnect.com", + walletName = "Tangem 2.0", + networkInfo = WcNetworkInfoUM(name = "Ethereum", iconRes = R.drawable.img_eth_22), + addressText = "0x345FF...34FA", + networkFee = "~ 0.22 $", + ), + transactionRequestInfo = WcTransactionRequestInfoUM( + blocks = persistentListOf( + WcTransactionRequestBlockUM( + persistentListOf( + WcTransactionRequestInfoItemUM( + title = resourceReference(R.string.wc_signature_type), + description = "personal_sign", + ), + WcTransactionRequestInfoItemUM( + title = resourceReference(R.string.wc_contents), + description = "Hello! My name is John Dow. test@tange.com", + ), + ), + ), + WcTransactionRequestBlockUM( + persistentListOf( + WcTransactionRequestInfoItemUM( + title = resourceReference(R.string.wc_transaction_info_to_title), + ), + WcTransactionRequestInfoItemUM( + title = resourceReference(R.string.settings_wallet_name_title), + description = "Bob", + ), + WcTransactionRequestInfoItemUM( + title = resourceReference(R.string.wc_common_wallet), + description = "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + ), + ), + ), + ), + ), + actions = WcTransactionActionsUM( + onDismiss = {}, + onSign = {}, + onCopy = {}, + ), + ), + ), +) \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcAddressItem.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcAddressItem.kt new file mode 100644 index 0000000000..8e9febc9b2 --- /dev/null +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcAddressItem.kt @@ -0,0 +1,44 @@ +package com.tangem.features.walletconnect.transaction.ui.common + +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign +import com.tangem.core.ui.extensions.stringResourceSafe +import com.tangem.core.ui.res.TangemTheme +import com.tangem.features.walletconnect.impl.R + +@Composable +internal fun WcAddressItem(addressText: String, modifier: Modifier = Modifier) { + Row(modifier = modifier, verticalAlignment = Alignment.CenterVertically) { + Icon( + modifier = Modifier.size(TangemTheme.dimens.size24), + painter = painterResource(R.drawable.ic_user_square_24), + contentDescription = null, + tint = TangemTheme.colors.icon.accent, + ) + Text( + modifier = Modifier + .padding(start = TangemTheme.dimens.spacing8) + .weight(1f), + text = stringResourceSafe(R.string.wc_common_address), + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.primary1, + ) + Text( + modifier = Modifier + .padding(start = TangemTheme.dimens.spacing16) + .weight(1f), + text = addressText, + textAlign = TextAlign.End, + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.tertiary, + ) + } +} \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcNetworkFeeItem.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcNetworkFeeItem.kt new file mode 100644 index 0000000000..bb4c1867c5 --- /dev/null +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcNetworkFeeItem.kt @@ -0,0 +1,67 @@ +package com.tangem.features.walletconnect.transaction.ui.common + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.painterResource +import com.tangem.core.ui.extensions.stringResourceSafe +import com.tangem.core.ui.res.TangemTheme +import com.tangem.features.walletconnect.impl.R + +@Composable +internal fun WcNetworkFeeItem(networkFeeText: String, modifier: Modifier = Modifier) { + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + modifier = Modifier.size(TangemTheme.dimens.size24), + painter = painterResource(R.drawable.ic_fee_new_24), + contentDescription = null, + tint = TangemTheme.colors.icon.accent, + ) + + Text( + modifier = Modifier.padding(start = TangemTheme.dimens.spacing8), + text = stringResourceSafe(R.string.common_network_fee_title), + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.primary1, + ) + + Icon( + modifier = Modifier + .padding(start = TangemTheme.dimens.spacing6) + .size(TangemTheme.dimens.size16), + painter = painterResource(id = R.drawable.ic_information_24), + tint = TangemTheme.colors.icon.informative, + contentDescription = null, + ) + + Spacer(modifier = Modifier.weight(1f)) + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.End, + ) { + Text( + text = networkFeeText, + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.tertiary, + ) + Spacer(modifier = Modifier.width(TangemTheme.dimens.spacing6)) + Icon( + modifier = Modifier + .clip(CircleShape) + .size(width = TangemTheme.dimens.size18, height = TangemTheme.dimens.size24), + painter = painterResource(id = R.drawable.ic_select_18_24), + tint = TangemTheme.colors.icon.informative, + contentDescription = null, + ) + } + } +} \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcNetworkItem.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcNetworkItem.kt index 1f337688aa..58c62d0366 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcNetworkItem.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcNetworkItem.kt @@ -30,7 +30,7 @@ internal fun WcNetworkItem(networkInfo: WcNetworkInfoUM, modifier: Modifier = Mo ) Text( modifier = Modifier - .padding(start = TangemTheme.dimens.spacing4) + .padding(start = TangemTheme.dimens.spacing8) .weight(1f), text = stringResourceSafe(R.string.wc_common_network), style = TangemTheme.typography.body1, diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcWalletItem.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcWalletItem.kt index 63633a22c1..18babacc67 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcWalletItem.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcWalletItem.kt @@ -25,7 +25,7 @@ internal fun WcWalletItem(walletName: String, modifier: Modifier = Modifier) { ) Text( modifier = Modifier - .padding(start = TangemTheme.dimens.spacing4) + .padding(start = TangemTheme.dimens.spacing8) .weight(1f), text = stringResourceSafe(R.string.manage_tokens_network_selector_wallet), style = TangemTheme.typography.body1, diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/sign/WcSignTransactionModalBottomSheet.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/sign/WcSignTransactionModalBottomSheet.kt deleted file mode 100644 index 665eb497b3..0000000000 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/sign/WcSignTransactionModalBottomSheet.kt +++ /dev/null @@ -1,154 +0,0 @@ -package com.tangem.features.walletconnect.transaction.ui.sign - -import android.content.res.Configuration -import androidx.compose.animation.animateContentSize -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Devices -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig -import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet -import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.features.walletconnect.impl.R -import com.tangem.features.walletconnect.transaction.entity.* -import com.tangem.features.walletconnect.transaction.ui.common.TransactionRequestInfoContent -import kotlinx.collections.immutable.persistentListOf - -@Composable -internal fun WcSignTransactionModalBottomSheet(config: TangemBottomSheetConfig) { - TangemModalBottomSheet( - config = config, - containerColor = TangemTheme.colors.background.tertiary, - title = { state -> - TangemModalBottomSheetTitle( - title = when (state.state) { - WcSignTransactionUM.State.TRANSACTION -> { - resourceReference(R.string.wallet_connect_title) - } - WcSignTransactionUM.State.TRANSACTION_REQUEST_INFO -> { - resourceReference(R.string.wc_transaction_request_title) - } - }, - endIconRes = state.endIconRes.takeIf { - state.state == WcSignTransactionUM.State.TRANSACTION - }, - onEndClick = state.actions.onDismiss, - startIconRes = state.startIconRes.takeIf { - state.state == WcSignTransactionUM.State.TRANSACTION_REQUEST_INFO - }, - onStartClick = state.actions.onBack, - ) - }, - content = { state -> - Box( - modifier = Modifier - .fillMaxWidth() - .animateContentSize(), - ) { - when (state.state) { - WcSignTransactionUM.State.TRANSACTION -> { - WcSignTransactionModalBottomSheetContent(state) - } - WcSignTransactionUM.State.TRANSACTION_REQUEST_INFO -> { - TransactionRequestInfoContent(state) - } - } - } - }, - ) -} - -@Composable -@Preview(showBackground = true, device = Devices.PIXEL_7_PRO) -@Preview(showBackground = true, device = Devices.PIXEL_7_PRO, uiMode = Configuration.UI_MODE_NIGHT_YES) -private fun WcSignTransactionBottomSheetPreview( - @PreviewParameter(WcSignTransactionStateProvider::class) state: WcSignTransactionUM, -) { - TangemThemePreview { - WcSignTransactionModalBottomSheet( - config = TangemBottomSheetConfig( - isShown = true, - onDismissRequest = {}, - content = state, - ), - ) - } -} - -private class WcSignTransactionStateProvider : CollectionPreviewParameterProvider( - listOf( - WcSignTransactionUM( - startIconRes = R.drawable.ic_back_24, - endIconRes = R.drawable.ic_close_24, - transactionIconRes = R.drawable.ic_doc_new_24, - state = WcSignTransactionUM.State.TRANSACTION, - transaction = WcTransactionUM( - appName = "React App", - appIcon = "", - isVerified = true, - appSubtitle = "react-app.walletconnect.com", - walletName = "Tangem 2.0", - networkInfo = WcNetworkInfoUM(name = "Ethereum", iconRes = R.drawable.img_eth_22), - ), - transactionRequestInfo = WcTransactionRequestInfoUM( - persistentListOf( - WcTransactionRequestInfoItemUM( - title = resourceReference(R.string.wc_signature_type), - description = "personal_sign", - ), - WcTransactionRequestInfoItemUM( - title = resourceReference(R.string.wc_contents), - description = "Hello! My name is John Dow. test@tange.com", - ), - ), - ), - actions = WcTransactionActionsUM( - onDismiss = {}, - onBack = {}, - onSign = {}, - onCopy = {}, - transactionRequestOnClick = {}, - ), - ), - WcSignTransactionUM( - startIconRes = R.drawable.ic_back_24, - endIconRes = R.drawable.ic_close_24, - transactionIconRes = R.drawable.ic_doc_new_24, - state = WcSignTransactionUM.State.TRANSACTION_REQUEST_INFO, - transaction = WcTransactionUM( - appName = "React App", - appIcon = "", - isVerified = true, - appSubtitle = "react-app.walletconnect.com", - walletName = "Tangem 2.0", - networkInfo = WcNetworkInfoUM(name = "Ethereum", iconRes = R.drawable.img_eth_22), - ), - transactionRequestInfo = WcTransactionRequestInfoUM( - persistentListOf( - WcTransactionRequestInfoItemUM( - title = resourceReference(R.string.wc_signature_type), - description = "personal_sign", - ), - WcTransactionRequestInfoItemUM( - title = resourceReference(R.string.wc_contents), - description = "Hello! My name is John Dow. test@tange.com", - ), - ), - ), - actions = WcTransactionActionsUM( - onDismiss = {}, - onBack = {}, - onSign = {}, - onCopy = {}, - transactionRequestOnClick = {}, - ), - ), - ), -) \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/sign/WcSignTransactionModalBottomSheetContent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/sign/WcSignTransactionModalBottomSheetContent.kt index 7548486b13..398d71924e 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/sign/WcSignTransactionModalBottomSheetContent.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/sign/WcSignTransactionModalBottomSheetContent.kt @@ -1,5 +1,6 @@ package com.tangem.features.walletconnect.transaction.ui.sign +import android.content.res.Configuration import androidx.compose.animation.animateContentSize import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -11,18 +12,33 @@ import androidx.compose.material3.HorizontalDivider import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet +import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle +import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.walletconnect.connections.ui.WcAppInfoItem -import com.tangem.features.walletconnect.transaction.entity.WcSignTransactionUM +import com.tangem.features.walletconnect.impl.R +import com.tangem.features.walletconnect.transaction.entity.* import com.tangem.features.walletconnect.transaction.ui.common.* -import com.tangem.features.walletconnect.transaction.ui.common.WcNetworkItem -import com.tangem.features.walletconnect.transaction.ui.common.WcTransactionRequestButtons -import com.tangem.features.walletconnect.transaction.ui.common.WcTransactionRequestItem -import com.tangem.features.walletconnect.transaction.ui.common.WcWalletItem +import kotlinx.collections.immutable.persistentListOf @Composable -internal fun WcSignTransactionModalBottomSheetContent(state: WcSignTransactionUM) { - Column(modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16)) { +internal fun WcSignTransactionModalBottomSheetContent( + state: WcSignTransactionUM, + onClickTransactionRequest: () -> Unit, +) { + Column( + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing16), + ) { Column( modifier = Modifier .clip(RoundedCornerShape(TangemTheme.dimens.radius14)) @@ -37,47 +53,17 @@ internal fun WcSignTransactionModalBottomSheetContent(state: WcSignTransactionUM subtitle = state.transaction.appSubtitle, isVerified = state.transaction.isVerified, ) - HorizontalDivider( - thickness = TangemTheme.dimens.size1, - color = TangemTheme.colors.stroke.primary, - ) + DividerWithPadding(start = 0.dp, end = 0.dp) WcTransactionRequestItem( - iconRes = state.transactionIconRes, + iconRes = R.drawable.ic_doc_new_24, modifier = Modifier .fillMaxWidth() - .clickable { state.actions.transactionRequestOnClick() } + .clickable { onClickTransactionRequest() } .padding(TangemTheme.dimens.spacing12), ) } Column(modifier = Modifier.padding(top = TangemTheme.dimens.spacing16)) { - Column( - modifier = Modifier - .clip(RoundedCornerShape(TangemTheme.dimens.radius14)) - .background(color = TangemTheme.colors.background.action) - .fillMaxWidth() - .animateContentSize(), - ) { - val itemsModifier = Modifier - .fillMaxWidth() - .padding(TangemTheme.dimens.spacing12) - - HorizontalDivider( - thickness = TangemTheme.dimens.size1, - color = TangemTheme.colors.stroke.primary, - ) - WcWalletItem( - modifier = itemsModifier, - walletName = state.transaction.walletName, - ) - HorizontalDivider( - thickness = TangemTheme.dimens.size1, - color = TangemTheme.colors.stroke.primary, - ) - WcNetworkItem( - modifier = itemsModifier, - networkInfo = state.transaction.networkInfo, - ) - } + WcSignTransactionItems(state) WcTransactionRequestButtons( modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing16), onDismiss = state.actions.onDismiss, @@ -86,4 +72,125 @@ internal fun WcSignTransactionModalBottomSheetContent(state: WcSignTransactionUM ) } } -} \ No newline at end of file +} + +@Composable +private fun WcSignTransactionItems(state: WcSignTransactionUM) { + Column( + modifier = Modifier + .clip(RoundedCornerShape(TangemTheme.dimens.radius14)) + .background(color = TangemTheme.colors.background.action) + .fillMaxWidth() + .animateContentSize(), + ) { + val itemsModifier = Modifier + .fillMaxWidth() + .padding(TangemTheme.dimens.spacing12) + + DividerWithPadding(start = 0.dp, end = 0.dp) + WcWalletItem( + modifier = itemsModifier, + walletName = state.transaction.walletName, + ) + DividerWithPadding(start = TangemTheme.dimens.spacing40, end = TangemTheme.dimens.spacing12) + WcNetworkItem( + modifier = itemsModifier, + networkInfo = state.transaction.networkInfo, + ) + if (!state.transaction.addressText.isNullOrEmpty()) { + DividerWithPadding(start = TangemTheme.dimens.spacing40, end = TangemTheme.dimens.spacing12) + WcAddressItem( + modifier = itemsModifier, + addressText = state.transaction.addressText, + ) + } + if (!state.transaction.networkFee.isNullOrEmpty()) { + DividerWithPadding(start = TangemTheme.dimens.spacing40, end = TangemTheme.dimens.spacing12) + WcNetworkFeeItem( + modifier = itemsModifier, + networkFeeText = state.transaction.networkFee, + ) + } + } +} + +@Composable +private fun DividerWithPadding(start: Dp, end: Dp) { + HorizontalDivider( + modifier = Modifier.padding( + start = start, + end = end, + ), + thickness = TangemTheme.dimens.size1, + color = TangemTheme.colors.stroke.primary, + ) +} + +@Composable +@Preview(showBackground = true, device = Devices.PIXEL_7_PRO) +@Preview(showBackground = true, device = Devices.PIXEL_7_PRO, uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun WcSignTransactionBottomSheetPreview( + @PreviewParameter(WcSignTransactionStateProvider::class) state: WcSignTransactionUM, +) { + TangemThemePreview { + TangemModalBottomSheet( + config = TangemBottomSheetConfig( + isShown = true, + onDismissRequest = {}, + content = state, + ), + containerColor = TangemTheme.colors.background.tertiary, + title = { + TangemModalBottomSheetTitle( + title = resourceReference(R.string.wallet_connect_title), + endIconRes = R.drawable.ic_close_24, + onEndClick = {}, + startIconRes = null, + onStartClick = {}, + ) + }, + content = { + WcSignTransactionModalBottomSheetContent(state = state, onClickTransactionRequest = {}) + }, + ) + } +} + +private class WcSignTransactionStateProvider : CollectionPreviewParameterProvider( + listOf( + WcSignTransactionUM( + transaction = WcTransactionUM( + appName = "React App", + appIcon = "", + isVerified = true, + appSubtitle = "react-app.walletconnect.com", + walletName = "Tangem 2.0", + networkInfo = WcNetworkInfoUM(name = "Ethereum", iconRes = R.drawable.img_eth_22), + ), + transactionRequestInfo = WcTransactionRequestInfoUM(persistentListOf()), + actions = WcTransactionActionsUM( + onDismiss = {}, + onSign = {}, + onCopy = {}, + ), + ), + WcSignTransactionUM( + transaction = WcTransactionUM( + appName = "React App", + appIcon = "", + isVerified = true, + appSubtitle = "react-app.walletconnect.com", + walletName = "Tangem 2.0", + networkInfo = WcNetworkInfoUM(name = "Ethereum", iconRes = R.drawable.img_eth_22), + addressText = "0x345FF...34FA", + networkFee = "~ 0.22 $", + ), + transactionRequestInfo = WcTransactionRequestInfoUM(persistentListOf()), + actions = WcTransactionActionsUM( + onDismiss = {}, + onSign = {}, + onCopy = {}, + ), + ), + ), +) \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/utils/WalletAddressUtils.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/utils/WalletAddressUtils.kt new file mode 100644 index 0000000000..a51a5d82f7 --- /dev/null +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/utils/WalletAddressUtils.kt @@ -0,0 +1,7 @@ +package com.tangem.features.walletconnect.transaction.utils + +const val ADDRESS_FIRST_PART_LENGTH = 7 +const val ADDRESS_SECOND_PART_LENGTH = 4 + +internal fun String.toShortAddressText() = + "${take(ADDRESS_FIRST_PART_LENGTH)}...${takeLast(ADDRESS_SECOND_PART_LENGTH)}" \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/utils/WcSignTransactionUtils.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/utils/WcSignTransactionUtils.kt index 5eba6dcc9f..0442435782 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/utils/WcSignTransactionUtils.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/utils/WcSignTransactionUtils.kt @@ -3,6 +3,9 @@ package com.tangem.features.walletconnect.transaction.utils import com.domain.blockaid.models.dapp.CheckDAppResult import com.tangem.core.ui.extensions.getActiveIconRes import com.tangem.core.ui.extensions.resourceReference +import com.tangem.domain.walletconnect.model.WcEthMethod +import com.tangem.domain.walletconnect.model.WcSolanaMethod +import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest import com.tangem.domain.walletconnect.usecase.method.WcMessageSignUseCase import com.tangem.domain.walletconnect.usecase.method.WcSignState import com.tangem.domain.walletconnect.usecase.method.WcSignStep @@ -11,33 +14,84 @@ import com.tangem.features.walletconnect.impl.R import com.tangem.features.walletconnect.transaction.entity.WcNetworkInfoUM import com.tangem.features.walletconnect.transaction.entity.WcSignTransactionUM import com.tangem.features.walletconnect.transaction.entity.WcTransactionActionsUM +import com.tangem.features.walletconnect.transaction.entity.WcTransactionRequestBlockUM import com.tangem.features.walletconnect.transaction.entity.WcTransactionRequestInfoItemUM import com.tangem.features.walletconnect.transaction.entity.WcTransactionRequestInfoUM import com.tangem.features.walletconnect.transaction.entity.WcTransactionUM import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList internal fun WcSignUseCase.toUM(signState: WcSignState<*>, actions: WcTransactionActionsUM): WcSignTransactionUM? { return when (this) { is WcMessageSignUseCase -> { - ethMessageSignToUM( - signState = signState, - signModel = signState.signModel as WcMessageSignUseCase.SignModel, - actions = actions, - ) + when (method) { + is WcEthMethod.SignTypedData -> signTypedDataToUM( + signState = signState, + signModel = signState.signModel as WcMessageSignUseCase.SignModel, + actions = actions, + ) + is WcEthMethod.MessageSign, is WcSolanaMethod.SignMessage -> messageSignToUM( + signState = signState, + signModel = signState.signModel as WcMessageSignUseCase.SignModel, + actions = actions, + ) + else -> null + } } else -> null } } -private fun WcMessageSignUseCase.ethMessageSignToUM( +private fun WcMessageSignUseCase.signTypedDataToUM( + signState: WcSignState<*>, + signModel: WcMessageSignUseCase.SignModel, + actions: WcTransactionActionsUM, +) = WcSignTransactionUM( + actions = actions, + transaction = WcTransactionUM( + appName = session.sdkModel.appMetaData.name, + appIcon = session.sdkModel.appMetaData.url, + isVerified = session.securityStatus == CheckDAppResult.SAFE, + appSubtitle = session.sdkModel.appMetaData.description, + walletName = session.wallet.name, + networkInfo = WcNetworkInfoUM( + name = network.name, + iconRes = getActiveIconRes(network.id.value), + ), + addressText = walletAddress.toShortAddressText(), + isLoading = signState.domainStep == WcSignStep.Signing, + ), + transactionRequestInfo = WcTransactionRequestInfoUM( + buildList { + add(createInfoBlockUM(rawSdkRequest, signModel)) + (method as? WcEthMethod.SignTypedData)?.params?.message?.to?.let { to -> + add( + WcTransactionRequestBlockUM( + persistentListOf( + WcTransactionRequestInfoItemUM( + title = resourceReference(R.string.wc_transaction_info_to_title), + ), + WcTransactionRequestInfoItemUM( + title = resourceReference(R.string.settings_wallet_name_title), + description = to.name, + ), + WcTransactionRequestInfoItemUM( + title = resourceReference(R.string.wc_common_wallet), + description = to.wallet, + ), + ), + ), + ) + } + }.toImmutableList(), + ), +) + +private fun WcMessageSignUseCase.messageSignToUM( signState: WcSignState<*>, signModel: WcMessageSignUseCase.SignModel, actions: WcTransactionActionsUM, ) = WcSignTransactionUM( - startIconRes = R.drawable.ic_back_24, - endIconRes = R.drawable.ic_close_24, - transactionIconRes = R.drawable.ic_doc_new_24, - state = WcSignTransactionUM.State.TRANSACTION, actions = actions, transaction = WcTransactionUM( appName = session.sdkModel.appMetaData.name, @@ -52,6 +106,15 @@ private fun WcMessageSignUseCase.ethMessageSignToUM( isLoading = signState.domainStep == WcSignStep.Signing, ), transactionRequestInfo = WcTransactionRequestInfoUM( + persistentListOf(createInfoBlockUM(rawSdkRequest, signModel)), + ), +) + +private fun createInfoBlockUM( + rawSdkRequest: WcSdkSessionRequest, + signModel: WcMessageSignUseCase.SignModel, +): WcTransactionRequestBlockUM { + return WcTransactionRequestBlockUM( persistentListOf( WcTransactionRequestInfoItemUM( title = resourceReference(R.string.wc_signature_type), @@ -62,5 +125,5 @@ private fun WcMessageSignUseCase.ethMessageSignToUM( description = signModel.humanMsg, ), ), - ), -) \ No newline at end of file + ) +} \ No newline at end of file diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index 9e58bcf67c..ccc406b5ff 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,7 +5,7 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "develop-1052" +tangemBlockchainSdk = "develop-1063" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-468" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^