diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/model/AddToPortfolioModel.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/model/AddToPortfolioModel.kt index 0aaee52e02..d6fc2e1ff3 100644 --- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/model/AddToPortfolioModel.kt +++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/model/AddToPortfolioModel.kt @@ -136,12 +136,12 @@ internal class AddToPortfolioModel @Inject constructor( // but its call AddToPortfolioManager.onAddedTokenClick callback !isAvailableToAdd -> { val singleNetwork = portfolio.account.addedMarketNetworks.first() - callbackDelegate.onNetworkSelected(singleNetwork) + callbackDelegate.onNetworkSelected.send(singleNetwork) } // force select a network, triggers [selectedNetwork] isSingleAvailableNetwork -> { val singleNetwork = portfolio.account.availableToAddNetworks.first() - callbackDelegate.onNetworkSelected(singleNetwork) + callbackDelegate.onNetworkSelected.send(singleNetwork) } // it's important to control root screen, UI depends on it(close/arrow icon) isSinglePortfolio -> navigation.replaceAll(routeToNetworkSelector(portfolio)) @@ -352,7 +352,7 @@ internal class AddToPortfolioModel @Inject constructor( val isSingleAvailableNetwork = portfolio.account.isSingleNetwork if (isSingleAvailableNetwork) { val singleNetwork = portfolio.account.availableToAddNetworks.first() - callbackDelegate.onNetworkSelected(singleNetwork) + callbackDelegate.onNetworkSelected.send(singleNetwork) } else { navigation.pushNew(routeToNetworkSelector(portfolio)) } diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/earn/DefaultEarnComponent.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/earn/DefaultEarnComponent.kt index 9e4094404f..17381adf81 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/earn/DefaultEarnComponent.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/earn/DefaultEarnComponent.kt @@ -33,15 +33,14 @@ import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.LocalMainBottomSheetColor import com.tangem.core.ui.res.LocalRedesignEnabled import com.tangem.core.ui.res.TangemTheme -import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioComponent import com.tangem.domain.models.earn.PreselectedEarnType +import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioComponent import com.tangem.features.feed.components.feed.FeedBottomSheetRoute -import kotlinx.serialization.Serializable import com.tangem.features.feed.model.earn.EarnModel -import com.tangem.features.feed.model.earn.analytics.EarnSource import com.tangem.features.feed.ui.components.FeedSearchBar import com.tangem.features.feed.ui.earn.EarnContent import dev.chrisbanes.haze.HazeProgressive +import kotlinx.serialization.Serializable internal class DefaultEarnComponent( appComponentContext: AppComponentContext, @@ -130,17 +129,12 @@ internal class DefaultEarnComponent( componentContext: ComponentContext, ): ComposableBottomSheetComponent = when (config) { is FeedBottomSheetRoute.AddToPortfolio -> { + val manager = checkNotNull(earnModel.currentAddToPortfolioManager) { + "currentAddToPortfolioManager must be set before activating AddToPortfolio slot" + } addToPortfolioComponentFactory.create( context = childByContext(componentContext), - params = AddToPortfolioComponent.Params( - addToPortfolioManager = when { - config.source == EarnSource.BEST_OPPORTUNITIES_SOURCE.value -> - earnModel.addBestOpportunitiesPortfolioManager - config.source == EarnSource.MOSTLY_USED_SOURCE.value -> - earnModel.addMostlyUsedPortfolioManager - else -> error("Unknown source: ${config.source}") - }, - ), + params = AddToPortfolioComponent.Params(addToPortfolioManager = manager), ) } is FeedBottomSheetRoute.NetworkFilter -> EarnNetworkFilterComponent( diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/feed/DefaultFeedComponent.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/feed/DefaultFeedComponent.kt index 21ebee8140..f83d390045 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/feed/DefaultFeedComponent.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/feed/DefaultFeedComponent.kt @@ -98,11 +98,12 @@ internal class DefaultFeedComponent( componentContext: ComponentContext, ): ComposableBottomSheetComponent = when (config) { is FeedBottomSheetRoute.AddToPortfolio -> { + val manager = checkNotNull(feedComponentModel.currentAddToPortfolioManager) { + "currentAddToPortfolioManager must be set before activating AddToPortfolio slot" + } addToPortfolioComponentFactory.create( context = childByContext(componentContext), - params = AddToPortfolioComponent.Params( - addToPortfolioManager = feedComponentModel.addToPortfolioManager, - ), + params = AddToPortfolioComponent.Params(addToPortfolioManager = manager), ) } is FeedBottomSheetRoute.NetworkFilter -> EmptyComposableBottomSheetComponent diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/earn/EarnModel.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/earn/EarnModel.kt index ae1280b2cf..bd7e22b2e4 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/earn/EarnModel.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/earn/EarnModel.kt @@ -22,8 +22,9 @@ import com.tangem.domain.markets.TokenMarketInfo import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.earn.EarnNetworks import com.tangem.domain.models.earn.EarnTokenWithCurrency -import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioManager import com.tangem.domain.models.earn.PreselectedEarnType +import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioManager +import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioManager.AnalyticsParams.Companion.CategoryEarn import com.tangem.features.feed.components.earn.DefaultEarnComponent import com.tangem.features.feed.components.earn.EarnNetworkFilterComponent import com.tangem.features.feed.components.earn.EarnTypeFilterComponent @@ -44,8 +45,8 @@ import com.tangem.features.feed.ui.earn.state.EarnFilterTypeUM import com.tangem.features.feed.ui.earn.state.EarnUM import com.tangem.utils.Provider import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.* import kotlinx.coroutines.flow.* -import kotlinx.coroutines.launch import javax.inject.Inject @Stable @@ -85,24 +86,13 @@ internal class EarnModel @Inject constructor( dispatchers = dispatchers, ) - val addBestOpportunitiesPortfolioManager: AddToPortfolioManager = addToPortfolioManagerFactory.create( - scope = modelScope, - settings = AddToPortfolioManager.Settings.Earn, - analyticsParams = AddToPortfolioManager.AnalyticsParams(source = EarnSource.BEST_OPPORTUNITIES_SOURCE.value), - ).apply { - updateLaunchMode(AddToPortfolioManager.LaunchMode.Preselected) - } - - val addMostlyUsedPortfolioManager: AddToPortfolioManager = addToPortfolioManagerFactory.create( - scope = modelScope, - settings = AddToPortfolioManager.Settings.Earn, - analyticsParams = AddToPortfolioManager.AnalyticsParams(source = EarnSource.MOSTLY_USED_SOURCE.value), - ).apply { - updateLaunchMode(AddToPortfolioManager.LaunchMode.Preselected) - } - val bottomSheetNavigation: SlotNavigation = SlotNavigation() + var currentAddToPortfolioManager: AddToPortfolioManager? = null + private set + + private var currentAddToPortfolioManagerScope: CoroutineScope? = null + val state: StateFlow get() = stateController.uiState @@ -114,30 +104,6 @@ internal class EarnModel @Inject constructor( subscribeOnNetworks() subscribeOnBatchFlow() subscribeToMostlyUsed() - - addBestOpportunitiesPortfolioManager.onDismiss.receiveAsFlow() - .onEach { bottomSheetNavigation.dismiss() } - .launchIn(modelScope) - addBestOpportunitiesPortfolioManager.onSuccessAdded.receiveAsFlow() - .onEach { bottomSheetNavigation.dismiss() } - .onEach(::openCurrencyDetails) - .launchIn(modelScope) - addBestOpportunitiesPortfolioManager.onAddedTokenClick.receiveAsFlow() - .onEach { bottomSheetNavigation.dismiss() } - .onEach(::openCurrencyDetails) - .launchIn(modelScope) - - addMostlyUsedPortfolioManager.onDismiss.receiveAsFlow() - .onEach { bottomSheetNavigation.dismiss() } - .launchIn(modelScope) - addMostlyUsedPortfolioManager.onSuccessAdded.receiveAsFlow() - .onEach { bottomSheetNavigation.dismiss() } - .onEach(::openCurrencyDetails) - .launchIn(modelScope) - addMostlyUsedPortfolioManager.onAddedTokenClick.receiveAsFlow() - .onEach { bottomSheetNavigation.dismiss() } - .onEach(::openCurrencyDetails) - .launchIn(modelScope) } private fun openCurrencyDetails(result: AddToPortfolioManager.Result) { @@ -354,17 +320,44 @@ internal class EarnModel @Inject constructor( contractAddress = earnTokenWithCurrency.earnToken.tokenAddress, decimalCount = earnTokenWithCurrency.earnToken.decimalCount, ) - when (source) { - EarnSource.BEST_OPPORTUNITIES_SOURCE -> addBestOpportunitiesPortfolioManager.apply { - setTokenParams(token) - setTokenNetworks(listOf(network)) - } - EarnSource.MOSTLY_USED_SOURCE -> addMostlyUsedPortfolioManager.apply { - setTokenParams(token) - setTokenNetworks(listOf(network)) - } + val manager = createAddToPortfolioManager(source = source).apply { + setTokenParams(token) + setTokenNetworks(listOf(network)) } - bottomSheetNavigation.activate(FeedBottomSheetRoute.AddToPortfolio(source.value)) + currentAddToPortfolioManager = manager + // Drop the slot through null so the same-source repeat click still recreates the child. + bottomSheetNavigation.dismiss() + bottomSheetNavigation.activate(FeedBottomSheetRoute.AddToPortfolio(source = source.value)) + } + + private fun createAddToPortfolioManager(source: EarnSource): AddToPortfolioManager { + currentAddToPortfolioManagerScope?.cancel() + val managerScope = CoroutineScope( + modelScope.coroutineContext + SupervisorJob(modelScope.coroutineContext.job), + ) + currentAddToPortfolioManagerScope = managerScope + + val manager = addToPortfolioManagerFactory.create( + scope = managerScope, + settings = AddToPortfolioManager.Settings.Earn, + analyticsParams = AddToPortfolioManager.AnalyticsParams(source = source.value, category = CategoryEarn), + ).apply { + updateLaunchMode(AddToPortfolioManager.LaunchMode.Preselected) + } + + manager.onDismiss.receiveAsFlow() + .onEach { bottomSheetNavigation.dismiss() } + .launchIn(managerScope) + manager.onSuccessAdded.receiveAsFlow() + .onEach { bottomSheetNavigation.dismiss() } + .onEach(::openCurrencyDetails) + .launchIn(managerScope) + manager.onAddedTokenClick.receiveAsFlow() + .onEach { bottomSheetNavigation.dismiss() } + .onEach(::openCurrencyDetails) + .launchIn(managerScope) + + return manager } private fun onTypeFilterOptionSelected(type: EarnFilterType) { diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/feed/FeedComponentModel.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/feed/FeedComponentModel.kt index 7a7c71ccc5..a0cb8e55db 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/feed/FeedComponentModel.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/feed/FeedComponentModel.kt @@ -41,10 +41,8 @@ import com.tangem.utils.Provider import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentHashMap -import kotlinx.coroutines.Job -import kotlinx.coroutines.delay +import kotlinx.coroutines.* import kotlinx.coroutines.flow.* -import kotlinx.coroutines.launch import org.joda.time.DateTime import org.joda.time.DateTimeZone import javax.inject.Inject @@ -62,7 +60,7 @@ internal class FeedComponentModel @Inject constructor( private val getTopEarnTokensUseCase: GetTopEarnTokensUseCase, private val appRouter: AppRouter, private val designFeatureToggles: DesignFeatureToggles, - addToPortfolioManagerFactory: AddToPortfolioManager.Factory, + private val addToPortfolioManagerFactory: AddToPortfolioManager.Factory, getTopFiveMarketTokenUseCase: GetTopFiveMarketTokenUseCase, getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, paramsContainer: ParamsContainer, @@ -87,16 +85,13 @@ internal class FeedComponentModel @Inject constructor( dispatchers = dispatchers, ) - val addToPortfolioManager: AddToPortfolioManager = addToPortfolioManagerFactory.create( - scope = modelScope, - settings = AddToPortfolioManager.Settings.Earn, - analyticsParams = AddToPortfolioManager.AnalyticsParams(source = AnalyticsParam.ScreensSources.Markets.value), - ).apply { - updateLaunchMode(AddToPortfolioManager.LaunchMode.Preselected) - } - val bottomSheetNavigation: SlotNavigation = SlotNavigation() + var currentAddToPortfolioManager: AddToPortfolioManager? = null + private set + + private var currentAddToPortfolioManagerScope: CoroutineScope? = null + val state: StateFlow get() = stateController.uiState @@ -110,18 +105,6 @@ internal class FeedComponentModel @Inject constructor( fetchCharts() subscribeOnCurrencyUpdate() subscribeOnDataState() - - addToPortfolioManager.onDismiss.receiveAsFlow() - .onEach { bottomSheetNavigation.dismiss() } - .launchIn(modelScope) - addToPortfolioManager.onSuccessAdded.receiveAsFlow() - .onEach { bottomSheetNavigation.dismiss() } - .onEach(::openCurrencyDetails) - .launchIn(modelScope) - addToPortfolioManager.onAddedTokenClick.receiveAsFlow() - .onEach { bottomSheetNavigation.dismiss() } - .onEach(::openCurrencyDetails) - .launchIn(modelScope) } private fun openCurrencyDetails(result: AddToPortfolioManager.Result) { @@ -431,12 +414,46 @@ internal class FeedComponentModel @Inject constructor( contractAddress = earnTokenWithCurrency.earnToken.tokenAddress, decimalCount = earnTokenWithCurrency.earnToken.decimalCount, ) - val route = FeedBottomSheetRoute.AddToPortfolio(AnalyticsParam.ScreensSources.Markets.value) - addToPortfolioManager.apply { + val manager = createAddToPortfolioManager().apply { setTokenParams(token) setTokenNetworks(listOf(network)) } - bottomSheetNavigation.activate(route) + currentAddToPortfolioManager = manager + // Drop the slot through null so the same-source repeat click still recreates the child. + bottomSheetNavigation.dismiss() + bottomSheetNavigation.activate(FeedBottomSheetRoute.AddToPortfolio(AnalyticsParam.ScreensSources.Markets.value)) + } + + private fun createAddToPortfolioManager(): AddToPortfolioManager { + currentAddToPortfolioManagerScope?.cancel() + val managerScope = CoroutineScope( + modelScope.coroutineContext + SupervisorJob(modelScope.coroutineContext.job), + ) + currentAddToPortfolioManagerScope = managerScope + + val manager = addToPortfolioManagerFactory.create( + scope = managerScope, + settings = AddToPortfolioManager.Settings.Earn, + analyticsParams = AddToPortfolioManager.AnalyticsParams( + source = AnalyticsParam.ScreensSources.Markets.value, + ), + ).apply { + updateLaunchMode(AddToPortfolioManager.LaunchMode.Preselected) + } + + manager.onDismiss.receiveAsFlow() + .onEach { bottomSheetNavigation.dismiss() } + .launchIn(managerScope) + manager.onSuccessAdded.receiveAsFlow() + .onEach { bottomSheetNavigation.dismiss() } + .onEach(::openCurrencyDetails) + .launchIn(managerScope) + manager.onAddedTokenClick.receiveAsFlow() + .onEach { bottomSheetNavigation.dismiss() } + .onEach(::openCurrencyDetails) + .launchIn(managerScope) + + return manager } private fun handleEarnPageOpenClicked() {