Updated on 2026-08-14
This commit is contained in:
commit
6942cb3171
32 changed files with 323 additions and 353 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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() })
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
package com.tangem.features.staking.api.navigation
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
|
||||
interface StakingRouter {
|
||||
|
||||
fun getEntryFragment(): Fragment
|
||||
}
|
||||
|
|
@ -46,6 +46,7 @@ dependencies {
|
|||
implementation(projects.core.navigation)
|
||||
implementation(projects.core.analytics)
|
||||
implementation(projects.core.analytics.models)
|
||||
implementation(projects.core.decompose)
|
||||
|
||||
|
||||
/** Domain */
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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 },
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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.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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue