Updated on 2026-08-14
This commit is contained in:
parent
7026daf2fd
commit
46c19f2a3c
8 changed files with 146 additions and 148 deletions
|
|
@ -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() })
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue