Updated on 2026-08-14

This commit is contained in:
Tangem 2025-02-17 13:19:28 +03:00
parent 7026daf2fd
commit 46c19f2a3c
8 changed files with 146 additions and 148 deletions

View file

@ -5,7 +5,6 @@ import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.feature.qrscanning.QrScanningRouter import com.tangem.feature.qrscanning.QrScanningRouter
import com.tangem.feature.referral.ReferralFragment import com.tangem.feature.referral.ReferralFragment
import com.tangem.feature.stories.api.StoriesComponent import com.tangem.feature.stories.api.StoriesComponent
import com.tangem.feature.swap.presentation.SwapFragment
import com.tangem.feature.walletsettings.component.WalletSettingsComponent import com.tangem.feature.walletsettings.component.WalletSettingsComponent
import com.tangem.features.details.component.DetailsComponent import com.tangem.features.details.component.DetailsComponent
import com.tangem.features.disclaimer.api.components.DisclaimerComponent import com.tangem.features.disclaimer.api.components.DisclaimerComponent
@ -16,6 +15,7 @@ import com.tangem.features.onboarding.v2.entry.OnboardingEntryComponent
import com.tangem.features.onramp.component.* import com.tangem.features.onramp.component.*
import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter
import com.tangem.features.send.api.navigation.SendRouter import com.tangem.features.send.api.navigation.SendRouter
import com.tangem.features.swap.SwapComponent
import com.tangem.features.staking.api.StakingComponent import com.tangem.features.staking.api.StakingComponent
import com.tangem.features.tester.api.TesterRouter import com.tangem.features.tester.api.TesterRouter
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
@ -59,6 +59,7 @@ internal class ChildFactory @Inject constructor(
private val welcomeComponentFactory: WelcomeComponent.Factory, private val welcomeComponentFactory: WelcomeComponent.Factory,
private val storiesComponentFactory: StoriesComponent.Factory, private val storiesComponentFactory: StoriesComponent.Factory,
private val stakingComponentFactory: StakingComponent.Factory, private val stakingComponentFactory: StakingComponent.Factory,
private val swapComponentFactory: SwapComponent.Factory,
private val sendRouter: SendRouter, private val sendRouter: SendRouter,
private val tokenDetailsRouter: TokenDetailsRouter, private val tokenDetailsRouter: TokenDetailsRouter,
private val walletRouter: WalletRouter, private val walletRouter: WalletRouter,
@ -222,6 +223,18 @@ internal class ChildFactory @Inject constructor(
componentFactory = stakingComponentFactory, componentFactory = stakingComponentFactory,
) )
} }
is AppRoute.Swap -> {
createComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = SwapComponent.Params(
currencyFrom = route.currencyFrom,
currencyTo = route.currencyTo,
userWalletId = route.userWalletId,
isInitialReverseOrder = route.isInitialReverseOrder,
),
componentFactory = swapComponentFactory,
)
}
is AppRoute.AccessCodeRecovery, is AppRoute.AccessCodeRecovery,
is AppRoute.AppCurrencySelector, is AppRoute.AppCurrencySelector,
is AppRoute.SaveWallet, is AppRoute.SaveWallet,
@ -237,7 +250,6 @@ internal class ChildFactory @Inject constructor(
is AppRoute.QrScanning, is AppRoute.QrScanning,
is AppRoute.ReferralProgram, is AppRoute.ReferralProgram,
is AppRoute.ResetToFactory, is AppRoute.ResetToFactory,
is AppRoute.Swap,
is AppRoute.Wallet, is AppRoute.Wallet,
is AppRoute.WalletConnectSessions, is AppRoute.WalletConnectSessions,
is AppRoute.CurrencyDetails, is AppRoute.CurrencyDetails,
@ -329,7 +341,16 @@ internal class ChildFactory @Inject constructor(
route.asFragmentChild(Provider { ResetCardFragment() }) route.asFragmentChild(Provider { ResetCardFragment() })
} }
is AppRoute.Swap -> { is AppRoute.Swap -> {
route.asFragmentChild(Provider { SwapFragment() }) route.asComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = SwapComponent.Params(
currencyFrom = route.currencyFrom,
currencyTo = route.currencyTo,
userWalletId = route.userWalletId,
isInitialReverseOrder = route.isInitialReverseOrder,
),
componentFactory = swapComponentFactory,
)
} }
is AppRoute.Wallet -> { is AppRoute.Wallet -> {
route.asFragmentChild(Provider { walletRouter.getEntryFragment() }) route.asFragmentChild(Provider { walletRouter.getEntryFragment() })

View file

@ -1,8 +1,19 @@
package com.tangem.feature.swap package com.tangem.feature.swap
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.background
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import com.arkivanov.essenty.lifecycle.subscribe
import com.tangem.common.ui.swapStoriesScreen.SwapStoriesScreen
import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.swap.model.SwapModel
import com.tangem.feature.swap.router.SwapNavScreen
import com.tangem.feature.swap.ui.SwapScreen
import com.tangem.feature.swap.ui.SwapSelectTokenScreen
import com.tangem.feature.swap.ui.SwapSuccessScreen
import com.tangem.features.swap.SwapComponent import com.tangem.features.swap.SwapComponent
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
@ -14,9 +25,50 @@ internal class DefaultSwapComponent @AssistedInject constructor(
@Assisted params: SwapComponent.Params, @Assisted params: SwapComponent.Params,
) : SwapComponent, AppComponentContext by appComponentContext { ) : SwapComponent, AppComponentContext by appComponentContext {
private val model: SwapModel = getOrCreateModel(params)
init {
lifecycle.subscribe(
onStart = model::onStart,
onStop = model::onStop,
)
}
@Composable @Composable
override fun Content(modifier: Modifier) { override fun Content(modifier: Modifier) {
TODO("Not yet implemented") Crossfade(
modifier = Modifier.background(TangemTheme.colors.background.secondary),
targetState = model.currentScreen,
label = "",
) { screen ->
when (screen) {
SwapNavScreen.PromoStories -> {
val storiesConfig = model.uiState.storiesConfig
if (storiesConfig != null) {
SwapStoriesScreen(config = storiesConfig)
} else {
SwapScreen(stateHolder = model.uiState)
}
}
SwapNavScreen.Main -> SwapScreen(stateHolder = model.uiState)
SwapNavScreen.Success -> {
val successState = model.uiState.successState
if (successState != null) {
SwapSuccessScreen(state = successState, model.uiState.onBackClicked)
} else {
SwapScreen(stateHolder = model.uiState)
}
}
SwapNavScreen.SelectToken -> {
val tokenState = model.uiState.selectTokenState
if (tokenState != null) {
SwapSelectTokenScreen(state = tokenState, onBack = model.uiState.onBackClicked)
} else {
SwapScreen(stateHolder = model.uiState)
}
}
}
}
} }
@AssistedFactory @AssistedFactory

View file

@ -0,0 +1,20 @@
package com.tangem.feature.swap.di
import com.tangem.core.decompose.di.DecomposeComponent
import com.tangem.core.decompose.model.Model
import com.tangem.feature.swap.model.SwapModel
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
@Module
@InstallIn(DecomposeComponent::class)
internal interface SwapModelModule {
@Binds
@IntoMap
@ClassKey(SwapModel::class)
fun provideSwapModel(model: SwapModel): Model
}

View file

@ -1,20 +1,20 @@
package com.tangem.feature.swap.viewmodels package com.tangem.feature.swap.model
import android.os.Bundle import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.lifecycle.*
import arrow.core.Either import arrow.core.Either
import arrow.core.getOrElse import arrow.core.getOrElse
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter import com.tangem.common.routing.AppRouter
import com.tangem.common.routing.bundle.unbundle
import com.tangem.common.ui.bottomsheet.permission.state.ApproveType import com.tangem.common.ui.bottomsheet.permission.state.ApproveType
import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionState.InProgress.getApproveTypeOrNull import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionState.InProgress.getApproveTypeOrNull
import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic import com.tangem.core.analytics.models.Basic
import com.tangem.core.decompose.di.ComponentScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.navigation.url.UrlOpener import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.resourceReference
@ -53,11 +53,11 @@ import com.tangem.feature.swap.router.SwapNavScreen
import com.tangem.feature.swap.router.SwapRouter import com.tangem.feature.swap.router.SwapRouter
import com.tangem.feature.swap.ui.StateBuilder import com.tangem.feature.swap.ui.StateBuilder
import com.tangem.feature.swap.utils.formatToUIRepresentation import com.tangem.feature.swap.utils.formatToUIRepresentation
import com.tangem.features.swap.SwapComponent
import com.tangem.features.swap.SwapFeatureToggles import com.tangem.features.swap.SwapFeatureToggles
import com.tangem.utils.Provider import com.tangem.utils.Provider
import com.tangem.utils.coroutines.* import com.tangem.utils.coroutines.*
import com.tangem.utils.isNullOrZero import com.tangem.utils.isNullOrZero
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -73,11 +73,13 @@ import kotlin.properties.Delegates
typealias SuccessLoadedSwapData = Map<SwapProvider, SwapState.QuotesLoadedState> typealias SuccessLoadedSwapData = Map<SwapProvider, SwapState.QuotesLoadedState>
@Suppress("LargeClass", "LongParameterList") @Suppress("LongParameterList", "LargeClass")
@HiltViewModel @Stable
internal class SwapViewModel @Inject constructor( @ComponentScoped
internal class SwapModel @Inject constructor(
paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
private val blockchainInteractor: BlockchainInteractor, private val blockchainInteractor: BlockchainInteractor,
private val dispatchers: CoroutineDispatcherProvider,
private val analyticsEventHandler: AnalyticsEventHandler, private val analyticsEventHandler: AnalyticsEventHandler,
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
@ -94,27 +96,16 @@ internal class SwapViewModel @Inject constructor(
private val getStoryContentUseCase: GetStoryContentUseCase, private val getStoryContentUseCase: GetStoryContentUseCase,
private val featureToggles: SwapFeatureToggles, private val featureToggles: SwapFeatureToggles,
swapInteractorFactory: SwapInteractor.Factory, swapInteractorFactory: SwapInteractor.Factory,
private val savedStateHandle: SavedStateHandle,
private val urlOpener: UrlOpener, private val urlOpener: UrlOpener,
router: AppRouter, router: AppRouter,
) : ViewModel(), DefaultLifecycleObserver { ) : Model() {
private val initialCurrencyFrom: CryptoCurrency private val params = paramsContainer.require<SwapComponent.Params>()
get() {
return savedStateHandle.get<Bundle>(AppRoute.Swap.CURRENCY_FROM_KEY)
?.unbundle(CryptoCurrency.serializer())
?: error("no expected parameter CryptoCurrency (from) found")
}
private val initialCurrencyTo: CryptoCurrency? = savedStateHandle.get<Bundle>(AppRoute.Swap.CURRENCY_TO_KEY) private val initialCurrencyFrom = params.currencyFrom
?.unbundle(CryptoCurrency.serializer()) private val initialCurrencyTo = params.currencyTo
private val userWalletId = params.userWalletId
private val userWalletId: UserWalletId = savedStateHandle.get<Bundle>(AppRoute.Swap.USER_WALLET_ID_KEY) private val isInitiallyReversed = params.isInitialReverseOrder
?.unbundle(UserWalletId.serializer())
?: error("no expected parameter UserWalletId found")
private val isInitiallyReversed: Boolean = savedStateHandle.get<Boolean>(AppRoute.Swap.IS_INITIAL_REVERSE_ORDER)
?: false
private val swapInteractor = swapInteractorFactory.create(userWalletId) private val swapInteractor = swapInteractorFactory.create(userWalletId)
@ -171,13 +162,13 @@ internal class SwapViewModel @Inject constructor(
get() = swapRouter.currentScreen get() = swapRouter.currentScreen
init { init {
viewModelScope.launch { modelScope.launch {
if (featureToggles.isPromoStoriesEnabled) { if (featureToggles.isPromoStoriesEnabled) {
initStories() initStories()
swapRouter.openScreen(SwapNavScreen.PromoStories) swapRouter.openScreen(SwapNavScreen.PromoStories)
} }
} }
viewModelScope.launch(dispatchers.io) { modelScope.launch(dispatchers.io) {
val fromStatus = getCryptoCurrencyStatusUseCase(userWalletId, initialCurrencyFrom.id).getOrNull() val fromStatus = getCryptoCurrencyStatusUseCase(userWalletId, initialCurrencyFrom.id).getOrNull()
val toStatus = initialCurrencyTo?.let { getCryptoCurrencyStatusUseCase(userWalletId, it.id).getOrNull() } val toStatus = initialCurrencyTo?.let { getCryptoCurrencyStatusUseCase(userWalletId, it.id).getOrNull() }
val wallet = getUserWalletUseCase(userWalletId).getOrNull() val wallet = getUserWalletUseCase(userWalletId).getOrNull()
@ -191,32 +182,28 @@ internal class SwapViewModel @Inject constructor(
initTokens(isInitiallyReversed) initTokens(isInitiallyReversed)
} }
} }
}
override fun onCreate(owner: LifecycleOwner) { analyticsEventHandler.send(SwapEvents.SwapScreenOpened(initialCurrencyFrom.symbol))
getBalanceHidingSettingsUseCase().flowWithLifecycle(owner.lifecycle).onEach {
isBalanceHidden = it.isBalanceHidden getBalanceHidingSettingsUseCase()
withContext(dispatchers.main) { .onEach {
isBalanceHidden = it.isBalanceHidden
uiState = stateBuilder.updateBalanceHiddenState(uiState, isBalanceHidden) uiState = stateBuilder.updateBalanceHiddenState(uiState, isBalanceHidden)
} }
}.launchIn(viewModelScope) .launchIn(modelScope)
} }
override fun onStart(owner: LifecycleOwner) { fun onStart() {
startLoadingQuotesFromLastState(true) startLoadingQuotesFromLastState(true)
} }
override fun onStop(owner: LifecycleOwner) { fun onStop() {
singleTaskScheduler.cancelTask() singleTaskScheduler.cancelTask()
} }
override fun onCleared() { override fun onDestroy() {
singleTaskScheduler.cancelTask() singleTaskScheduler.cancelTask()
super.onCleared() super.onDestroy()
}
fun onScreenOpened() {
analyticsEventHandler.send(SwapEvents.SwapScreenOpened(initialCurrencyFrom.symbol))
} }
private fun sendSelectTokenScreenOpenedEvent() { private fun sendSelectTokenScreenOpenedEvent() {
@ -227,7 +214,7 @@ internal class SwapViewModel @Inject constructor(
} }
private fun initTokens(isReverseFromTo: Boolean) { private fun initTokens(isReverseFromTo: Boolean) {
viewModelScope.launch(dispatchers.main) { modelScope.launch(dispatchers.main) {
runCatching(dispatchers.io) { runCatching(dispatchers.io) {
swapInteractor.getTokensDataState(initialCurrencyFrom) swapInteractor.getTokensDataState(initialCurrencyFrom)
}.onSuccess { state -> }.onSuccess { state ->
@ -286,7 +273,7 @@ internal class SwapViewModel @Inject constructor(
} }
private fun initStories() { private fun initStories() {
viewModelScope.launch { modelScope.launch {
getStoryContentUseCase.invokeSync(StoryContentIds.STORY_FIRST_TIME_SWAP.id).fold( getStoryContentUseCase.invokeSync(StoryContentIds.STORY_FIRST_TIME_SWAP.id).fold(
ifLeft = { ifLeft = {
Timber.e("Unable to load stories for ${StoryContentIds.STORY_FIRST_TIME_SWAP.id}") Timber.e("Unable to load stories for ${StoryContentIds.STORY_FIRST_TIME_SWAP.id}")
@ -361,7 +348,7 @@ internal class SwapViewModel @Inject constructor(
) )
} }
singleTaskScheduler.scheduleTask( singleTaskScheduler.scheduleTask(
viewModelScope, modelScope,
loadQuotesTask( loadQuotesTask(
fromToken = fromToken, fromToken = fromToken,
toToken = toToken, toToken = toToken,
@ -602,7 +589,7 @@ internal class SwapViewModel @Inject constructor(
makeDefaultAlert(resourceReference(R.string.swapping_fee_estimation_error_text)) makeDefaultAlert(resourceReference(R.string.swapping_fee_estimation_error_text))
return return
} }
viewModelScope.launch(dispatchers.main) { modelScope.launch(dispatchers.main) {
runCatching(dispatchers.io) { runCatching(dispatchers.io) {
swapInteractor.onSwap( swapInteractor.onSwap(
swapProvider = provider, swapProvider = provider,
@ -691,7 +678,7 @@ internal class SwapViewModel @Inject constructor(
} }
private fun givePermissionsToSwap() { private fun givePermissionsToSwap() {
viewModelScope.launch(dispatchers.main) { modelScope.launch(dispatchers.main) {
runCatching { runCatching {
val fromToken = requireNotNull(dataState.fromCryptoCurrency?.currency) { val fromToken = requireNotNull(dataState.fromCryptoCurrency?.currency) {
"dataState.fromCurrency might not be null" "dataState.fromCurrency might not be null"
@ -756,7 +743,7 @@ internal class SwapViewModel @Inject constructor(
} }
private fun onSearchEntered(searchQuery: String) { private fun onSearchEntered(searchQuery: String) {
viewModelScope.launch(dispatchers.io) { modelScope.launch(dispatchers.io) {
val tokenDataState = dataState.tokensDataState ?: return@launch val tokenDataState = dataState.tokensDataState ?: return@launch
val group = if (isOrderReversed) { val group = if (isOrderReversed) {
tokenDataState.fromGroup tokenDataState.fromGroup
@ -892,12 +879,12 @@ internal class SwapViewModel @Inject constructor(
startLoadingQuotesFromLastState(isSilent = true) startLoadingQuotesFromLastState(isSilent = true)
} }
.flowOn(dispatchers.main) .flowOn(dispatchers.main)
.launchIn(viewModelScope) .launchIn(modelScope)
.saveIn(if (isFromCurrency) fromTokenBalanceJobHolder else toTokenBalanceJobHolder) .saveIn(if (isFromCurrency) fromTokenBalanceJobHolder else toTokenBalanceJobHolder)
} }
private fun onChangeCardsClicked() { private fun onChangeCardsClicked() {
viewModelScope.launch { modelScope.launch {
val newFromToken = dataState.toCryptoCurrency val newFromToken = dataState.toCryptoCurrency
val newToToken = dataState.fromCryptoCurrency val newToToken = dataState.fromCryptoCurrency
@ -943,7 +930,7 @@ internal class SwapViewModel @Inject constructor(
forceQuotesUpdate: Boolean = false, forceQuotesUpdate: Boolean = false,
reduceBalanceBy: BigDecimal = BigDecimal.ZERO, reduceBalanceBy: BigDecimal = BigDecimal.ZERO,
) { ) {
viewModelScope.launch { modelScope.launch {
val fromToken = dataState.fromCryptoCurrency val fromToken = dataState.fromCryptoCurrency
val toToken = dataState.toCryptoCurrency val toToken = dataState.toCryptoCurrency
if (fromToken != null) { if (fromToken != null) {
@ -968,7 +955,7 @@ internal class SwapViewModel @Inject constructor(
isAmountChangedByUser = true isAmountChangedByUser = true
} }
amountDebouncer.debounce(viewModelScope, DEBOUNCE_AMOUNT_DELAY, forceUpdate = forceQuotesUpdate) { amountDebouncer.debounce(modelScope, DEBOUNCE_AMOUNT_DELAY, forceUpdate = forceQuotesUpdate) {
startLoadingQuotes( startLoadingQuotes(
fromToken = fromToken, fromToken = fromToken,
toToken = toToken, toToken = toToken,
@ -1085,7 +1072,7 @@ internal class SwapViewModel @Inject constructor(
onSelectFeeType = { onSelectFeeType = {
uiState = stateBuilder.dismissBottomSheet(uiState) uiState = stateBuilder.dismissBottomSheet(uiState)
dataState = dataState.copy(selectedFee = it) dataState = dataState.copy(selectedFee = it)
viewModelScope.launch(dispatchers.io) { modelScope.launch(dispatchers.io) {
startLoadingQuotesFromLastState(false) startLoadingQuotesFromLastState(false)
} }
}, },
@ -1145,7 +1132,7 @@ internal class SwapViewModel @Inject constructor(
swapRouter.openScreen(SwapNavScreen.Success) swapRouter.openScreen(SwapNavScreen.Success)
}, },
onStoriesClose = { onStoriesClose = {
viewModelScope.launch { shouldShowStoriesUseCase.neverToShow(StoryContentIds.STORY_FIRST_TIME_SWAP.id) } modelScope.launch { shouldShowStoriesUseCase.neverToShow(StoryContentIds.STORY_FIRST_TIME_SWAP.id) }
swapRouter.openScreen(SwapNavScreen.Main) swapRouter.openScreen(SwapNavScreen.Main)
}, },
) )
@ -1227,7 +1214,7 @@ internal class SwapViewModel @Inject constructor(
return getSelectedAppCurrencyUseCase().map { maybeAppCurrency -> return getSelectedAppCurrencyUseCase().map { maybeAppCurrency ->
maybeAppCurrency.getOrElse { AppCurrency.Default } maybeAppCurrency.getOrElse { AppCurrency.Default }
}.stateIn( }.stateIn(
scope = viewModelScope, scope = modelScope,
started = SharingStarted.Eagerly, started = SharingStarted.Eagerly,
initialValue = AppCurrency.Default, initialValue = AppCurrency.Default,
) )
@ -1295,7 +1282,7 @@ internal class SwapViewModel @Inject constructor(
private fun updateWalletBalance() { private fun updateWalletBalance() {
dataState.fromCryptoCurrency?.currency?.network?.let { network -> dataState.fromCryptoCurrency?.currency?.network?.let { network ->
viewModelScope.launch { modelScope.launch {
withContext(NonCancellable) { withContext(NonCancellable) {
updateForBalance(userWalletId, network) updateForBalance(userWalletId, network)
} }
@ -1336,7 +1323,7 @@ internal class SwapViewModel @Inject constructor(
} }
private fun onFailedTxEmailClick(errorMessage: String) { private fun onFailedTxEmailClick(errorMessage: String) {
viewModelScope.launch { modelScope.launch {
val transaction = dataState.swapDataModel?.transaction val transaction = dataState.swapDataModel?.transaction
val fromCurrencyStatus = dataState.fromCryptoCurrency ?: initialFromStatus val fromCurrencyStatus = dataState.fromCryptoCurrency ?: initialFromStatus
val network = fromCurrencyStatus.currency.network val network = fromCurrencyStatus.currency.network

View file

@ -1,4 +1,4 @@
package com.tangem.feature.swap.presentation package com.tangem.feature.swap.model
import com.tangem.common.ui.notifications.NotificationUM import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.common.ui.notifications.NotificationsFactory.addDustWarningNotification import com.tangem.common.ui.notifications.NotificationsFactory.addDustWarningNotification

View file

@ -1,4 +1,4 @@
package com.tangem.feature.swap.viewmodels package com.tangem.feature.swap.model
import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.feature.swap.domain.models.domain.SwapDataModel import com.tangem.feature.swap.domain.models.domain.SwapDataModel

View file

@ -1,82 +0,0 @@
package com.tangem.feature.swap.presentation
import android.os.Bundle
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.background
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.fragment.app.viewModels
import com.tangem.common.ui.swapStoriesScreen.SwapStoriesScreen
import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.feature.swap.router.SwapNavScreen
import com.tangem.feature.swap.ui.SwapScreen
import com.tangem.feature.swap.ui.SwapSelectTokenScreen
import com.tangem.feature.swap.ui.SwapSuccessScreen
import com.tangem.feature.swap.viewmodels.SwapViewModel
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
class SwapFragment : ComposeFragment() {
@Inject
override lateinit var uiDependencies: UiDependencies
private val viewModel by viewModels<SwapViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycle.addObserver(viewModel)
}
@Composable
override fun ScreenContent(modifier: Modifier) {
viewModel.onScreenOpened()
ScreenContent(viewModel = viewModel)
}
@Suppress("TopLevelComposableFunctions")
@Composable
private fun ScreenContent(viewModel: SwapViewModel) {
Crossfade(
modifier = Modifier.background(TangemTheme.colors.background.secondary),
targetState = viewModel.currentScreen,
label = "",
) { screen ->
when (screen) {
SwapNavScreen.PromoStories -> {
val storiesConfig = viewModel.uiState.storiesConfig
if (storiesConfig != null) {
SwapStoriesScreen(config = storiesConfig)
} else {
SwapScreen(stateHolder = viewModel.uiState)
}
}
SwapNavScreen.Main -> SwapScreen(stateHolder = viewModel.uiState)
SwapNavScreen.Success -> {
val successState = viewModel.uiState.successState
if (successState != null) {
SwapSuccessScreen(state = successState, viewModel.uiState.onBackClicked)
} else {
SwapScreen(stateHolder = viewModel.uiState)
}
}
SwapNavScreen.SelectToken -> {
val tokenState = viewModel.uiState.selectTokenState
if (tokenState != null) {
SwapSelectTokenScreen(state = tokenState, onBack = viewModel.uiState.onBackClicked)
} else {
SwapScreen(stateHolder = viewModel.uiState)
}
}
}
}
}
override fun onDestroy() {
lifecycle.removeObserver(viewModel)
super.onDestroy()
}
}

View file

@ -33,9 +33,9 @@ import com.tangem.feature.swap.models.*
import com.tangem.feature.swap.models.states.* import com.tangem.feature.swap.models.states.*
import com.tangem.feature.swap.models.states.events.SwapEvent import com.tangem.feature.swap.models.states.events.SwapEvent
import com.tangem.feature.swap.presentation.R import com.tangem.feature.swap.presentation.R
import com.tangem.feature.swap.presentation.SwapNotificationsFactory import com.tangem.feature.swap.model.SwapNotificationsFactory
import com.tangem.feature.swap.utils.formatToUIRepresentation import com.tangem.feature.swap.utils.formatToUIRepresentation
import com.tangem.feature.swap.viewmodels.SwapProcessDataState import com.tangem.feature.swap.model.SwapProcessDataState
import com.tangem.utils.Provider import com.tangem.utils.Provider
import com.tangem.utils.StringsSigns.DASH_SIGN import com.tangem.utils.StringsSigns.DASH_SIGN
import com.tangem.utils.StringsSigns.PERCENT import com.tangem.utils.StringsSigns.PERCENT