diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml
index 258d009305..b9b098fef2 100644
--- a/core/res/src/main/res/values/strings.xml
+++ b/core/res/src/main/res/values/strings.xml
@@ -259,6 +259,7 @@
From
Synchronize addresses
Get started
+ Get token
Go to provider
Go to token
Got it
@@ -651,6 +652,7 @@
Trending
Staking is the easiest way to receive rewards on your crypto. %s
Earn up to %s APY
+ Token Added
About %s
- %d exchange
@@ -1821,8 +1823,8 @@
Aave is a decentralized protocol managing over $81.9 billion in total value.
Decentralized and self-custodial
By using service, you agree with provider\n%1$s and %2$s
- Earn %s%% yearly
- Aave • Variable Interest Rate
+ Connect Aave
+ Aave %1$s% • Variable Interest Rate
Aave
Avg %s
Last year returns
diff --git a/domain/account/status/src/main/java/com/tangem/domain/account/status/di/AccountStatusUseCaseModule.kt b/domain/account/status/src/main/java/com/tangem/domain/account/status/di/AccountStatusUseCaseModule.kt
index 79d373768b..be262a1106 100644
--- a/domain/account/status/src/main/java/com/tangem/domain/account/status/di/AccountStatusUseCaseModule.kt
+++ b/domain/account/status/src/main/java/com/tangem/domain/account/status/di/AccountStatusUseCaseModule.kt
@@ -4,6 +4,7 @@ import com.tangem.domain.account.repository.AccountsCRUDRepository
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
import com.tangem.domain.account.status.usecase.GetAccountCurrencyByAddressUseCase
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
+import com.tangem.domain.account.status.usecase.GetCryptoCurrencyActionsUseCaseV2
import com.tangem.domain.account.status.usecase.SaveCryptoCurrenciesUseCase
import com.tangem.domain.account.supplier.SingleAccountListSupplier
import com.tangem.domain.common.wallets.UserWalletsListRepository
@@ -14,6 +15,7 @@ import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
import com.tangem.domain.staking.StakingIdFactory
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
import com.tangem.domain.staking.utils.StakingCleaner
+import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.derivations.DerivationsRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@@ -41,6 +43,20 @@ internal object AccountStatusUseCaseModule {
)
}
+ @Provides
+ @Singleton
+ fun provideGetCryptoCurrencyActionsUseCaseV2(
+ accountsCRUDRepository: AccountsCRUDRepository,
+ singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
+ getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCase,
+ ): GetCryptoCurrencyActionsUseCaseV2 {
+ return GetCryptoCurrencyActionsUseCaseV2(
+ accountsCRUDRepository = accountsCRUDRepository,
+ singleAccountStatusListSupplier = singleAccountStatusListSupplier,
+ getCryptoCurrencyActionsUseCase = getCryptoCurrencyActionsUseCase,
+ )
+ }
+
@Provides
@Singleton
fun provideGetAccountCurrencyStatusUseCase(
diff --git a/features/account/impl/src/main/java/com/tangem/features/account/selector/di/PortfolioSelectorModule.kt b/features/account/impl/src/main/java/com/tangem/features/account/selector/di/PortfolioSelectorModule.kt
new file mode 100644
index 0000000000..d28a1c305e
--- /dev/null
+++ b/features/account/impl/src/main/java/com/tangem/features/account/selector/di/PortfolioSelectorModule.kt
@@ -0,0 +1,20 @@
+package com.tangem.features.account.selector.di
+
+import com.tangem.core.decompose.di.ModelComponent
+import com.tangem.core.decompose.model.Model
+import com.tangem.features.account.selector.PortfolioSelectorModel
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+@InstallIn(ModelComponent::class)
+internal interface PortfolioSelectorModule {
+
+ @Binds
+ @IntoMap
+ @ClassKey(PortfolioSelectorModel::class)
+ fun portfolioSelectorModel(model: PortfolioSelectorModel): Model
+}
\ No newline at end of file
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/api/AddToPortfolioComponent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/api/AddToPortfolioComponent.kt
new file mode 100644
index 0000000000..f35084b04a
--- /dev/null
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/api/AddToPortfolioComponent.kt
@@ -0,0 +1,18 @@
+package com.tangem.features.markets.portfolio.add.api
+
+import com.tangem.core.decompose.factory.ComponentFactory
+import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
+
+internal interface AddToPortfolioComponent : ComposableBottomSheetComponent {
+
+ data class Params(
+ val addToPortfolioManager: AddToPortfolioManager,
+ val callback: Callback,
+ )
+
+ interface Callback {
+ fun onDismiss()
+ }
+
+ interface Factory : ComponentFactory
+}
\ No newline at end of file
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/api/AddToPortfolioManager.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/api/AddToPortfolioManager.kt
new file mode 100644
index 0000000000..28daa1389c
--- /dev/null
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/api/AddToPortfolioManager.kt
@@ -0,0 +1,38 @@
+package com.tangem.features.markets.portfolio.add.api
+
+import com.tangem.domain.markets.TokenMarketInfo
+import com.tangem.domain.markets.TokenMarketParams
+import com.tangem.features.account.PortfolioFetcher
+import com.tangem.features.markets.portfolio.api.MarketsPortfolioComponent.AnalyticsParams
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+
+internal interface AddToPortfolioManager {
+
+ val token: TokenMarketParams
+ val analyticsParams: AnalyticsParams?
+ val portfolioFetcher: PortfolioFetcher
+
+ val state: StateFlow
+
+ val allAvailableNetworks: Flow>
+ fun setTokenNetworks(networks: List)
+
+ sealed interface State {
+ data object Init : State
+ data class AvailableToAdd(
+ val availableToAddData: AvailableToAddData,
+ ) : State
+
+ data object NothingToAdd : State
+ }
+
+ interface Factory {
+ fun create(
+ scope: CoroutineScope,
+ token: TokenMarketParams,
+ analyticsParams: AnalyticsParams?,
+ ): AddToPortfolioManager
+ }
+}
\ No newline at end of file
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/api/AvailableToAddData.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/api/AvailableToAddData.kt
index 400f8b25c2..9ca10389a9 100644
--- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/api/AvailableToAddData.kt
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/api/AvailableToAddData.kt
@@ -7,6 +7,7 @@ import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
+import kotlinx.serialization.Serializable
internal data class AvailableToAddData(
val availableToAddWallets: Map,
@@ -24,6 +25,7 @@ internal data class AvailableToAddWallet(
val availableToAddAccounts: Map,
)
+@Serializable
internal data class AvailableToAddAccount(
val account: AccountStatus,
val availableNetworks: Set,
@@ -41,6 +43,7 @@ internal data class AvailableToAddAccount(
.toSet()
}
+@Serializable
internal data class SelectedPortfolio(
val userWallet: UserWallet,
val account: AvailableToAddAccount,
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/AddTokenComponent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/AddTokenComponent.kt
index 0b1272cba8..3f6407aacc 100644
--- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/AddTokenComponent.kt
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/AddTokenComponent.kt
@@ -8,7 +8,6 @@ import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableContentComponent
-import com.tangem.domain.markets.TokenMarketParams
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.features.markets.portfolio.add.api.SelectedNetwork
import com.tangem.features.markets.portfolio.add.api.SelectedPortfolio
@@ -38,7 +37,6 @@ internal class AddTokenComponent @AssistedInject constructor(
}
data class Params(
- val marketParams: TokenMarketParams,
val eventBuilder: PortfolioAnalyticsEvent.EventBuilder,
val selectedPortfolio: Flow,
val selectedNetwork: Flow,
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/ChooseNetworkComponent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/ChooseNetworkComponent.kt
index 3ab91ac88c..e954b972bd 100644
--- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/ChooseNetworkComponent.kt
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/ChooseNetworkComponent.kt
@@ -1,48 +1,36 @@
package com.tangem.features.markets.portfolio.add.impl
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.factory.ComponentFactory
+import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.markets.TokenMarketInfo
+import com.tangem.features.markets.portfolio.add.api.SelectedPortfolio
+import com.tangem.features.markets.portfolio.add.impl.model.ChooseNetworkModel
import com.tangem.features.markets.portfolio.add.impl.ui.ChooseNetworkContent
-import com.tangem.features.markets.portfolio.add.impl.ui.state.ChooseNetworkUM
-import com.tangem.features.markets.portfolio.impl.model.BlockchainRowUMConverter
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
-import kotlinx.collections.immutable.toPersistentList
internal class ChooseNetworkComponent @AssistedInject constructor(
@Assisted context: AppComponentContext,
@Assisted private val params: Params,
) : AppComponentContext by context, ComposableContentComponent {
- private val state by lazy {
- val converter = BlockchainRowUMConverter(
- alreadyAddedNetworks = params.alreadyAdded.mapTo(mutableSetOf()) { it.networkId },
- )
- val allAvailableNetworks = params.allAvailable.map { it to true }
- ChooseNetworkUM(
- networks = converter.convertList(allAvailableNetworks).toPersistentList(),
- onNetworkClick = onNetworkClick@{ row ->
- val network = params.allAvailable
- .find { it.networkId == row.id }
- ?: return@onNetworkClick
- params.callbacks.onNetworkSelected(network)
- },
- )
- }
+ private val model: ChooseNetworkModel = getOrCreateModel(params)
@Composable
override fun Content(modifier: Modifier) {
+ val state by model.uiState.collectAsStateWithLifecycle()
ChooseNetworkContent(state)
}
data class Params(
- val alreadyAdded: Set,
- val allAvailable: List,
+ val selectedPortfolio: SelectedPortfolio,
val callbacks: Callbacks,
)
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/DefaultAddToPortfolioComponent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/DefaultAddToPortfolioComponent.kt
new file mode 100644
index 0000000000..69b9573e71
--- /dev/null
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/DefaultAddToPortfolioComponent.kt
@@ -0,0 +1,210 @@
+package com.tangem.features.markets.portfolio.add.impl
+
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.arkivanov.decompose.ComponentContext
+import com.arkivanov.decompose.extensions.compose.subscribeAsState
+import com.arkivanov.decompose.router.stack.ChildStack
+import com.arkivanov.decompose.router.stack.backStack
+import com.arkivanov.decompose.router.stack.childStack
+import com.arkivanov.decompose.router.stack.pop
+import com.tangem.core.decompose.context.AppComponentContext
+import com.tangem.core.decompose.context.child
+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.TangemBottomSheetConfigContent
+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.TextReference
+import com.tangem.core.ui.extensions.resourceReference
+import com.tangem.core.ui.res.TangemTheme
+import com.tangem.features.account.PortfolioSelectorComponent
+import com.tangem.features.markets.impl.R
+import com.tangem.features.markets.portfolio.add.api.AddToPortfolioComponent
+import com.tangem.features.markets.portfolio.add.api.AddToPortfolioComponent.Params
+import com.tangem.features.markets.portfolio.add.impl.model.AddToPortfolioModel
+import com.tangem.features.markets.portfolio.add.impl.model.AddToPortfolioRoutes
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+internal class DefaultAddToPortfolioComponent @AssistedInject constructor(
+ @Assisted context: AppComponentContext,
+ @Assisted private val params: Params,
+ portfolioSelectorComponentFactory: PortfolioSelectorComponent.Factory,
+ addTokenComponentFactory: AddTokenComponent.Factory,
+ tokenActionsComponentFactory: TokenActionsComponent.Factory,
+ private val chooseNetworkComponentFactory: ChooseNetworkComponent.Factory,
+) : AppComponentContext by context, AddToPortfolioComponent {
+
+ private val model: AddToPortfolioModel = getOrCreateModel(params)
+
+ private val portfolioSelectorComponent: PortfolioSelectorComponent = portfolioSelectorComponentFactory.create(
+ context = child("portfolioSelectorComponent"),
+ params = PortfolioSelectorComponent.Params(
+ portfolioFetcher = model.portfolioFetcher,
+ controller = model.portfolioSelectorController,
+ ),
+ )
+
+ private val addTokenComponent: AddTokenComponent = addTokenComponentFactory.create(
+ context = child("addTokenComponent"),
+ params = AddTokenComponent.Params(
+ eventBuilder = model.eventBuilder,
+ callbacks = model,
+ selectedPortfolio = model.selectedPortfolio,
+ selectedNetwork = model.selectedNetwork,
+ ),
+ )
+
+ private val tokenActionsComponent: TokenActionsComponent = tokenActionsComponentFactory.create(
+ context = child("tokenActionsComponent"),
+ params = TokenActionsComponent.Params(
+ eventBuilder = model.eventBuilder,
+ callbacks = model,
+ data = model.tokenActionsData,
+ ),
+ )
+
+ private val childStack = childStack(
+ key = "addToPortfolioStack",
+ handleBackButton = true,
+ source = model.navigation,
+ serializer = AddToPortfolioRoutes.serializer(),
+ initialStack = { model.currentStack },
+ childFactory = ::contentChild,
+ )
+
+ private fun onBack() {
+ if (childStack.backStack.isNotEmpty()) model.navigation.pop() else dismiss()
+ }
+
+ override fun dismiss() {
+ params.callback.onDismiss()
+ }
+
+ @Composable
+ override fun BottomSheet() {
+ val stack by childStack.subscribeAsState()
+ val contentStack = remember { mutableStateOf(stack) }
+ val currentRoute = stack.active.configuration
+ val isNotEmpty = currentRoute != AddToPortfolioRoutes.Empty
+ if (isNotEmpty) {
+ contentStack.value = stack
+ }
+
+ TangemModalBottomSheet(
+ scrollableContent = false,
+ onBack = ::onBack,
+ config = TangemBottomSheetConfig(
+ isShown = isNotEmpty,
+ onDismissRequest = ::dismiss,
+ content = TangemBottomSheetConfigContent.Empty,
+ ),
+ containerColor = TangemTheme.colors.background.tertiary,
+ title = { state ->
+ AnimatedContent(targetState = contentStack.value) { stack ->
+ BottomSheetTitle(
+ stack = stack,
+ onBackClick = ::onBack,
+ modifier = Modifier.fillMaxWidth(),
+ )
+ }
+ },
+ content = { state ->
+ AnimatedContent(targetState = contentStack.value) { stack ->
+ val paddingModifier = Modifier.padding(
+ start = 16.dp,
+ end = 16.dp,
+ bottom = 16.dp,
+ )
+ val scrollableContent = when (stack.active.configuration) {
+ AddToPortfolioRoutes.PortfolioSelector -> false
+ AddToPortfolioRoutes.AddToken,
+ AddToPortfolioRoutes.Empty,
+ is AddToPortfolioRoutes.NetworkSelector,
+ AddToPortfolioRoutes.TokenActions,
+ -> true
+ }
+ if (scrollableContent) {
+ Column(
+ modifier = paddingModifier.verticalScroll(rememberScrollState()),
+ ) {
+ stack.active.instance.Content(modifier = Modifier)
+ }
+ } else {
+ stack.active.instance.Content(modifier = paddingModifier)
+ }
+ }
+ },
+ )
+ }
+
+ @Composable
+ private fun BottomSheetTitle(
+ stack: ChildStack,
+ onBackClick: (() -> Unit),
+ modifier: Modifier = Modifier,
+ ) {
+ val title: TextReference = when (stack.active.configuration) {
+ AddToPortfolioRoutes.AddToken -> resourceReference(R.string.common_add_token)
+ AddToPortfolioRoutes.Empty -> TextReference.EMPTY
+ is AddToPortfolioRoutes.NetworkSelector -> resourceReference(R.string.common_choose_network)
+ AddToPortfolioRoutes.TokenActions -> resourceReference(R.string.common_get_token)
+ AddToPortfolioRoutes.PortfolioSelector -> (stack.active.instance as PortfolioSelectorComponent)
+ .title.collectAsStateWithLifecycle().value
+ }
+ val startIconRes: Int?
+ val endIconRes: Int?
+ if (stack.backStack.isNotEmpty()) {
+ startIconRes = R.drawable.ic_back_24
+ endIconRes = null
+ } else {
+ startIconRes = null
+ endIconRes = R.drawable.ic_close_24
+ }
+ TangemModalBottomSheetTitle(
+ modifier = modifier,
+ title = title,
+ startIconRes = startIconRes,
+ endIconRes = endIconRes,
+ onStartClick = onBackClick,
+ onEndClick = onBackClick,
+ )
+ }
+
+ private fun contentChild(
+ config: AddToPortfolioRoutes,
+ componentContext: ComponentContext,
+ ): ComposableContentComponent = when (config) {
+ AddToPortfolioRoutes.AddToken -> addTokenComponent
+ AddToPortfolioRoutes.PortfolioSelector -> portfolioSelectorComponent
+ AddToPortfolioRoutes.TokenActions -> tokenActionsComponent
+ AddToPortfolioRoutes.Empty -> ComposableContentComponent.EMPTY
+ is AddToPortfolioRoutes.NetworkSelector -> chooseNetworkComponentFactory.create(
+ context = childByContext(componentContext),
+ params = ChooseNetworkComponent.Params(
+ selectedPortfolio = config.selectedPortfolio,
+ callbacks = model,
+ ),
+ )
+ }
+
+ @AssistedFactory
+ interface Factory : AddToPortfolioComponent.Factory {
+ override fun create(context: AppComponentContext, params: Params): DefaultAddToPortfolioComponent
+ }
+}
\ No newline at end of file
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/di/AddToPortfolioComponentModule.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/di/AddToPortfolioComponentModule.kt
new file mode 100644
index 0000000000..39e8de3e0b
--- /dev/null
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/di/AddToPortfolioComponentModule.kt
@@ -0,0 +1,21 @@
+package com.tangem.features.markets.portfolio.add.impl.di
+
+import com.tangem.features.markets.portfolio.add.api.AddToPortfolioComponent
+import com.tangem.features.markets.portfolio.add.api.AddToPortfolioManager
+import com.tangem.features.markets.portfolio.add.impl.DefaultAddToPortfolioComponent
+import com.tangem.features.markets.portfolio.add.impl.ui.DefaultAddToPortfolioManager
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+
+@Module
+@InstallIn(SingletonComponent::class)
+internal interface AddToPortfolioComponentModule {
+
+ @Binds
+ fun bindAddToPortfolioComponent(factory: DefaultAddToPortfolioComponent.Factory): AddToPortfolioComponent.Factory
+
+ @Binds
+ fun bindAddToPortfolioManagerFactory(factory: DefaultAddToPortfolioManager.Factory): AddToPortfolioManager.Factory
+}
\ No newline at end of file
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/di/AddToPortfolioModelModule.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/di/AddToPortfolioModelModule.kt
new file mode 100644
index 0000000000..b093d471f6
--- /dev/null
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/di/AddToPortfolioModelModule.kt
@@ -0,0 +1,38 @@
+package com.tangem.features.markets.portfolio.add.impl.di
+
+import com.tangem.core.decompose.di.ModelComponent
+import com.tangem.core.decompose.model.Model
+import com.tangem.features.markets.portfolio.add.impl.model.AddToPortfolioModel
+import com.tangem.features.markets.portfolio.add.impl.model.AddTokenModel
+import com.tangem.features.markets.portfolio.add.impl.model.ChooseNetworkModel
+import com.tangem.features.markets.portfolio.add.impl.model.TokenActionsModel
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+@InstallIn(ModelComponent::class)
+internal interface AddToPortfolioModelModule {
+
+ @Binds
+ @IntoMap
+ @ClassKey(AddTokenModel::class)
+ fun addTokenModel(model: AddTokenModel): Model
+
+ @Binds
+ @IntoMap
+ @ClassKey(AddToPortfolioModel::class)
+ fun addToPortfolioModel(model: AddToPortfolioModel): Model
+
+ @Binds
+ @IntoMap
+ @ClassKey(TokenActionsModel::class)
+ fun tokenActionsModel(model: TokenActionsModel): Model
+
+ @Binds
+ @IntoMap
+ @ClassKey(ChooseNetworkModel::class)
+ fun chooseNetworkModel(model: ChooseNetworkModel): Model
+}
\ No newline at end of file
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/AddToPortfolioModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/AddToPortfolioModel.kt
new file mode 100644
index 0000000000..4cfe815d2c
--- /dev/null
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/AddToPortfolioModel.kt
@@ -0,0 +1,315 @@
+package com.tangem.features.markets.portfolio.add.impl.model
+
+import com.arkivanov.decompose.router.stack.StackNavigation
+import com.arkivanov.decompose.router.stack.popToFirst
+import com.arkivanov.decompose.router.stack.pushNew
+import com.arkivanov.decompose.router.stack.replaceAll
+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.resourceReference
+import com.tangem.core.ui.message.ToastMessage
+import com.tangem.domain.account.status.usecase.GetCryptoCurrencyActionsUseCaseV2
+import com.tangem.domain.markets.GetTokenMarketCryptoCurrency
+import com.tangem.domain.markets.TokenMarketInfo
+import com.tangem.domain.models.currency.CryptoCurrency
+import com.tangem.domain.models.currency.CryptoCurrencyStatus
+import com.tangem.domain.models.wallet.UserWallet
+import com.tangem.features.account.PortfolioSelectorController
+import com.tangem.features.markets.impl.R
+import com.tangem.features.markets.portfolio.add.api.*
+import com.tangem.features.markets.portfolio.add.impl.AddTokenComponent
+import com.tangem.features.markets.portfolio.add.impl.ChooseNetworkComponent
+import com.tangem.features.markets.portfolio.add.impl.TokenActionsComponent
+import com.tangem.features.markets.portfolio.impl.analytics.PortfolioAnalyticsEvent
+import com.tangem.features.markets.portfolio.impl.loader.PortfolioData
+import com.tangem.utils.coroutines.CoroutineDispatcherProvider
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.flow.*
+import timber.log.Timber
+import javax.inject.Inject
+
+@ModelScoped
+@Suppress("LongParameterList")
+internal class AddToPortfolioModel @Inject constructor(
+ paramsContainer: ParamsContainer,
+ override val dispatchers: CoroutineDispatcherProvider,
+ private val callbackDelegate: AddToPortfolioCallbackDelegate,
+ private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCaseV2,
+ private val getTokenMarketCryptoCurrency: GetTokenMarketCryptoCurrency,
+ private val messageSender: UiMessageSender,
+ val portfolioSelectorController: PortfolioSelectorController,
+) : Model(),
+ ChooseNetworkComponent.Callbacks by callbackDelegate,
+ TokenActionsComponent.Callbacks by callbackDelegate,
+ AddTokenComponent.Callbacks by callbackDelegate {
+
+ private val params = paramsContainer.require()
+ val navigation = StackNavigation()
+ var currentStack = listOf(AddToPortfolioRoutes.Empty)
+
+ /* Flows that hold state and provide it to child models */
+ val selectedNetwork: MutableSharedFlow = replayMutableSharedFlow()
+ val selectedPortfolio: MutableSharedFlow = replayMutableSharedFlow()
+ val tokenActionsData: MutableSharedFlow = replayMutableSharedFlow()
+
+ private val addToPortfolioManager = params.addToPortfolioManager
+ val portfolioFetcher = addToPortfolioManager.portfolioFetcher
+ val eventBuilder = PortfolioAnalyticsEvent.EventBuilder(
+ token = addToPortfolioManager.token,
+ source = addToPortfolioManager.analyticsParams?.source,
+ )
+
+ val featureData: Flow = combineFeatureData()
+
+ init {
+ navigation.subscribe { currentStack = it.transformer.invoke(currentStack) }
+ startAddToPortfolioFlow()
+ }
+
+ private fun replayMutableSharedFlow() = MutableSharedFlow(
+ replay = 1,
+ onBufferOverflow = BufferOverflow.DROP_OLDEST,
+ )
+
+ @Suppress("LongMethod")
+ private fun startAddToPortfolioFlow() {
+ channelFlow {
+ fun finishFlow() {
+ params.callback.onDismiss()
+ channel.close()
+ }
+ val featureDataFlow: StateFlow = featureData
+ .filterIsInstance()
+ .map { it.availableToAddData }
+ .distinctUntilChanged()
+ .stateIn(this)
+
+ // use snapshot data, looks like we don’t need to remap at runtime
+ val data = featureDataFlow.value
+
+ // you must control it via [AddToPortfolioComponent.state]
+ if (!data.availableToAdd) {
+ finishFlow()
+ return@channelFlow
+ }
+
+ // launch data flows, emits on user/code selection, updates state holder
+ setupPortfolioFlow(data)
+ .onEach { selectedPortfolio.emit(it) }
+ .launchIn(this)
+ setupNetworkFlow(selectedPortfolio)
+ .onEach { selectedNetwork.emit(it) }
+ .launchIn(this)
+
+ val isSinglePortfolio = data.isSinglePortfolio
+ if (isSinglePortfolio) {
+ val accountId = data.availableToAddWallets.values.first()
+ .availableToAddAccounts.values.first()
+ .account.account.accountId
+ // force select a portfolio, triggers [selectedPortfolio]
+ portfolioSelectorController.selectAccount(accountId)
+ } else {
+ navigation.replaceAll(AddToPortfolioRoutes.PortfolioSelector)
+ }
+
+ val firstPartOfNavigation = selectedPortfolio
+ .onEach { portfolio ->
+ val isSingleAvailableNetwork = portfolio.account.isSingleNetwork
+ when {
+ // force select a network, triggers [selectedNetwork]
+ isSingleAvailableNetwork -> {
+ val singleNetwork = portfolio.account.availableToAddNetworks.first()
+ callbackDelegate.onNetworkSelected(singleNetwork)
+ }
+ // it's important to control root screen, UI depends on it(close/arrow icon)
+ isSinglePortfolio -> navigation.replaceAll(routeToNetworkSelector(portfolio))
+ else -> navigation.pushNew(routeToNetworkSelector(portfolio))
+ }
+ }
+ .launchIn(this)
+
+ // main flow that combine all require data
+ val allRequireForAdd = combine(
+ flow = selectedNetwork,
+ flow2 = selectedPortfolio,
+ transform = { a, b -> a to b },
+ ).shareIn(
+ scope = this,
+ started = SharingStarted.Eagerly,
+ replay = 1,
+ )
+
+ // suspend until all required data is selected
+ allRequireForAdd.first()
+ // line of navigation to AddToken screen is finished; cancel the job, select a new root screen
+ firstPartOfNavigation.cancel()
+ navigation.replaceAll(AddToPortfolioRoutes.AddToken)
+
+ // handle actions from AddToken screen
+ callbackDelegate.onChangeNetworkClick.receiveAsFlow()
+ .map { routeToNetworkSelector(selectedPortfolio.first()) }
+ .onEach { route -> navigation.pushNew(route) }
+ .launchIn(this)
+ // handle actions from AddToken screen
+ callbackDelegate.onChangePortfolioClick.receiveAsFlow()
+ .onEach { navigation.pushNew(AddToPortfolioRoutes.PortfolioSelector) }
+ .launchIn(this)
+
+ allRequireForAdd
+ .onEach { (network, portfolio) ->
+ // after selecting a new Portfolio, must verify previous selected Network
+ // if it’s unavailable, navigate to NetworkSelector
+ val isAvailableSelectedNetwork = portfolio.account.availableToAddNetworks
+ .any { it.networkId == network.selectedNetwork.networkId }
+ if (isAvailableSelectedNetwork) {
+ navigation.popToFirst()
+ } else {
+ navigation.pushNew(routeToNetworkSelector(portfolio))
+ }
+ }
+ .launchIn(this)
+
+ // suspend until token is added
+ val addedToken = callbackDelegate.onTokenAdded.receiveAsFlow().first()
+ val selectedPortfolio = selectedPortfolio.first()
+
+ messageSender.send(ToastMessage(message = resourceReference(R.string.markets_token_added)))
+
+ setupTokenActionsFlow(selectedPortfolio, addedToken)
+ .onEach { tokenActionsData.emit(it) }
+ .onEach {
+ if (it.actions.isNotEmpty()) {
+ navigation.replaceAll(AddToPortfolioRoutes.TokenActions)
+ } else {
+ finishFlow()
+ }
+ }
+ .onEmpty { finishFlow() }
+ .launchIn(this)
+
+ callbackDelegate.onLaterClick.receiveAsFlow().first()
+ finishFlow()
+ }
+ .catch {
+ Timber.e(it)
+ params.callback.onDismiss()
+ }
+ .launchIn(modelScope)
+ }
+
+ private fun setupTokenActionsFlow(
+ selectedPortfolio: SelectedPortfolio,
+ addedToken: CryptoCurrencyStatus,
+ ): Flow = getCryptoCurrencyActionsUseCase(
+ currency = addedToken.currency,
+ accountId = selectedPortfolio.account.account.account.accountId,
+ )
+ .map {
+ PortfolioData.CryptoCurrencyData(
+ userWallet = selectedPortfolio.userWallet,
+ status = addedToken,
+ actions = it.states,
+ )
+ }
+
+ private fun setupPortfolioFlow(data: AvailableToAddData): Flow = combine(
+ flow = portfolioSelectorController.isAccountMode,
+ flow2 = portfolioSelectorController.selectedAccount,
+ transform = { isAccountMode, selectedAccountId ->
+ selectedAccountId ?: return@combine null
+ val availableToAddWallets =
+ data.availableToAddWallets[selectedAccountId.userWalletId] ?: return@combine null
+ val availableToAddAccount =
+ availableToAddWallets.availableToAddAccounts[selectedAccountId] ?: return@combine null
+ SelectedPortfolio(
+ isAccountMode = isAccountMode,
+ userWallet = availableToAddWallets.userWallet,
+ account = availableToAddAccount,
+ availableMorePortfolio = !data.isSinglePortfolio,
+ )
+ },
+ )
+ .filterNotNull()
+ .onEach { selectedPortfolio.emit(it) }
+
+ private fun setupNetworkFlow(selectedPortfolioFlow: SharedFlow): Flow = combine(
+ flow = selectedPortfolioFlow,
+ flow2 = callbackDelegate.onNetworkSelected.receiveAsFlow(),
+ transform = transform@{ selectedPortfolio, selectedNetwork ->
+ SelectedNetwork(
+ cryptoCurrency = createCryptoCurrency(
+ userWallet = selectedPortfolio.userWallet,
+ network = selectedNetwork,
+ ) ?: return@transform null,
+ selectedNetwork = selectedNetwork,
+ availableMoreNetwork = !selectedPortfolio.account.isSingleNetwork,
+ )
+ },
+ )
+ .filterNotNull()
+
+ private suspend fun createCryptoCurrency(
+ userWallet: UserWallet,
+ network: TokenMarketInfo.Network,
+ ): CryptoCurrency? = getTokenMarketCryptoCurrency(
+ userWalletId = userWallet.walletId,
+ tokenMarketParams = addToPortfolioManager.token,
+ network = network,
+ )
+
+ private fun routeToNetworkSelector(portfolio: SelectedPortfolio): AddToPortfolioRoutes.NetworkSelector {
+ return AddToPortfolioRoutes.NetworkSelector(selectedPortfolio = portfolio)
+ }
+
+ private fun combineFeatureData() = addToPortfolioManager.state.onEach { state ->
+ when (state) {
+ is AddToPortfolioManager.State.AvailableToAdd ->
+ portfolioSelectorController.isEnabled.value = isEnabled@{ userWallet, accountStatus ->
+ val availableWallet = state.availableToAddData.availableToAddWallets[userWallet.walletId]
+ ?: return@isEnabled false
+ val availableAccount =
+ availableWallet.availableToAddAccounts[accountStatus.account.accountId]
+ return@isEnabled availableAccount != null
+ }
+ AddToPortfolioManager.State.Init,
+ AddToPortfolioManager.State.NothingToAdd,
+ -> Unit
+ }
+ }
+}
+
+@ModelScoped
+internal class AddToPortfolioCallbackDelegate @Inject constructor() :
+ ChooseNetworkComponent.Callbacks,
+ TokenActionsComponent.Callbacks,
+ AddTokenComponent.Callbacks {
+
+ val onNetworkSelected = Channel()
+ val onLaterClick = Channel()
+ val onChangeNetworkClick = Channel()
+ val onChangePortfolioClick = Channel()
+ val onTokenAdded = Channel()
+
+ override fun onNetworkSelected(network: TokenMarketInfo.Network) {
+ onNetworkSelected.trySend(network)
+ }
+
+ override fun onLaterClick() {
+ onLaterClick.trySend(Unit)
+ }
+
+ override fun onChangeNetworkClick() {
+ onChangeNetworkClick.trySend(Unit)
+ }
+
+ override fun onChangePortfolioClick() {
+ onChangePortfolioClick.trySend(Unit)
+ }
+
+ override fun onTokenAdded(status: CryptoCurrencyStatus) {
+ onTokenAdded.trySend(status)
+ }
+}
\ No newline at end of file
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/AddToPortfolioRoutes.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/AddToPortfolioRoutes.kt
new file mode 100644
index 0000000000..4f46fac1d3
--- /dev/null
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/AddToPortfolioRoutes.kt
@@ -0,0 +1,28 @@
+package com.tangem.features.markets.portfolio.add.impl.model
+
+import androidx.compose.runtime.Immutable
+import com.tangem.core.decompose.navigation.Route
+import com.tangem.features.markets.portfolio.add.api.SelectedPortfolio
+import kotlinx.serialization.Serializable
+
+@Serializable
+@Immutable
+internal sealed interface AddToPortfolioRoutes : Route {
+
+ @Serializable
+ data object Empty : AddToPortfolioRoutes
+
+ @Serializable
+ data object PortfolioSelector : AddToPortfolioRoutes
+
+ @Serializable
+ data class NetworkSelector(
+ val selectedPortfolio: SelectedPortfolio,
+ ) : AddToPortfolioRoutes
+
+ @Serializable
+ data object AddToken : AddToPortfolioRoutes
+
+ @Serializable
+ data object TokenActions : AddToPortfolioRoutes
+}
\ No newline at end of file
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/AddTokenModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/AddTokenModel.kt
index 8e227d1ec8..d6ceeb31fb 100644
--- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/AddTokenModel.kt
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/AddTokenModel.kt
@@ -70,8 +70,7 @@ internal class AddTokenModel @Inject constructor(
val accountId = selectedPortfolio.account.account.account.accountId
saveCryptoCurrenciesUseCase(
accountId = accountId,
- add = listOf(cryptoCurrency),
- remove = listOf(),
+ add = cryptoCurrency,
)
val status = getAccountCurrencyStatusUseCase.invokeSync(
userWalletId = accountId.userWalletId,
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/ChooseNetworkModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/ChooseNetworkModel.kt
new file mode 100644
index 0000000000..fa3e0c3ab8
--- /dev/null
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/ChooseNetworkModel.kt
@@ -0,0 +1,125 @@
+package com.tangem.features.markets.portfolio.add.impl.model
+
+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.core.decompose.ui.UiMessageSender
+import com.tangem.core.ui.components.rows.model.BlockchainRowUM
+import com.tangem.core.ui.extensions.resourceReference
+import com.tangem.core.ui.extensions.stringReference
+import com.tangem.core.ui.extensions.wrappedList
+import com.tangem.core.ui.message.DialogMessage
+import com.tangem.core.ui.message.SnackbarMessage
+import com.tangem.domain.managetokens.CheckCurrencyUnsupportedUseCase
+import com.tangem.domain.managetokens.model.CurrencyUnsupportedState
+import com.tangem.domain.markets.TokenMarketInfo
+import com.tangem.domain.models.wallet.UserWalletId
+import com.tangem.features.markets.impl.R
+import com.tangem.features.markets.portfolio.add.impl.ChooseNetworkComponent
+import com.tangem.features.markets.portfolio.add.impl.ui.state.ChooseNetworkUM
+import com.tangem.features.markets.portfolio.impl.model.BlockchainRowUMConverter
+import com.tangem.utils.coroutines.CoroutineDispatcherProvider
+import kotlinx.collections.immutable.toPersistentList
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+import timber.log.Timber
+import javax.inject.Inject
+
+@ModelScoped
+@Suppress("LongParameterList")
+internal class ChooseNetworkModel @Inject constructor(
+ paramsContainer: ParamsContainer,
+ private val messageSender: UiMessageSender,
+ private val checkCurrencyUnsupportedUseCase: CheckCurrencyUnsupportedUseCase,
+ override val dispatchers: CoroutineDispatcherProvider,
+) : Model() {
+
+ private val params = paramsContainer.require()
+
+ val uiState: StateFlow = MutableStateFlow(buildUI())
+
+ private fun buildUI(): ChooseNetworkUM {
+ val allAvailable = params.selectedPortfolio.account.availableNetworks
+ val alreadyAdded = allAvailable
+ .subtract(params.selectedPortfolio.account.availableToAddNetworks)
+ val converter = BlockchainRowUMConverter(
+ alreadyAddedNetworks = alreadyAdded.mapTo(mutableSetOf()) { it.networkId },
+ )
+ val allAvailableNetworks = allAvailable.map { it to true }
+ return ChooseNetworkUM(
+ networks = converter.convertList(allAvailableNetworks).toPersistentList(),
+ onNetworkClick = onNetworkClick@{ row ->
+ val network = allAvailable
+ .find { it.networkId == row.id }
+ ?: return@onNetworkClick
+ checkNetwork(row, network)
+ },
+ )
+ }
+
+ private fun checkNetwork(row: BlockchainRowUM, network: TokenMarketInfo.Network) = modelScope.launch {
+ val selectedWalletId = params.selectedPortfolio.userWallet.walletId
+ val unsupportedState = checkCurrencyUnsupportedState(
+ userWalletId = selectedWalletId,
+ rawNetworkId = row.id,
+ isMainNetwork = row.isMainNetwork,
+ )
+ if (unsupportedState != null) {
+ showUnsupportedWarning(unsupportedState)
+ } else {
+ params.callbacks.onNetworkSelected(network)
+ }
+ }
+
+ private suspend fun checkCurrencyUnsupportedState(
+ userWalletId: UserWalletId,
+ rawNetworkId: String,
+ isMainNetwork: Boolean,
+ ): CurrencyUnsupportedState? {
+ return checkCurrencyUnsupportedUseCase(
+ userWalletId = userWalletId,
+ networkId = rawNetworkId,
+ isMainNetwork = isMainNetwork,
+ ).getOrElse {
+ Timber.e(
+ it,
+ """
+ Failed to check currency unsupported state
+ |- User wallet ID: $userWalletId
+ |- Network ID: $rawNetworkId
+ |- Is main network: $isMainNetwork
+ """.trimIndent(),
+ )
+
+ val message = SnackbarMessage(
+ message = it.localizedMessage?.let(::stringReference) ?: resourceReference(R.string.common_error),
+ )
+ messageSender.send(message)
+
+ null
+ }
+ }
+
+ private fun showUnsupportedWarning(unsupportedState: CurrencyUnsupportedState) {
+ val message = DialogMessage(
+ message = when (unsupportedState) {
+ is CurrencyUnsupportedState.Token.NetworkTokensUnsupported -> resourceReference(
+ id = R.string.alert_manage_tokens_unsupported_message,
+ formatArgs = wrappedList(unsupportedState.networkName),
+ )
+ is CurrencyUnsupportedState.Token.UnsupportedCurve -> resourceReference(
+ id = R.string.alert_manage_tokens_unsupported_curve_message,
+ formatArgs = wrappedList(unsupportedState.networkName),
+ )
+ is CurrencyUnsupportedState.UnsupportedNetwork -> resourceReference(
+ id = R.string.alert_manage_tokens_unsupported_curve_message,
+ formatArgs = wrappedList(unsupportedState.networkName),
+ )
+ },
+ )
+
+ messageSender.send(message)
+ }
+}
\ No newline at end of file
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/ui/DefaultAddToPortfolioManager.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/ui/DefaultAddToPortfolioManager.kt
new file mode 100644
index 0000000000..c68c7b7ad5
--- /dev/null
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/ui/DefaultAddToPortfolioManager.kt
@@ -0,0 +1,73 @@
+package com.tangem.features.markets.portfolio.add.impl.ui
+
+import com.tangem.domain.markets.TokenMarketInfo
+import com.tangem.domain.markets.TokenMarketParams
+import com.tangem.features.account.PortfolioFetcher
+import com.tangem.features.markets.portfolio.add.api.AddToPortfolioManager
+import com.tangem.features.markets.portfolio.add.api.AddToPortfolioManager.State
+import com.tangem.features.markets.portfolio.add.impl.converter.AvailableToAddDataConverter
+import com.tangem.features.markets.portfolio.api.MarketsPortfolioComponent
+import com.tangem.utils.coroutines.CoroutineDispatcherProvider
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.flow.*
+
+internal class DefaultAddToPortfolioManager @AssistedInject constructor(
+ private val availableToAddDataConverter: AvailableToAddDataConverter,
+ @Assisted override val token: TokenMarketParams,
+ @Assisted override val analyticsParams: MarketsPortfolioComponent.AnalyticsParams?,
+ @Assisted val scope: CoroutineScope,
+ dispatchers: CoroutineDispatcherProvider,
+ portfolioFetcherFactory: PortfolioFetcher.Factory,
+) : AddToPortfolioManager {
+
+ private val _allAvailableNetworks = MutableSharedFlow>(
+ replay = 1,
+ onBufferOverflow = BufferOverflow.DROP_OLDEST,
+ )
+
+ override val allAvailableNetworks: Flow> = _allAvailableNetworks.asSharedFlow()
+ override val portfolioFetcher: PortfolioFetcher = portfolioFetcherFactory.create(
+ mode = PortfolioFetcher.Mode.All(onlyMultiCurrency = true),
+ scope = scope,
+ )
+
+ override val state: StateFlow =
+ combine(
+ flow = portfolioFetcher.data.map { it.balances }.distinctUntilChanged(),
+ flow2 = allAvailableNetworks.map { it.toSet() }.distinctUntilChanged(),
+ ) { balances, availableNetworks ->
+ val data = availableToAddDataConverter.convert(
+ balances = balances,
+ availableNetworks = availableNetworks,
+ marketParams = token,
+ )
+ if (data.availableToAdd) {
+ State.AvailableToAdd(data)
+ } else {
+ State.NothingToAdd
+ }
+ }
+ .flowOn(dispatchers.default)
+ .stateIn(
+ scope = scope,
+ started = SharingStarted.Eagerly,
+ initialValue = State.Init,
+ )
+
+ override fun setTokenNetworks(networks: List) {
+ _allAvailableNetworks.tryEmit(networks)
+ }
+
+ @AssistedFactory
+ interface Factory : AddToPortfolioManager.Factory {
+ override fun create(
+ scope: CoroutineScope,
+ token: TokenMarketParams,
+ analyticsParams: MarketsPortfolioComponent.AnalyticsParams?,
+ ): DefaultAddToPortfolioManager
+ }
+}
\ No newline at end of file