Updated on 2026-08-14

This commit is contained in:
Tangem 2025-02-17 13:38:04 +03:00
commit 6942cb3171
32 changed files with 323 additions and 353 deletions

View file

@ -65,7 +65,6 @@ import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
import com.tangem.features.send.api.navigation.SendRouter
import com.tangem.features.staking.api.navigation.StakingRouter
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
import com.tangem.features.wallet.navigation.WalletRouter
import com.tangem.google.GoogleServicesHelper
@ -178,9 +177,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
@Inject
lateinit var emailSender: EmailSender
@Inject
lateinit var stakingRouter: StakingRouter
@Inject
@RootAppComponentContext
internal lateinit var rootComponentContext: AppComponentContext

View file

@ -1,6 +1,5 @@
package com.tangem.tap.di.domain
import com.tangem.domain.tokens.*
import com.tangem.domain.txhistory.repository.TxHistoryRepository
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
import com.tangem.domain.txhistory.usecase.GetFixedTxHistoryItemsUseCase
@ -9,27 +8,23 @@ import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ViewModelComponent
import dagger.hilt.android.scopes.ViewModelScoped
import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(ViewModelComponent::class)
@InstallIn(SingletonComponent::class)
internal object TxHistoryDomainModule {
@Provides
@ViewModelScoped
fun provideGetTxHistoryItemsCountUseCase(txHistoryRepository: TxHistoryRepository): GetTxHistoryItemsCountUseCase {
return GetTxHistoryItemsCountUseCase(repository = txHistoryRepository)
}
@Provides
@ViewModelScoped
fun provideGetTxHistoryItemsUseCase(txHistoryRepository: TxHistoryRepository): GetTxHistoryItemsUseCase {
return GetTxHistoryItemsUseCase(repository = txHistoryRepository)
}
@Provides
@ViewModelScoped
fun providesGetExplorerTransactionUrlUseCase(
txHistoryRepository: TxHistoryRepository,
): GetExplorerTransactionUrlUseCase {
@ -37,7 +32,6 @@ internal object TxHistoryDomainModule {
}
@Provides
@ViewModelScoped
fun providesGetFixedTxHistoryItemsUseCase(txHistoryRepository: TxHistoryRepository): GetFixedTxHistoryItemsUseCase {
return GetFixedTxHistoryItemsUseCase(repository = txHistoryRepository)
}

View file

@ -1,7 +1,6 @@
package com.tangem.tap.proxy.redux
import com.tangem.blockchainsdk.BlockchainSDKFactory
import com.tangem.data.card.TransactionSignerFactory
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.common.routing.AppRouter
import com.tangem.core.decompose.ui.UiMessageSender
@ -9,6 +8,7 @@ import com.tangem.core.navigation.settings.SettingsManager
import com.tangem.core.navigation.share.ShareManager
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.clipboard.ClipboardManager
import com.tangem.data.card.TransactionSignerFactory
import com.tangem.datasource.connection.NetworkConnectionManager
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
import com.tangem.datasource.local.config.issuers.IssuersConfigStorage
@ -34,7 +34,6 @@ import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
import com.tangem.features.onramp.OnrampFeatureToggles
import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter
import com.tangem.features.staking.api.navigation.StakingRouter
import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles
import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
@ -65,7 +64,6 @@ data class DaggerGraphState(
val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase? = null,
val getCardInfoUseCase: GetCardInfoUseCase? = null,
val issuersConfigStorage: IssuersConfigStorage? = null,
val stakingRouter: StakingRouter? = null,
val urlOpener: UrlOpener? = null,
val shareManager: ShareManager? = null,
val appRouter: AppRouter? = null,

View file

@ -5,7 +5,6 @@ import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.feature.qrscanning.QrScanningRouter
import com.tangem.feature.referral.ReferralFragment
import com.tangem.feature.stories.api.StoriesComponent
import com.tangem.feature.swap.presentation.SwapFragment
import com.tangem.feature.walletsettings.component.WalletSettingsComponent
import com.tangem.features.details.component.DetailsComponent
import com.tangem.features.disclaimer.api.components.DisclaimerComponent
@ -16,7 +15,8 @@ import com.tangem.features.onboarding.v2.entry.OnboardingEntryComponent
import com.tangem.features.onramp.component.*
import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter
import com.tangem.features.send.api.navigation.SendRouter
import com.tangem.features.staking.api.navigation.StakingRouter
import com.tangem.features.swap.SwapComponent
import com.tangem.features.staking.api.StakingComponent
import com.tangem.features.tester.api.TesterRouter
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
import com.tangem.features.wallet.navigation.WalletRouter
@ -58,11 +58,12 @@ internal class ChildFactory @Inject constructor(
private val onboardingEntryComponentFactory: OnboardingEntryComponent.Factory,
private val welcomeComponentFactory: WelcomeComponent.Factory,
private val storiesComponentFactory: StoriesComponent.Factory,
private val stakingComponentFactory: StakingComponent.Factory,
private val swapComponentFactory: SwapComponent.Factory,
private val sendRouter: SendRouter,
private val tokenDetailsRouter: TokenDetailsRouter,
private val walletRouter: WalletRouter,
private val qrScanningRouter: QrScanningRouter,
private val stakingRouter: StakingRouter,
private val testerRouter: TesterRouter,
private val pushNotificationRouter: PushNotificationsRouter,
private val routingFeatureToggles: RoutingFeatureToggles,
@ -211,6 +212,29 @@ internal class ChildFactory @Inject constructor(
componentFactory = storiesComponentFactory,
)
}
is AppRoute.Staking -> {
createComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = StakingComponent.Params(
userWalletId = route.userWalletId,
cryptoCurrencyId = route.cryptoCurrencyId,
yield = route.yield,
),
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.AppCurrencySelector,
is AppRoute.SaveWallet,
@ -226,11 +250,9 @@ internal class ChildFactory @Inject constructor(
is AppRoute.QrScanning,
is AppRoute.ReferralProgram,
is AppRoute.ResetToFactory,
is AppRoute.Swap,
is AppRoute.Wallet,
is AppRoute.WalletConnectSessions,
is AppRoute.CurrencyDetails,
is AppRoute.Staking,
is AppRoute.PushNotification,
-> error("Unsupported route: $route")
}
@ -319,7 +341,16 @@ internal class ChildFactory @Inject constructor(
route.asFragmentChild(Provider { ResetCardFragment() })
}
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 -> {
route.asFragmentChild(Provider { walletRouter.getEntryFragment() })
@ -337,7 +368,15 @@ internal class ChildFactory @Inject constructor(
Child.LegacyIntent(testerRouter.getEntryIntent())
}
is AppRoute.Staking -> {
route.asFragmentChild(Provider { stakingRouter.getEntryFragment() })
route.asComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = StakingComponent.Params(
userWalletId = route.userWalletId,
cryptoCurrencyId = route.cryptoCurrencyId,
yield = route.yield,
),
componentFactory = stakingComponentFactory,
)
}
is AppRoute.PushNotification -> {
route.asFragmentChild(Provider { pushNotificationRouter.entryFragment() })

View file

@ -257,17 +257,7 @@ sealed class AppRoute(val path: String) : Route {
val userWalletId: UserWalletId,
val cryptoCurrencyId: CryptoCurrency.ID,
val yield: Yield,
) : AppRoute(path = "/staking/${userWalletId.stringValue}/${cryptoCurrencyId.value}/${yield.id}"),
RouteBundleParams {
override fun getBundle(): Bundle = bundle(serializer())
companion object {
const val USER_WALLET_ID_KEY = "userWalletId"
const val CRYPTO_CURRENCY_ID_KEY = "cryptoCurrencyId"
const val YIELD_KEY = "yield"
}
}
) : AppRoute(path = "/staking/${userWalletId.stringValue}/${cryptoCurrencyId.value}/${yield.id}")
@Serializable
data object PushNotification : AppRoute(path = "/push_notification")

View file

@ -10,6 +10,14 @@ android {
}
dependencies {
/** Core */
implementation(projects.core.decompose)
implementation(projects.core.ui)
/** Domain models */
implementation(projects.domain.staking.models)
implementation(projects.domain.tokens.models)
implementation(projects.domain.wallets.models)
/** AndroidX */
implementation(deps.androidx.fragment.ktx)

View file

@ -0,0 +1,18 @@
package com.tangem.features.staking.api
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
interface StakingComponent : ComposableContentComponent {
data class Params(
val userWalletId: UserWalletId,
val cryptoCurrencyId: CryptoCurrency.ID,
val yield: Yield,
)
interface Factory : ComponentFactory<Params, StakingComponent>
}

View file

@ -1,8 +0,0 @@
package com.tangem.features.staking.api.navigation
import androidx.fragment.app.Fragment
interface StakingRouter {
fun getEntryFragment(): Fragment
}

View file

@ -46,6 +46,7 @@ dependencies {
implementation(projects.core.navigation)
implementation(projects.core.analytics)
implementation(projects.core.analytics.models)
implementation(projects.core.decompose)
/** Domain */

View file

@ -0,0 +1,33 @@
package com.tangem.features.staking.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.model.getOrCreateModel
import com.tangem.features.staking.api.StakingComponent
import com.tangem.features.staking.impl.presentation.model.StakingModel
import com.tangem.features.staking.impl.presentation.ui.StakingScreen
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
internal class DefaultStakingComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@Assisted params: StakingComponent.Params,
) : StakingComponent, AppComponentContext by appComponentContext {
private val model: StakingModel = getOrCreateModel(params)
@Composable
override fun Content(modifier: Modifier) {
val currentState by model.uiState.collectAsStateWithLifecycle()
StakingScreen(currentState)
}
@AssistedFactory
interface Factory : StakingComponent.Factory {
override fun create(context: AppComponentContext, params: StakingComponent.Params): DefaultStakingComponent
}
}

View file

@ -0,0 +1,38 @@
package com.tangem.features.staking.impl.di
import com.tangem.core.decompose.di.ComponentScoped
import com.tangem.core.decompose.di.DecomposeComponent
import com.tangem.core.decompose.model.Model
import com.tangem.features.staking.api.StakingComponent
import com.tangem.features.staking.impl.DefaultStakingComponent
import com.tangem.features.staking.impl.navigation.DefaultStakingRouter
import com.tangem.features.staking.impl.navigation.InnerStakingRouter
import com.tangem.features.staking.impl.presentation.model.StakingModel
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
@Module
@InstallIn(SingletonComponent::class)
internal interface StakingModule {
@Binds
fun bindComponentFactory(factory: DefaultStakingComponent.Factory): StakingComponent.Factory
@Binds
@IntoMap
@ClassKey(StakingModel::class)
fun bindModel(model: StakingModel): Model
}
@Module
@InstallIn(DecomposeComponent::class)
internal interface StakingComponentModule {
@Binds
@ComponentScoped
fun bindRouter(impl: DefaultStakingRouter): InnerStakingRouter
}

View file

@ -1,28 +0,0 @@
package com.tangem.features.staking.impl.di
import com.tangem.common.routing.AppRouter
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.features.staking.api.navigation.StakingRouter
import com.tangem.features.staking.impl.navigation.DefaultStakingRouter
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityComponent
import dagger.hilt.android.scopes.ActivityScoped
/**
* DI module provides implementation of [StakingRouter]
*/
@Module
@InstallIn(ActivityComponent::class)
internal object StakingRouterModule {
@Provides
@ActivityScoped
fun provideStakingRouter(urlOpener: UrlOpener, router: AppRouter): StakingRouter {
return DefaultStakingRouter(
urlOpener = urlOpener,
router = router,
)
}
}

View file

@ -1,18 +1,18 @@
package com.tangem.features.staking.impl.navigation
import androidx.fragment.app.Fragment
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.core.decompose.di.ComponentScoped
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.staking.impl.presentation.StakingFragment
import javax.inject.Inject
internal class DefaultStakingRouter(
@ComponentScoped
internal class DefaultStakingRouter @Inject constructor(
private val urlOpener: UrlOpener,
private val router: AppRouter,
) : InnerStakingRouter {
override fun getEntryFragment(): Fragment = StakingFragment.create()
override fun openUrl(url: String) {
urlOpener.openUrl(url)

View file

@ -2,9 +2,8 @@ package com.tangem.features.staking.impl.navigation
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.staking.api.navigation.StakingRouter
interface InnerStakingRouter : StakingRouter {
internal interface InnerStakingRouter {
fun openUrl(url: String)

View file

@ -1,77 +0,0 @@
package com.tangem.features.staking.impl.presentation
import android.os.Bundle
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.fragment.app.viewModels
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.features.staking.api.navigation.StakingRouter
import com.tangem.features.staking.impl.navigation.InnerStakingRouter
import com.tangem.features.staking.impl.presentation.state.StakingStateController
import com.tangem.features.staking.impl.presentation.state.StakingStateRouter
import com.tangem.features.staking.impl.presentation.ui.StakingScreen
import com.tangem.features.staking.impl.presentation.viewmodel.StakingViewModel
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
/**
* Staking fragment
*/
@AndroidEntryPoint
internal class StakingFragment : ComposeFragment() {
@Inject
override lateinit var uiDependencies: UiDependencies
@Inject
lateinit var stakingRouter: StakingRouter
@Inject
lateinit var appRouter: AppRouter
@Inject
lateinit var stateController: StakingStateController
@Inject
lateinit var analyticsEventsHandler: AnalyticsEventHandler
private val viewModel by viewModels<StakingViewModel>()
private val innerStakingRouter: InnerStakingRouter
get() = requireNotNull(stakingRouter as? InnerStakingRouter) {
"innerStakingRouter should be instance of InnerStakingRouter"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycle.addObserver(viewModel)
viewModel.setRouter(
innerStakingRouter,
StakingStateRouter(
appRouter = appRouter,
stateController = stateController,
analyticsEventsHandler = analyticsEventsHandler,
),
)
}
@Composable
override fun ScreenContent(modifier: Modifier) {
val currentState = viewModel.uiState.collectAsStateWithLifecycle()
StakingScreen(currentState.value)
}
override fun onDestroy() {
lifecycle.removeObserver(viewModel)
super.onDestroy()
}
companion object {
/** Create staking fragment instance */
fun create(): StakingFragment = StakingFragment()
}
}

View file

@ -1,4 +1,4 @@
package com.tangem.features.staking.impl.presentation.viewmodel
package com.tangem.features.staking.impl.presentation.model
import com.tangem.common.ui.amountScreen.AmountScreenClickIntents
import com.tangem.common.ui.bottomsheet.permission.state.ApproveType

View file

@ -1,14 +1,9 @@
package com.tangem.features.staking.impl.presentation.viewmodel
package com.tangem.features.staking.impl.presentation.model
import android.os.Bundle
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.compose.runtime.Stable
import arrow.core.getOrElse
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.bundle.unbundle
import com.tangem.common.routing.AppRouter
import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
@ -17,6 +12,9 @@ import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionBottomS
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.api.ParamsInterceptorHolder
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.share.ShareManager
import com.tangem.core.ui.haptic.TangemHapticEffect
import com.tangem.core.ui.haptic.VibratorHapticManager
@ -52,6 +50,7 @@ import com.tangem.domain.utils.convertToSdkAmount
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.features.staking.api.StakingComponent
import com.tangem.features.staking.impl.analytics.StakingParamsInterceptor
import com.tangem.features.staking.impl.analytics.utils.StakingAnalyticSender
import com.tangem.features.staking.impl.navigation.InnerStakingRouter
@ -77,7 +76,6 @@ import com.tangem.utils.Provider
import com.tangem.utils.coroutines.*
import com.tangem.utils.extensions.isSingleItem
import com.tangem.utils.extensions.orZero
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.CoroutineScope
@ -89,11 +87,13 @@ import java.util.concurrent.CopyOnWriteArrayList
import javax.inject.Inject
import kotlin.properties.Delegates
@Suppress("LargeClass", "LongParameterList", "TooManyFunctions")
@HiltViewModel
internal class StakingViewModel @Inject constructor(
@Suppress("LargeClass", "TooManyFunctions", "LongParameterList")
@Stable
@ComponentScoped
internal class StakingModel @Inject constructor(
paramsContainer: ParamsContainer,
private val stateController: StakingStateController,
private val dispatchers: CoroutineDispatcherProvider,
override val dispatchers: CoroutineDispatcherProvider,
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
private val getCurrencyStatusUpdatesUseCase: GetCurrencyStatusUpdatesUseCase,
private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase,
@ -122,33 +122,30 @@ internal class StakingViewModel @Inject constructor(
private val paramsInterceptorHolder: ParamsInterceptorHolder,
private val shareManager: ShareManager,
@DelayedWork private val coroutineScope: CoroutineScope,
savedStateHandle: SavedStateHandle,
) : ViewModel(), DefaultLifecycleObserver, StakingClickIntents {
private val innerRouter: InnerStakingRouter,
private val appRouter: AppRouter,
) : Model(), StakingClickIntents {
val uiState: StateFlow<StakingUiState> = stateController.uiState
val value: StakingUiState get() = uiState.value
private var stakingStateRouter: StakingStateRouter by Delegates.notNull()
private val params = paramsContainer.require<StakingComponent.Params>()
private val cryptoCurrencyId: CryptoCurrency.ID =
savedStateHandle.get<Bundle>(AppRoute.Staking.CRYPTO_CURRENCY_ID_KEY)
?.unbundle(CryptoCurrency.ID.serializer())
?: error("This screen can't be opened without `CryptoCurrency.ID`")
private var stakingStateRouter: StakingStateRouter = StakingStateRouter(
appRouter = appRouter,
stateController = stateController,
analyticsEventsHandler = analyticsEventHandler,
)
private val userWalletId: UserWalletId = savedStateHandle.get<Bundle>(AppRoute.Staking.USER_WALLET_ID_KEY)
?.unbundle(UserWalletId.serializer())
?: error("This screen can't be opened without `UserWalletId`")
private val yield: Yield = savedStateHandle.get<Bundle>(AppRoute.Staking.YIELD_KEY)
?.unbundle(Yield.serializer())
?: error("This screen can't be opened without `Yield`")
private val cryptoCurrencyId: CryptoCurrency.ID = params.cryptoCurrencyId
private val userWalletId: UserWalletId = params.userWalletId
private val yield: Yield = params.yield
private var cryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull()
private var processingActions: List<StakingAction> = emptyList()
private var feeCryptoCurrencyStatus: CryptoCurrencyStatus? = null
private var minimumTransactionAmount: EnterAmountBoundary? = null
private var innerRouter: InnerStakingRouter by Delegates.notNull()
private var userWallet: UserWallet by Delegates.notNull()
private var appCurrency: AppCurrency by Delegates.notNull()
@ -218,8 +215,8 @@ internal class StakingViewModel @Inject constructor(
subscribeOnCurrencyStatusUpdates()
}
override fun onCleared() {
super.onCleared()
override fun onDestroy() {
super.onDestroy()
paramsInterceptorHolder.removeParamsInterceptor(StakingParamsInterceptor.ID)
approvalJobHolder.cancel()
feeJobHolder.cancel()
@ -266,7 +263,7 @@ internal class StakingViewModel @Inject constructor(
cryptoCurrency = cryptoCurrencyStatus.currency,
),
)
viewModelScope.launch {
modelScope.launch {
feeLoader.getFee(
onStakingFee = { gasEstimate ->
stateController.update(
@ -303,7 +300,7 @@ internal class StakingViewModel @Inject constructor(
override fun onActionClick() {
if (isAssentState()) {
viewModelScope.launch {
modelScope.launch {
stakingAnalyticSender.sendTransactionStakingClickedAnalytics(value)
stateController.update(SetConfirmationStateInProgressTransformer())
transactionSender.constructAndSendTransactions(
@ -546,7 +543,7 @@ internal class StakingViewModel @Inject constructor(
}
override fun onApprovalClick() {
viewModelScope.launch {
modelScope.launch {
stateController.update(
SetApprovalBottomSheetInProgressTransformer {
stateController.update(DismissBottomSheetStateTransformer)
@ -619,7 +616,7 @@ internal class StakingViewModel @Inject constructor(
}
private fun updateNotifications(feeError: GetFeeError? = null) {
viewModelScope.launch {
modelScope.launch {
val confirmationState = value.confirmationState as? StakingStates.ConfirmationState.Data
val feeState = confirmationState?.feeState as? FeeState.Content
val amountState = value.amountState as? AmountState.Data
@ -733,7 +730,7 @@ internal class StakingViewModel @Inject constructor(
private fun awaitForAllowance() {
val approval = stakingApproval as? StakingApproval.Needed ?: return
allowanceTaskScheduler.scheduleTask(
scope = viewModelScope,
scope = modelScope,
task = PeriodicTask(
delay = ALLOWANCE_UPDATE_DELAY,
task = {
@ -783,7 +780,7 @@ internal class StakingViewModel @Inject constructor(
}
override fun onFailedTxEmailClick(errorMessage: String) {
viewModelScope.launch {
modelScope.launch {
val network = cryptoCurrencyStatus.currency.network
val cardInfo = getCardInfoUseCase(userWallet.scanResponse).getOrElse { error("CardInfo must be not null") }
@ -822,11 +819,6 @@ internal class StakingViewModel @Inject constructor(
innerRouter.openTokenDetails(userWalletId, cryptoCurrency)
}
fun setRouter(router: InnerStakingRouter, stateRouter: StakingStateRouter) {
innerRouter = router
this.stakingStateRouter = stateRouter
}
private suspend fun setupApprovalNeeded() {
stakingApproval = isApproveNeededUseCase(cryptoCurrencyStatus.currency).fold(
ifRight = { approval ->
@ -903,7 +895,7 @@ internal class StakingViewModel @Inject constructor(
)
}
.flowOn(dispatchers.main)
.launchIn(viewModelScope)
.launchIn(modelScope)
}
private fun subscribeOnBalanceHiding() {
@ -914,7 +906,7 @@ internal class StakingViewModel @Inject constructor(
stateController.update(transformer = HideBalanceStateTransformer(it.isBalanceHidden))
}
.flowOn(dispatchers.main)
.launchIn(viewModelScope)
.launchIn(modelScope)
}
private fun subscribeOnSelectedAppCurrency() {
@ -925,7 +917,7 @@ internal class StakingViewModel @Inject constructor(
appCurrency = maybeAppCurrency.getOrElse { AppCurrency.Default }
}
.flowOn(dispatchers.main)
.launchIn(viewModelScope)
.launchIn(modelScope)
}
private fun subscribeOnStepChanges() {
@ -951,7 +943,7 @@ internal class StakingViewModel @Inject constructor(
}
}
.flowOn(dispatchers.main)
.launchIn(viewModelScope)
.launchIn(modelScope)
.saveIn(stepChangesJobHolder)
}
@ -971,13 +963,13 @@ internal class StakingViewModel @Inject constructor(
}
}
.flowOn(dispatchers.main)
.launchIn(viewModelScope)
.launchIn(modelScope)
}
private fun updateInitialData() {
stateController.updateAll(
SetInitialDataStateTransformer(
clickIntents = this@StakingViewModel,
clickIntents = this@StakingModel,
yield = yield,
isAnyTokenStaked = isAnyTokenStaked,
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },

View file

@ -14,7 +14,7 @@ import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType
import com.tangem.features.staking.impl.presentation.state.events.StakingEvent
import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents
import com.tangem.features.staking.impl.presentation.model.StakingClickIntents
import kotlinx.collections.immutable.ImmutableList
import java.math.BigDecimal

View file

@ -6,7 +6,7 @@ import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.features.staking.impl.presentation.state.BalanceState
import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType
import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents
import com.tangem.features.staking.impl.presentation.model.StakingClickIntents
import java.math.BigDecimal
@Suppress("TooManyFunctions")

View file

@ -12,7 +12,7 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.StakingUiState
import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents
import com.tangem.features.staking.impl.presentation.model.StakingClickIntents
import com.tangem.utils.Provider
import com.tangem.utils.transformer.Transformer

View file

@ -23,7 +23,7 @@ import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType
import com.tangem.features.staking.impl.presentation.state.converters.RewardsValidatorStateConverter
import com.tangem.features.staking.impl.presentation.state.converters.YieldBalancesConverter
import com.tangem.features.staking.impl.presentation.state.utils.getRewardScheduleText
import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents
import com.tangem.features.staking.impl.presentation.model.StakingClickIntents
import com.tangem.lib.crypto.BlockchainUtils.isPolkadot
import com.tangem.utils.Provider
import com.tangem.utils.isNullOrZero

View file

@ -17,7 +17,7 @@ import com.tangem.core.ui.format.bigdecimal.percent
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.StakingStates
import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents
import com.tangem.features.staking.impl.presentation.model.StakingClickIntents
import com.tangem.utils.extensions.orZero
@Composable

View file

@ -29,7 +29,7 @@ import com.tangem.features.staking.impl.presentation.state.stub.StakingClickInte
import com.tangem.features.staking.impl.presentation.ui.block.NotificationsBlock
import com.tangem.features.staking.impl.presentation.ui.block.StakingFeeBlock
import com.tangem.features.staking.impl.presentation.ui.block.ValidatorBlock
import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents
import com.tangem.features.staking.impl.presentation.model.StakingClickIntents
@Suppress("LongParameterList")
@Composable

View file

@ -50,7 +50,7 @@ import com.tangem.features.staking.impl.presentation.state.InnerYieldBalanceStat
import com.tangem.features.staking.impl.presentation.state.StakingStates
import com.tangem.features.staking.impl.presentation.state.previewdata.InitialStakingStatePreview
import com.tangem.features.staking.impl.presentation.state.stub.StakingClickIntentsStub
import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents
import com.tangem.features.staking.impl.presentation.model.StakingClickIntents
import com.tangem.utils.StringsSigns.DOT
import com.tangem.utils.extensions.orZero

View file

@ -31,7 +31,7 @@ import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.StakingStates
import com.tangem.features.staking.impl.presentation.state.previewdata.ValidatorStatePreviewData
import com.tangem.features.staking.impl.presentation.state.stub.StakingClickIntentsStub
import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents
import com.tangem.features.staking.impl.presentation.model.StakingClickIntents
import com.tangem.utils.extensions.orZero
/**

View file

@ -1,8 +1,19 @@
package com.tangem.feature.swap
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.background
import androidx.compose.runtime.Composable
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.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 dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@ -14,9 +25,50 @@ internal class DefaultSwapComponent @AssistedInject constructor(
@Assisted params: SwapComponent.Params,
) : SwapComponent, AppComponentContext by appComponentContext {
private val model: SwapModel = getOrCreateModel(params)
init {
lifecycle.subscribe(
onStart = model::onStart,
onStop = model::onStop,
)
}
@Composable
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

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