Updated on 2026-08-14
This commit is contained in:
commit
bb28c0a7e3
100 changed files with 6093 additions and 46 deletions
|
|
@ -91,4 +91,16 @@ internal object SettingsDomainModule {
|
|||
): UpdateBalanceHidingSettingsUseCase {
|
||||
return UpdateBalanceHidingSettingsUseCase(balanceHidingRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
fun provideSetWalletsScrollPreviewIsShown(settingsRepository: SettingsRepository): NeverToShowWalletsScrollPreview {
|
||||
return NeverToShowWalletsScrollPreview(settingsRepository = settingsRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
fun provideIsWalletsScrollPreviewEnabled(settingsRepository: SettingsRepository): IsWalletsScrollPreviewEnabled {
|
||||
return IsWalletsScrollPreviewEnabled(settingsRepository = settingsRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -20,6 +20,8 @@ import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
|
|||
import com.tangem.domain.tokens.TokenWithBlockchain
|
||||
import com.tangem.domain.tokens.TokensAction
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.walletconnect.WalletConnectActions
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||
import com.tangem.tap.*
|
||||
|
|
@ -90,13 +92,17 @@ object TokensMiddleware {
|
|||
if (scanResponse.supportsHdWallet()) {
|
||||
deriveMissingCoins(scanResponse = scanResponse, currencyList = currencyList) {
|
||||
submitNewAdd(
|
||||
userWalletId = action.userWallet.walletId,
|
||||
userWallet = action.userWallet,
|
||||
updatedScanResponse = it,
|
||||
currencyList = currencyList,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
submitNewAdd(userWalletId = action.userWallet.walletId, scanResponse, currencyList = currencyList)
|
||||
submitNewAdd(
|
||||
userWallet = action.userWallet,
|
||||
updatedScanResponse = scanResponse,
|
||||
currencyList = currencyList,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -375,16 +381,18 @@ object TokensMiddleware {
|
|||
}
|
||||
|
||||
private fun submitNewAdd(
|
||||
userWalletId: UserWalletId,
|
||||
userWallet: UserWallet,
|
||||
updatedScanResponse: ScanResponse,
|
||||
currencyList: List<CryptoCurrency>,
|
||||
) {
|
||||
scope.launch {
|
||||
userWalletsListManager.update(
|
||||
userWalletId = userWalletId,
|
||||
userWalletId = userWallet.walletId,
|
||||
update = { it.copy(scanResponse = updatedScanResponse) },
|
||||
).doOnSuccess {
|
||||
addCryptoCurrenciesUseCase(userWalletId, currencyList)
|
||||
addCryptoCurrenciesUseCase(userWallet.walletId, currencyList).onRight {
|
||||
store.dispatch(action = WalletConnectActions.New.SetupUserChains(userWallet = userWallet))
|
||||
}
|
||||
}
|
||||
}
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo())
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ object PreferencesKeys {
|
|||
val BALANCE_HIDING_SETTINGS_KEY by lazy { stringPreferencesKey(name = "balanceHidingSettings") }
|
||||
|
||||
val SWAP_TRANSACTIONS_KEY by lazy { stringPreferencesKey(name = "swapTransactions") }
|
||||
|
||||
val WALLETS_SCROLL_PREVIEW_KEY by lazy { booleanPreferencesKey(name = "walletsScrollPreview") }
|
||||
}
|
||||
|
||||
/** Preferences keys set that should be migrated from "PreferencesDataSource" to a new DataStore<Preferences> */
|
||||
|
|
|
|||
|
|
@ -34,5 +34,9 @@
|
|||
{
|
||||
"name": "REDESIGNED_SEND_SCREEN_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "WALLETS_SCROLLING_PREVIEW_ENABLED",
|
||||
"version": "5.4.0"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.core.ui.event
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.NonRestartableComposable
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* A Composable function that reacts to a given [StateEvent], executing the provided action only once when the event
|
||||
|
|
@ -17,8 +18,8 @@ import androidx.compose.runtime.NonRestartableComposable
|
|||
fun <A> EventEffect(event: StateEvent<A>, onTrigger: suspend (data: A) -> Unit) {
|
||||
LaunchedEffect(event) {
|
||||
if (event is StateEvent.Triggered<A>) {
|
||||
onTrigger(event.data)
|
||||
event.onConsume()
|
||||
launch { onTrigger(event.data) }
|
||||
.invokeOnCompletion { event.onConsume() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ package com.tangem.data.card
|
|||
import com.tangem.datasource.local.card.UsedCardInfo
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getObject
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectList
|
||||
import com.tangem.domain.card.repository.CardRepository
|
||||
import com.tangem.utils.extensions.addOrReplace
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
|
@ -14,7 +14,7 @@ internal class DefaultCardRepository(
|
|||
) : CardRepository {
|
||||
|
||||
override fun wasCardScanned(cardId: String): Flow<Boolean> {
|
||||
return appPreferencesStore.getObject<List<UsedCardInfo>>(key = PreferencesKeys.USED_CARDS_INFO_KEY)
|
||||
return appPreferencesStore.getObjectList<UsedCardInfo>(key = PreferencesKeys.USED_CARDS_INFO_KEY)
|
||||
.map { savedCards ->
|
||||
savedCards?.any { it.cardId == cardId } ?: false
|
||||
}
|
||||
|
|
@ -22,14 +22,14 @@ internal class DefaultCardRepository(
|
|||
|
||||
override suspend fun setCardWasScanned(cardId: String) {
|
||||
appPreferencesStore.editData { mutablePreferences ->
|
||||
val usedCards: List<UsedCardInfo>? = mutablePreferences.getObject(
|
||||
val usedCards: List<UsedCardInfo>? = mutablePreferences.getObjectList(
|
||||
key = PreferencesKeys.USED_CARDS_INFO_KEY,
|
||||
)
|
||||
|
||||
val updatedUsedCards = usedCards?.updateCard(cardId)
|
||||
?: listOf(UsedCardInfo(cardId = cardId, isScanned = true))
|
||||
|
||||
mutablePreferences.setObject(
|
||||
mutablePreferences.setObjectList(
|
||||
key = PreferencesKeys.USED_CARDS_INFO_KEY,
|
||||
value = updatedUsedCards,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,16 +1,35 @@
|
|||
package com.tangem.data.settings
|
||||
|
||||
import com.tangem.data.source.preferences.PreferencesDataSource
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getSyncOrDefault
|
||||
import com.tangem.datasource.local.preferences.utils.store
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
internal class DefaultSettingsRepository(
|
||||
private val preferencesDataSource: PreferencesDataSource,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : SettingsRepository {
|
||||
|
||||
override suspend fun shouldShowSaveUserWalletScreen(): Boolean {
|
||||
return withContext(dispatchers.io) { preferencesDataSource.shouldShowSaveUserWalletScreen }
|
||||
}
|
||||
|
||||
override suspend fun isWalletScrollPreviewEnabled(): Boolean {
|
||||
return appPreferencesStore.getSyncOrDefault(
|
||||
key = PreferencesKeys.WALLETS_SCROLL_PREVIEW_KEY,
|
||||
default = true,
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun setWalletScrollPreviewAvailability(isEnabled: Boolean) {
|
||||
appPreferencesStore.store(
|
||||
key = PreferencesKeys.WALLETS_SCROLL_PREVIEW_KEY,
|
||||
value = isEnabled,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -21,10 +21,12 @@ internal object SettingsDataModule {
|
|||
@Singleton
|
||||
fun provideSettingsRepository(
|
||||
preferencesDataSource: PreferencesDataSource,
|
||||
appPreferencesStore: AppPreferencesStore,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): SettingsRepository {
|
||||
return DefaultSettingsRepository(
|
||||
preferencesDataSource = preferencesDataSource,
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.domain.settings
|
||||
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
|
||||
/**
|
||||
* Checks if wallets scroll preview is enabled
|
||||
*
|
||||
* @property settingsRepository settings repository
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class IsWalletsScrollPreviewEnabled(private val settingsRepository: SettingsRepository) {
|
||||
|
||||
suspend operator fun invoke(): Boolean = settingsRepository.isWalletScrollPreviewEnabled()
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.domain.settings
|
||||
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
|
||||
/**
|
||||
* Never to show wallets scroll preview
|
||||
*
|
||||
* @property settingsRepository settings repository
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class NeverToShowWalletsScrollPreview(
|
||||
private val settingsRepository: SettingsRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke() = settingsRepository.setWalletScrollPreviewAvailability(isEnabled = false)
|
||||
}
|
||||
|
|
@ -3,4 +3,8 @@ package com.tangem.domain.settings.repositories
|
|||
interface SettingsRepository {
|
||||
|
||||
suspend fun shouldShowSaveUserWalletScreen(): Boolean
|
||||
|
||||
suspend fun isWalletScrollPreviewEnabled(): Boolean
|
||||
|
||||
suspend fun setWalletScrollPreviewAvailability(isEnabled: Boolean)
|
||||
}
|
||||
|
|
@ -32,10 +32,7 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
) {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
suspend operator fun invoke(
|
||||
userWallet: UserWallet,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
): Flow<TokenActionsState> {
|
||||
operator fun invoke(userWallet: UserWallet, cryptoCurrencyStatus: CryptoCurrencyStatus): Flow<TokenActionsState> {
|
||||
val operations = CurrenciesStatusesOperations(
|
||||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
|
|
@ -43,19 +40,25 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
userWalletId = userWallet.walletId,
|
||||
)
|
||||
val networkId = cryptoCurrencyStatus.currency.network.id
|
||||
val networkFlow = if (userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()) {
|
||||
operations.getNetworkCoinForSingleWalletWithTokenFlow(networkId)
|
||||
} else if (!userWallet.isMultiCurrency) {
|
||||
operations.getPrimaryCurrencyStatusFlow()
|
||||
} else {
|
||||
operations.getNetworkCoinFlow(networkId, cryptoCurrencyStatus.currency.network.derivationPath)
|
||||
}
|
||||
return networkFlow.mapLatest { maybeCoinStatus ->
|
||||
createTokenActionsState(
|
||||
userWalletId = userWallet.walletId,
|
||||
coinStatus = maybeCoinStatus.getOrNull(),
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
)
|
||||
|
||||
return flow {
|
||||
val networkFlow = if (userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()) {
|
||||
operations.getNetworkCoinForSingleWalletWithTokenFlow(networkId)
|
||||
} else if (!userWallet.isMultiCurrency) {
|
||||
operations.getPrimaryCurrencyStatusFlow()
|
||||
} else {
|
||||
operations.getNetworkCoinFlow(networkId, cryptoCurrencyStatus.currency.network.derivationPath)
|
||||
}
|
||||
|
||||
val flow = networkFlow.mapLatest { maybeCoinStatus ->
|
||||
createTokenActionsState(
|
||||
userWalletId = userWallet.walletId,
|
||||
coinStatus = maybeCoinStatus.getOrNull(),
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
)
|
||||
}
|
||||
|
||||
emitAll(flow)
|
||||
}.flowOn(dispatchers.io)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,12 +2,13 @@ package com.tangem.domain.wallets.usecase
|
|||
|
||||
import arrow.core.Either
|
||||
import arrow.core.raise.either
|
||||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnSuccess
|
||||
import arrow.core.right
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.wallets.legacy.WalletsStateHolder
|
||||
import com.tangem.domain.wallets.legacy.ensureUserWalletListManagerNotNull
|
||||
import com.tangem.domain.wallets.models.SelectWalletError
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
/**
|
||||
|
|
@ -22,16 +23,20 @@ class SelectWalletUseCase(
|
|||
private val reduxStateHolder: ReduxStateHolder,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(userWalletId: UserWalletId): Either<SelectWalletError, Unit> {
|
||||
suspend operator fun invoke(userWalletId: UserWalletId): Either<SelectWalletError, UserWallet> {
|
||||
return either {
|
||||
val userWalletsListManager = ensureUserWalletListManagerNotNull(
|
||||
walletsStateHolder = walletsStateHolder,
|
||||
raise = { SelectWalletError.DataError },
|
||||
)
|
||||
|
||||
userWalletsListManager.select(userWalletId)
|
||||
.doOnFailure { raise(SelectWalletError.UnableToSelectUserWallet) }
|
||||
.doOnSuccess { reduxStateHolder.onUserWalletSelected(it) }
|
||||
return when (val result = userWalletsListManager.select(userWalletId)) {
|
||||
is CompletionResult.Failure -> raise(SelectWalletError.UnableToSelectUserWallet)
|
||||
is CompletionResult.Success -> {
|
||||
reduxStateHolder.onUserWalletSelected(result.data)
|
||||
result.data.right()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,4 +9,6 @@ interface WalletFeatureToggles {
|
|||
|
||||
/** Availability of redesigned screen */
|
||||
val isRedesignedScreenEnabled: Boolean
|
||||
|
||||
val isWalletsScrollingPreviewEnabled: Boolean
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.feature.wallet.di
|
|||
|
||||
import com.tangem.core.navigation.ReduxNavController
|
||||
import com.tangem.feature.wallet.presentation.router.DefaultWalletRouter
|
||||
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
|
||||
import com.tangem.features.wallet.navigation.WalletRouter
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -15,7 +16,10 @@ internal object WalletRouterModule {
|
|||
|
||||
@Provides
|
||||
@ActivityScoped
|
||||
fun provideWalletRouter(reduxNavController: ReduxNavController): WalletRouter {
|
||||
return DefaultWalletRouter(reduxNavController = reduxNavController)
|
||||
fun provideWalletRouter(
|
||||
reduxNavController: ReduxNavController,
|
||||
walletFeatureToggles: WalletFeatureToggles,
|
||||
): WalletRouter {
|
||||
return DefaultWalletRouter(reduxNavController = reduxNavController, walletFeatureToggles = walletFeatureToggles)
|
||||
}
|
||||
}
|
||||
|
|
@ -16,4 +16,7 @@ internal class DefaultWalletFeatureToggles(
|
|||
|
||||
override val isRedesignedScreenEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(name = "REDESIGNED_WALLET_SCREEN_ENABLED")
|
||||
|
||||
override val isWalletsScrollingPreviewEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(name = "WALLETS_SCROLLING_PREVIEW_ENABLED")
|
||||
}
|
||||
|
|
@ -26,12 +26,18 @@ import com.tangem.feature.wallet.presentation.WalletFragment
|
|||
import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensScreen
|
||||
import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensViewModel
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.WalletScreen
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.WalletScreenV2
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletViewModel
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletViewModelV2
|
||||
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
|
||||
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
/** Default implementation of wallet feature router */
|
||||
internal class DefaultWalletRouter(private val reduxNavController: ReduxNavController) : InnerWalletRouter {
|
||||
internal class DefaultWalletRouter(
|
||||
private val reduxNavController: ReduxNavController,
|
||||
private val walletFeatureToggles: WalletFeatureToggles,
|
||||
) : InnerWalletRouter {
|
||||
|
||||
private var navController: NavHostController by Delegates.notNull()
|
||||
private var onFinish: () -> Unit = {}
|
||||
|
|
@ -47,10 +53,19 @@ internal class DefaultWalletRouter(private val reduxNavController: ReduxNavContr
|
|||
startDestination = WalletRoute.Wallet.route,
|
||||
) {
|
||||
composable(WalletRoute.Wallet.route) {
|
||||
val viewModel = hiltViewModel<WalletViewModel>().apply { router = this@DefaultWalletRouter }
|
||||
LocalLifecycleOwner.current.lifecycle.addObserver(viewModel)
|
||||
if (walletFeatureToggles.isWalletsScrollingPreviewEnabled) {
|
||||
val viewModel = hiltViewModel<WalletViewModel>().apply {
|
||||
router = this@DefaultWalletRouter
|
||||
}
|
||||
|
||||
WalletScreen(state = viewModel.uiState)
|
||||
WalletScreen(state = viewModel.uiState)
|
||||
} else {
|
||||
val viewModel = hiltViewModel<WalletViewModelV2>().apply {
|
||||
setWalletRouter(router = this@DefaultWalletRouter)
|
||||
}
|
||||
|
||||
WalletScreenV2(state = viewModel.uiState.collectAsStateWithLifecycle().value)
|
||||
}
|
||||
}
|
||||
|
||||
composable(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.analytics.utils
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.NetworkGroup
|
||||
import com.tangem.domain.tokens.model.TokenList
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
|
||||
import dagger.hilt.android.scopes.ViewModelScoped
|
||||
import java.math.BigDecimal
|
||||
import javax.inject.Inject
|
||||
|
||||
@ViewModelScoped
|
||||
internal class TokenListAnalyticsSender @Inject constructor(
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
) {
|
||||
|
||||
fun send(maybeTokenList: Either<TokenListError, TokenList>) {
|
||||
val tokenList = (maybeTokenList as? Either.Right)?.value ?: return
|
||||
|
||||
createCardBalanceState(tokenList)?.let {
|
||||
analyticsEventHandler.send(event = WalletScreenAnalyticsEvent.Basic.BalanceLoaded(balance = it))
|
||||
}
|
||||
}
|
||||
|
||||
private fun createCardBalanceState(tokenList: TokenList): AnalyticsParam.CardBalanceState? {
|
||||
return when (val fiatBalance = tokenList.totalFiatBalance) {
|
||||
is TokenList.FiatBalance.Failed -> fiatBalance.toCardBalanceState(tokenList)
|
||||
is TokenList.FiatBalance.Loaded -> fiatBalance.toCardBalanceState()
|
||||
TokenList.FiatBalance.Loading -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun TokenList.FiatBalance.Failed.toCardBalanceState(tokenList: TokenList): AnalyticsParam.CardBalanceState {
|
||||
val currenciesStatuses = when (tokenList) {
|
||||
is TokenList.Empty -> emptyList()
|
||||
is TokenList.GroupedByNetwork -> tokenList.groups.flatMap(NetworkGroup::currencies)
|
||||
is TokenList.Ungrouped -> tokenList.currencies
|
||||
}
|
||||
|
||||
return when {
|
||||
currenciesStatuses.isEmpty() -> AnalyticsParam.CardBalanceState.Empty
|
||||
currenciesStatuses.any { it.value is CryptoCurrencyStatus.NoQuote } -> {
|
||||
AnalyticsParam.CardBalanceState.NoRate
|
||||
}
|
||||
else -> AnalyticsParam.CardBalanceState.BlockchainError
|
||||
}
|
||||
}
|
||||
|
||||
private fun TokenList.FiatBalance.Loaded.toCardBalanceState(): AnalyticsParam.CardBalanceState? {
|
||||
return if (amount > BigDecimal.ZERO) {
|
||||
AnalyticsParam.CardBalanceState.Full
|
||||
} else if (amount.isZero()) {
|
||||
AnalyticsParam.CardBalanceState.Empty
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.domain
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.common.CardTypesResolver
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.demo.IsDemoCardUseCase
|
||||
import com.tangem.domain.settings.IsReadyToShowRateAppUseCase
|
||||
import com.tangem.domain.tokens.GetTokenListUseCase
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.NetworkGroup
|
||||
import com.tangem.domain.tokens.model.TokenList
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletNotification
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
|
||||
import dagger.hilt.android.scopes.ViewModelScoped
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.conflate
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@ViewModelScoped
|
||||
internal class GetMultiWalletWarningsFactory @Inject constructor(
|
||||
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||
private val getTokenListUseCase: GetTokenListUseCase,
|
||||
private val isDemoCardUseCase: IsDemoCardUseCase,
|
||||
private val isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase,
|
||||
private val isNeedToBackupUseCase: IsNeedToBackupUseCase,
|
||||
) {
|
||||
|
||||
private var readyForRateAppNotification = false
|
||||
|
||||
fun create(clickIntents: WalletClickIntentsV2): Flow<ImmutableList<WalletNotification>> {
|
||||
val userWallet = getSelectedWalletSyncUseCase().fold(
|
||||
ifLeft = {
|
||||
Timber.e("Failed to get selected wallet $it")
|
||||
return flowOf(value = persistentListOf())
|
||||
},
|
||||
ifRight = { it },
|
||||
)
|
||||
|
||||
val cardTypesResolver = userWallet.scanResponse.cardTypesResolver
|
||||
|
||||
return combine(
|
||||
flow = getTokenListUseCase(userWallet.walletId).conflate(),
|
||||
flow2 = isReadyToShowRateAppUseCase().conflate(),
|
||||
flow3 = isNeedToBackupUseCase(userWallet.walletId).conflate(),
|
||||
// flow4 = getMissedAddressCryptoCurrenciesUseCase(userWallet.walletId).conflate(),
|
||||
) { maybeTokenList, isReadyToShowRating, isNeedToBackup ->
|
||||
// maybeTokenList.onRight { Timber.e(it.toString()) }
|
||||
// maybeMissedAddressCurrencies.onRight { Timber.e(it.toString()) }
|
||||
readyForRateAppNotification = true
|
||||
buildList {
|
||||
addCriticalNotifications(cardTypesResolver)
|
||||
|
||||
addInformationalNotifications(cardTypesResolver, maybeTokenList, clickIntents)
|
||||
|
||||
addWarningNotifications(cardTypesResolver, maybeTokenList, isNeedToBackup, clickIntents)
|
||||
|
||||
addRateTheAppNotification(isReadyToShowRating, clickIntents)
|
||||
}.toImmutableList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addCriticalNotifications(cardTypesResolver: CardTypesResolver) {
|
||||
addIf(
|
||||
element = WalletNotification.Critical.DevCard,
|
||||
condition = !cardTypesResolver.isReleaseFirmwareType(),
|
||||
)
|
||||
|
||||
addIf(
|
||||
element = WalletNotification.Critical.FailedCardValidation,
|
||||
condition = cardTypesResolver.isReleaseFirmwareType() && cardTypesResolver.isAttestationFailed(),
|
||||
)
|
||||
|
||||
cardTypesResolver.getRemainingSignatures()?.let { remainingSignatures ->
|
||||
addIf(
|
||||
element = WalletNotification.Warning.LowSignatures(count = remainingSignatures),
|
||||
condition = remainingSignatures <= MAX_REMAINING_SIGNATURES_COUNT,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addInformationalNotifications(
|
||||
cardTypesResolver: CardTypesResolver,
|
||||
maybeTokenList: Either<TokenListError, TokenList>,
|
||||
clickIntents: WalletClickIntentsV2,
|
||||
) {
|
||||
addIf(
|
||||
element = WalletNotification.Informational.DemoCard,
|
||||
condition = isDemoCardUseCase(cardId = cardTypesResolver.getCardId()),
|
||||
)
|
||||
|
||||
addMissingAddressesNotification(maybeTokenList, clickIntents)
|
||||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addMissingAddressesNotification(
|
||||
maybeTokenList: Either<TokenListError, TokenList>,
|
||||
clickIntents: WalletClickIntentsV2,
|
||||
) {
|
||||
val currencies = maybeTokenList.getMissingAddressCurrencies()
|
||||
|
||||
addIf(
|
||||
element = WalletNotification.Informational.MissingAddresses(
|
||||
missingAddressesCount = currencies.count(),
|
||||
onGenerateClick = {
|
||||
clickIntents.onGenerateMissedAddressesClick(missedAddressCurrencies = currencies)
|
||||
},
|
||||
),
|
||||
condition = currencies.isNotEmpty(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun Either<TokenListError, TokenList>.getMissingAddressCurrencies(): List<CryptoCurrency> {
|
||||
return fold(
|
||||
ifLeft = { emptyList() },
|
||||
ifRight = { tokenList ->
|
||||
val currencies = when (tokenList) {
|
||||
is TokenList.GroupedByNetwork -> tokenList.groups.flatMap(NetworkGroup::currencies)
|
||||
is TokenList.Ungrouped -> tokenList.currencies
|
||||
is TokenList.Empty -> emptyList()
|
||||
}
|
||||
|
||||
currencies
|
||||
.filter { it.value is CryptoCurrencyStatus.MissedDerivation }
|
||||
.map(CryptoCurrencyStatus::currency)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addWarningNotifications(
|
||||
cardTypesResolver: CardTypesResolver,
|
||||
tokenList: Either<TokenListError, TokenList>,
|
||||
isNeedToBackup: Boolean,
|
||||
clickIntents: WalletClickIntentsV2,
|
||||
) {
|
||||
addIf(
|
||||
element = WalletNotification.Warning.MissingBackup(
|
||||
onStartBackupClick = clickIntents::onAddBackupCardClick,
|
||||
),
|
||||
condition = isNeedToBackup,
|
||||
)
|
||||
|
||||
addIf(
|
||||
element = WalletNotification.Warning.TestNetCard,
|
||||
condition = cardTypesResolver.isTestCard(),
|
||||
)
|
||||
|
||||
addIf(
|
||||
element = WalletNotification.Warning.SomeNetworksUnreachable,
|
||||
condition = tokenList.hasUnreachableNetworks(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun Either<TokenListError, TokenList>.hasUnreachableNetworks(): Boolean {
|
||||
return fold(
|
||||
ifLeft = { false },
|
||||
ifRight = { tokenList ->
|
||||
val currencies = when (tokenList) {
|
||||
is TokenList.GroupedByNetwork -> tokenList.groups.flatMap(NetworkGroup::currencies)
|
||||
is TokenList.Ungrouped -> tokenList.currencies
|
||||
is TokenList.Empty -> emptyList()
|
||||
}
|
||||
|
||||
currencies.any { it.value is CryptoCurrencyStatus.Unreachable }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addRateTheAppNotification(
|
||||
isReadyToShowRating: Boolean,
|
||||
clickIntents: WalletClickIntentsV2,
|
||||
) {
|
||||
addIf(
|
||||
element = WalletNotification.RateApp(
|
||||
onLikeClick = clickIntents::onLikeAppClick,
|
||||
onDislikeClick = clickIntents::onDislikeAppClick,
|
||||
onCloseClick = clickIntents::onCloseRateAppWarningClick,
|
||||
),
|
||||
condition = isReadyToShowRating && readyForRateAppNotification,
|
||||
)
|
||||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addIf(element: WalletNotification, condition: Boolean) {
|
||||
if (condition) {
|
||||
add(element = element)
|
||||
if (element is WalletNotification.Critical || element is WalletNotification.Warning) {
|
||||
readyForRateAppNotification = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val MAX_REMAINING_SIGNATURES_COUNT = 10
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.domain
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.common.CardTypesResolver
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.demo.IsDemoCardUseCase
|
||||
import com.tangem.domain.settings.IsReadyToShowRateAppUseCase
|
||||
import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase
|
||||
import com.tangem.domain.tokens.error.CurrencyStatusError
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletNotification
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
|
||||
import dagger.hilt.android.scopes.ViewModelScoped
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.*
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@ViewModelScoped
|
||||
internal class GetSingleWalletWarningsFactory @Inject constructor(
|
||||
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||
private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase,
|
||||
private val isDemoCardUseCase: IsDemoCardUseCase,
|
||||
private val isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase,
|
||||
private val isNeedToBackupUseCase: IsNeedToBackupUseCase,
|
||||
private val hasSingleWalletSignedHashesUseCase: HasSingleWalletSignedHashesUseCase,
|
||||
) {
|
||||
|
||||
private var readyForRateAppNotification = false
|
||||
|
||||
fun create(clickIntents: WalletClickIntentsV2): Flow<ImmutableList<WalletNotification>> {
|
||||
val userWallet = getSelectedWalletSyncUseCase().fold(
|
||||
ifLeft = {
|
||||
Timber.e("Failed to get selected wallet $it")
|
||||
return flowOf(value = persistentListOf())
|
||||
},
|
||||
ifRight = { it },
|
||||
)
|
||||
|
||||
val cardTypesResolver = userWallet.scanResponse.cardTypesResolver
|
||||
|
||||
return combine(
|
||||
flow = getPrimaryCurrencyStatusUpdatesUseCase(userWallet.walletId),
|
||||
flow2 = isReadyToShowRateAppUseCase().conflate(),
|
||||
flow3 = isNeedToBackupUseCase(userWallet.walletId).conflate(),
|
||||
) { primaryCurrencyStatus, isReadyToShowRating, isNeedToBackup ->
|
||||
readyForRateAppNotification = true
|
||||
buildList {
|
||||
addCriticalNotifications(cardTypesResolver)
|
||||
|
||||
addInformationalNotifications(cardTypesResolver)
|
||||
|
||||
addWarningNotifications(
|
||||
userWallet,
|
||||
cardTypesResolver,
|
||||
primaryCurrencyStatus,
|
||||
isNeedToBackup,
|
||||
clickIntents,
|
||||
)
|
||||
|
||||
addRateTheAppNotification(isReadyToShowRating, clickIntents)
|
||||
}.toImmutableList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addCriticalNotifications(cardTypesResolver: CardTypesResolver) {
|
||||
addIf(
|
||||
element = WalletNotification.Critical.DevCard,
|
||||
condition = !cardTypesResolver.isReleaseFirmwareType(),
|
||||
)
|
||||
|
||||
addIf(
|
||||
element = WalletNotification.Critical.FailedCardValidation,
|
||||
condition = cardTypesResolver.isReleaseFirmwareType() && cardTypesResolver.isAttestationFailed(),
|
||||
)
|
||||
|
||||
cardTypesResolver.getRemainingSignatures()?.let { remainingSignatures ->
|
||||
addIf(
|
||||
element = WalletNotification.Warning.LowSignatures(count = remainingSignatures),
|
||||
condition = remainingSignatures <= MAX_REMAINING_SIGNATURES_COUNT,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addInformationalNotifications(cardTypesResolver: CardTypesResolver) {
|
||||
addIf(
|
||||
element = WalletNotification.Informational.DemoCard,
|
||||
condition = isDemoCardUseCase(cardId = cardTypesResolver.getCardId()),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun MutableList<WalletNotification>.addWarningNotifications(
|
||||
userWallet: UserWallet,
|
||||
cardTypesResolver: CardTypesResolver,
|
||||
maybePrimaryCurrencyStatus: Either<CurrencyStatusError, CryptoCurrencyStatus>,
|
||||
isNeedToBackup: Boolean,
|
||||
clickIntents: WalletClickIntentsV2,
|
||||
) {
|
||||
val cryptoCurrencyStatus = maybePrimaryCurrencyStatus.fold(ifLeft = { null }, ifRight = { it })
|
||||
|
||||
addIf(
|
||||
element = WalletNotification.Warning.MissingBackup(
|
||||
onStartBackupClick = clickIntents::onAddBackupCardClick,
|
||||
),
|
||||
condition = isNeedToBackup,
|
||||
)
|
||||
|
||||
addIf(
|
||||
element = WalletNotification.Warning.TestNetCard,
|
||||
condition = cardTypesResolver.isTestCard(),
|
||||
)
|
||||
|
||||
addIf(
|
||||
element = WalletNotification.Warning.NetworksUnreachable,
|
||||
condition = cryptoCurrencyStatus?.value is CryptoCurrencyStatus.Unreachable,
|
||||
)
|
||||
|
||||
addNoAccountWarning(cryptoCurrencyStatus)
|
||||
|
||||
addIf(
|
||||
element = WalletNotification.Warning.NumberOfSignedHashesIncorrect(
|
||||
onCloseClick = clickIntents::onCloseAlreadySignedHashesWarningClick,
|
||||
),
|
||||
condition = hasSignedHashes(userWallet, cryptoCurrencyStatus),
|
||||
)
|
||||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addNoAccountWarning(cryptoCurrencyStatus: CryptoCurrencyStatus?) {
|
||||
val noAccountStatus = cryptoCurrencyStatus?.value as? CryptoCurrencyStatus.NoAccount
|
||||
if (noAccountStatus != null) {
|
||||
add(
|
||||
element = WalletNotification.Informational.NoAccount(
|
||||
network = cryptoCurrencyStatus.currency.name,
|
||||
amount = noAccountStatus.amountToCreateAccount.toString(),
|
||||
symbol = cryptoCurrencyStatus.currency.symbol,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun hasSignedHashes(
|
||||
selectedWallet: UserWallet,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus?,
|
||||
): Boolean {
|
||||
return cryptoCurrencyStatus?.currency?.network?.let {
|
||||
hasSingleWalletSignedHashesUseCase(userWallet = selectedWallet, network = it)
|
||||
.conflate()
|
||||
.distinctUntilChanged()
|
||||
.firstOrNull()
|
||||
} ?: false
|
||||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addRateTheAppNotification(
|
||||
isReadyToShowRating: Boolean,
|
||||
clickIntents: WalletClickIntentsV2,
|
||||
) {
|
||||
addIf(
|
||||
element = WalletNotification.RateApp(
|
||||
onLikeClick = clickIntents::onLikeAppClick,
|
||||
onDislikeClick = clickIntents::onDislikeAppClick,
|
||||
onCloseClick = clickIntents::onCloseRateAppWarningClick,
|
||||
),
|
||||
condition = isReadyToShowRating && readyForRateAppNotification,
|
||||
)
|
||||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addIf(element: WalletNotification, condition: Boolean) {
|
||||
if (condition) {
|
||||
add(element = element)
|
||||
if (element is WalletNotification.Critical || element is WalletNotification.Warning) {
|
||||
readyForRateAppNotification = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val MAX_REMAINING_SIGNATURES_COUNT = 10
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.domain
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase
|
||||
import com.tangem.domain.tokens.error.CurrencyStatusError
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import kotlinx.coroutines.flow.*
|
||||
import timber.log.Timber
|
||||
|
||||
internal fun GetSelectedWalletSyncUseCase.unwrap(): UserWallet? {
|
||||
return this().fold(
|
||||
ifLeft = {
|
||||
Timber.e("Impossible to get selected wallet $it")
|
||||
null
|
||||
},
|
||||
ifRight = { it },
|
||||
)
|
||||
}
|
||||
|
||||
internal suspend fun GetPrimaryCurrencyStatusUpdatesUseCase.unwrap(userWalletId: UserWalletId): CryptoCurrencyStatus? {
|
||||
return this(userWalletId)
|
||||
.conflate()
|
||||
.distinctUntilChanged()
|
||||
.filter(Either<CurrencyStatusError, CryptoCurrencyStatus>::isRight)
|
||||
.firstOrNull()
|
||||
?.fold(
|
||||
ifLeft = {
|
||||
Timber.e("Impossible to get primary currency status $it")
|
||||
null
|
||||
},
|
||||
ifRight = { it },
|
||||
)
|
||||
}
|
||||
|
||||
internal suspend fun GetSelectedAppCurrencyUseCase.unwrap(): AppCurrency {
|
||||
return this()
|
||||
.map { maybeAppCurrency ->
|
||||
maybeAppCurrency.getOrElse { AppCurrency.Default }
|
||||
}
|
||||
.firstOrNull()
|
||||
?: AppCurrency.Default
|
||||
}
|
||||
|
||||
internal suspend fun GetPrimaryCurrencyStatusUpdatesUseCase.collectLatest(
|
||||
userWalletId: UserWalletId,
|
||||
onRight: suspend (CryptoCurrencyStatus) -> Unit,
|
||||
) {
|
||||
this(userWalletId = userWalletId)
|
||||
.conflate()
|
||||
.distinctUntilChanged()
|
||||
.collectLatest { maybeStatus ->
|
||||
maybeStatus.onRight { onRight(it) }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.domain
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.domain.settings.SetWalletWithFundsFoundUseCase
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.NetworkGroup
|
||||
import com.tangem.domain.tokens.model.TokenList
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class WalletWithFundsChecker @Inject constructor(
|
||||
private val setWalletWithFundsFoundUseCase: SetWalletWithFundsFoundUseCase,
|
||||
) {
|
||||
|
||||
suspend fun check(maybeTokenList: Either<TokenListError, TokenList>) {
|
||||
val tokenList = (maybeTokenList as? Either.Right)?.value ?: return
|
||||
|
||||
val hasNonZeroWallets = when (tokenList) {
|
||||
is TokenList.GroupedByNetwork -> {
|
||||
tokenList.groups
|
||||
.flatMap(NetworkGroup::currencies)
|
||||
.hasNonZeroWallets()
|
||||
}
|
||||
is TokenList.Ungrouped -> tokenList.currencies.hasNonZeroWallets()
|
||||
is TokenList.Empty -> false
|
||||
}
|
||||
|
||||
if (hasNonZeroWallets) setWalletWithFundsFoundUseCase()
|
||||
}
|
||||
|
||||
private fun List<CryptoCurrencyStatus>.hasNonZeroWallets(): Boolean {
|
||||
return any {
|
||||
val amount = it.value.amount ?: return@any false
|
||||
!amount.isZero()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.loaders
|
||||
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.loaders.implementors.MultiWalletContentLoaderFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.loaders.implementors.SingleWalletContentLoaderFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.loaders.implementors.SingleWalletWithTokenContentLoaderFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.loaders.implementors.WalletContentLoader
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
|
||||
import dagger.hilt.android.scopes.ViewModelScoped
|
||||
import javax.inject.Inject
|
||||
|
||||
@ViewModelScoped
|
||||
internal class WalletContentLoaderFactory @Inject constructor(
|
||||
private val multiWalletContentLoaderFactory: MultiWalletContentLoaderFactory,
|
||||
private val singleWalletWithTokenContentLoaderFactory: SingleWalletWithTokenContentLoaderFactory,
|
||||
private val singleWalletContentLoaderFactory: SingleWalletContentLoaderFactory,
|
||||
) {
|
||||
|
||||
fun create(
|
||||
userWallet: UserWallet,
|
||||
appCurrency: AppCurrency,
|
||||
clickIntents: WalletClickIntentsV2,
|
||||
isRefresh: Boolean = false,
|
||||
): WalletContentLoader? {
|
||||
return when {
|
||||
userWallet.isMultiCurrency -> {
|
||||
multiWalletContentLoaderFactory.create(userWallet, appCurrency, clickIntents)
|
||||
}
|
||||
userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken() -> {
|
||||
singleWalletWithTokenContentLoaderFactory.create(userWallet, appCurrency, clickIntents)
|
||||
}
|
||||
!userWallet.isMultiCurrency -> {
|
||||
singleWalletContentLoaderFactory.create(userWallet, appCurrency, clickIntents, isRefresh)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.loaders
|
||||
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.Job
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
internal class WalletLoaderStorage @Inject constructor() {
|
||||
|
||||
private val loaders = ConcurrentHashMap<UserWalletId, List<Job>>()
|
||||
|
||||
fun contains(id: UserWalletId) = loaders.containsKey(id)
|
||||
|
||||
fun set(id: UserWalletId, jobs: List<Job>) {
|
||||
loaders[id] = jobs
|
||||
}
|
||||
|
||||
fun remove(id: UserWalletId) {
|
||||
loaders[id]?.let {
|
||||
it.forEach(Job::cancel)
|
||||
loaders.remove(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.loaders
|
||||
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.hilt.android.scopes.ViewModelScoped
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Base wallet screen content loader. Use it to load content by [UserWallet].
|
||||
*
|
||||
* @property factory factory that creates loader
|
||||
* @property storage storage that save loader's jobs
|
||||
* @property dispatchers coroutine dispatchers provider
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@ViewModelScoped
|
||||
internal class WalletScreenContentLoader @Inject constructor(
|
||||
private val factory: WalletContentLoaderFactory,
|
||||
private val storage: WalletLoaderStorage,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Load content by [UserWallet]
|
||||
*
|
||||
* @param userWallet user wallet
|
||||
* @param appCurrency app currency
|
||||
* @param clickIntents click intents
|
||||
* @param isRefresh flag that determinate if content must load again
|
||||
* @param coroutineScope coroutine scope
|
||||
*/
|
||||
fun load(
|
||||
userWallet: UserWallet,
|
||||
appCurrency: AppCurrency,
|
||||
clickIntents: WalletClickIntentsV2,
|
||||
isRefresh: Boolean = false,
|
||||
coroutineScope: CoroutineScope,
|
||||
) {
|
||||
if (userWallet.isLocked) return
|
||||
|
||||
val id = userWallet.walletId
|
||||
if (!storage.contains(id)) {
|
||||
loadInternal(userWallet, appCurrency, clickIntents, coroutineScope, isRefresh)
|
||||
} else {
|
||||
if (isRefresh) {
|
||||
storage.remove(id)
|
||||
loadInternal(userWallet, appCurrency, clickIntents, coroutineScope, true)
|
||||
} else {
|
||||
Timber.d("$id content loading has already started")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Cancel loading by [id] */
|
||||
fun cancel(id: UserWalletId) {
|
||||
Timber.d("$id content loading is canceled")
|
||||
storage.remove(id)
|
||||
}
|
||||
|
||||
private fun loadInternal(
|
||||
userWallet: UserWallet,
|
||||
appCurrency: AppCurrency,
|
||||
clickIntents: WalletClickIntentsV2,
|
||||
coroutineScope: CoroutineScope,
|
||||
isRefresh: Boolean,
|
||||
) {
|
||||
val loader = factory.create(
|
||||
userWallet = userWallet,
|
||||
appCurrency = appCurrency,
|
||||
clickIntents = clickIntents,
|
||||
isRefresh = isRefresh,
|
||||
)
|
||||
|
||||
if (loader == null) {
|
||||
Timber.e("Impossible to create loader for $userWallet")
|
||||
return
|
||||
}
|
||||
|
||||
Timber.d("${userWallet.walletId} content loading is ${if (isRefresh) "re" else ""}started")
|
||||
|
||||
loader.subscribers
|
||||
.map { it.subscribe(coroutineScope, dispatchers) }
|
||||
.let { storage.set(userWallet.walletId, it) }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
|
||||
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.GetTokenListUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarningsFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateHolderV2
|
||||
import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletWarningsSubscriber
|
||||
import com.tangem.feature.wallet.presentation.wallet.subscribers.TokenListSubscriber
|
||||
import com.tangem.feature.wallet.presentation.wallet.subscribers.WalletSubscriber
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class MultiWalletContentLoader(
|
||||
private val userWallet: UserWallet,
|
||||
private val appCurrency: AppCurrency,
|
||||
private val clickIntents: WalletClickIntentsV2,
|
||||
private val stateHolder: WalletStateHolderV2,
|
||||
private val tokenListAnalyticsSender: TokenListAnalyticsSender,
|
||||
private val walletWithFundsChecker: WalletWithFundsChecker,
|
||||
private val getTokenListUseCase: GetTokenListUseCase,
|
||||
private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory,
|
||||
) : WalletContentLoader(id = userWallet.walletId) {
|
||||
|
||||
override fun create(): List<WalletSubscriber<*>> {
|
||||
return listOf(
|
||||
TokenListSubscriber(
|
||||
userWallet = userWallet,
|
||||
appCurrency = appCurrency,
|
||||
stateHolder = stateHolder,
|
||||
clickIntents = clickIntents,
|
||||
tokenListAnalyticsSender = tokenListAnalyticsSender,
|
||||
walletWithFundsChecker = walletWithFundsChecker,
|
||||
getTokenListUseCase = getTokenListUseCase,
|
||||
),
|
||||
MultiWalletWarningsSubscriber(
|
||||
userWalletId = userWallet.walletId,
|
||||
stateHolder = stateHolder,
|
||||
clickIntents = clickIntents,
|
||||
getMultiWalletWarningsFactory = getMultiWalletWarningsFactory,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
|
||||
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.GetTokenListUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarningsFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateHolderV2
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
|
||||
import dagger.hilt.android.scopes.ViewModelScoped
|
||||
import javax.inject.Inject
|
||||
|
||||
@ViewModelScoped
|
||||
internal class MultiWalletContentLoaderFactory @Inject constructor(
|
||||
private val stateHolder: WalletStateHolderV2,
|
||||
private val getTokenListUseCase: GetTokenListUseCase,
|
||||
private val tokenListAnalyticsSender: TokenListAnalyticsSender,
|
||||
private val walletWithFundsChecker: WalletWithFundsChecker,
|
||||
private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory,
|
||||
) {
|
||||
|
||||
fun create(
|
||||
userWallet: UserWallet,
|
||||
appCurrency: AppCurrency,
|
||||
clickIntents: WalletClickIntentsV2,
|
||||
): WalletContentLoader {
|
||||
return MultiWalletContentLoader(
|
||||
userWallet = userWallet,
|
||||
appCurrency = appCurrency,
|
||||
clickIntents = clickIntents,
|
||||
stateHolder = stateHolder,
|
||||
tokenListAnalyticsSender = tokenListAnalyticsSender,
|
||||
walletWithFundsChecker = walletWithFundsChecker,
|
||||
getTokenListUseCase = getTokenListUseCase,
|
||||
getMultiWalletWarningsFactory = getMultiWalletWarningsFactory,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
|
||||
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.settings.SetWalletWithFundsFoundUseCase
|
||||
import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase
|
||||
import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase
|
||||
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase
|
||||
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.GetSingleWalletWarningsFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateHolderV2
|
||||
import com.tangem.feature.wallet.presentation.wallet.subscribers.*
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class SingleWalletContentLoader(
|
||||
private val userWallet: UserWallet,
|
||||
private val appCurrency: AppCurrency,
|
||||
private val clickIntents: WalletClickIntentsV2,
|
||||
private val isRefresh: Boolean,
|
||||
private val stateHolder: WalletStateHolderV2,
|
||||
private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase,
|
||||
private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCase,
|
||||
private val getSingleWalletWarningsFactory: GetSingleWalletWarningsFactory,
|
||||
private val setWalletWithFundsFoundUseCase: SetWalletWithFundsFoundUseCase,
|
||||
private val txHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase,
|
||||
private val txHistoryItemsUseCase: GetTxHistoryItemsUseCase,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
) : WalletContentLoader(id = userWallet.walletId) {
|
||||
|
||||
override fun create(): List<WalletSubscriber<*>> {
|
||||
return listOf(
|
||||
PrimaryCurrencySubscriber(
|
||||
userWallet = userWallet,
|
||||
appCurrency = appCurrency,
|
||||
stateHolder = stateHolder,
|
||||
getPrimaryCurrencyStatusUpdatesUseCase = getPrimaryCurrencyStatusUpdatesUseCase,
|
||||
setWalletWithFundsFoundUseCase = setWalletWithFundsFoundUseCase,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
),
|
||||
SingleWalletButtonsSubscriber(
|
||||
userWallet = userWallet,
|
||||
stateHolder = stateHolder,
|
||||
clickIntents = clickIntents,
|
||||
getPrimaryCurrencyStatusUpdatesUseCase = getPrimaryCurrencyStatusUpdatesUseCase,
|
||||
getCryptoCurrencyActionsUseCase = getCryptoCurrencyActionsUseCase,
|
||||
),
|
||||
SingleWalletNotificationsSubscriber(
|
||||
userWalletId = userWallet.walletId,
|
||||
stateHolder = stateHolder,
|
||||
clickIntents = clickIntents,
|
||||
getSingleWalletWarningsFactory = getSingleWalletWarningsFactory,
|
||||
),
|
||||
TxHistorySubscriber(
|
||||
userWallet = userWallet,
|
||||
isRefresh = isRefresh,
|
||||
stateHolder = stateHolder,
|
||||
clickIntents = clickIntents,
|
||||
getPrimaryCurrencyStatusUpdatesUseCase = getPrimaryCurrencyStatusUpdatesUseCase,
|
||||
txHistoryItemsCountUseCase = txHistoryItemsCountUseCase,
|
||||
txHistoryItemsUseCase = txHistoryItemsUseCase,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
|
||||
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.settings.SetWalletWithFundsFoundUseCase
|
||||
import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase
|
||||
import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase
|
||||
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase
|
||||
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.GetSingleWalletWarningsFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateHolderV2
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
|
||||
import dagger.hilt.android.scopes.ViewModelScoped
|
||||
import javax.inject.Inject
|
||||
|
||||
@ViewModelScoped
|
||||
@Suppress("LongParameterList")
|
||||
internal class SingleWalletContentLoaderFactory @Inject constructor(
|
||||
private val stateHolder: WalletStateHolderV2,
|
||||
private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase,
|
||||
private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCase,
|
||||
private val getSingleWalletWarningsFactory: GetSingleWalletWarningsFactory,
|
||||
private val setWalletWithFundsFoundUseCase: SetWalletWithFundsFoundUseCase,
|
||||
private val txHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase,
|
||||
private val txHistoryItemsUseCase: GetTxHistoryItemsUseCase,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
) {
|
||||
|
||||
fun create(
|
||||
userWallet: UserWallet,
|
||||
appCurrency: AppCurrency,
|
||||
clickIntents: WalletClickIntentsV2,
|
||||
isRefresh: Boolean,
|
||||
): WalletContentLoader {
|
||||
return SingleWalletContentLoader(
|
||||
userWallet = userWallet,
|
||||
appCurrency = appCurrency,
|
||||
clickIntents = clickIntents,
|
||||
isRefresh = isRefresh,
|
||||
stateHolder = stateHolder,
|
||||
getPrimaryCurrencyStatusUpdatesUseCase = getPrimaryCurrencyStatusUpdatesUseCase,
|
||||
getCryptoCurrencyActionsUseCase = getCryptoCurrencyActionsUseCase,
|
||||
getSingleWalletWarningsFactory = getSingleWalletWarningsFactory,
|
||||
setWalletWithFundsFoundUseCase = setWalletWithFundsFoundUseCase,
|
||||
txHistoryItemsCountUseCase = txHistoryItemsCountUseCase,
|
||||
txHistoryItemsUseCase = txHistoryItemsUseCase,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
|
||||
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.GetCardTokensListUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarningsFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateHolderV2
|
||||
import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletWarningsSubscriber
|
||||
import com.tangem.feature.wallet.presentation.wallet.subscribers.SingleWalletWithTokenListSubscriber
|
||||
import com.tangem.feature.wallet.presentation.wallet.subscribers.WalletSubscriber
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class SingleWalletWithTokenContentLoader(
|
||||
private val userWallet: UserWallet,
|
||||
private val appCurrency: AppCurrency,
|
||||
private val clickIntents: WalletClickIntentsV2,
|
||||
private val stateHolder: WalletStateHolderV2,
|
||||
private val tokenListAnalyticsSender: TokenListAnalyticsSender,
|
||||
private val walletWithFundsChecker: WalletWithFundsChecker,
|
||||
private val getCardTokensListUseCase: GetCardTokensListUseCase,
|
||||
private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory,
|
||||
) : WalletContentLoader(id = userWallet.walletId) {
|
||||
|
||||
override fun create(): List<WalletSubscriber<*>> {
|
||||
return listOf(
|
||||
SingleWalletWithTokenListSubscriber(
|
||||
userWallet = userWallet,
|
||||
appCurrency = appCurrency,
|
||||
stateHolder = stateHolder,
|
||||
clickIntents = clickIntents,
|
||||
tokenListAnalyticsSender = tokenListAnalyticsSender,
|
||||
walletWithFundsChecker = walletWithFundsChecker,
|
||||
getCardTokensListUseCase = getCardTokensListUseCase,
|
||||
),
|
||||
MultiWalletWarningsSubscriber(
|
||||
userWalletId = userWallet.walletId,
|
||||
stateHolder = stateHolder,
|
||||
clickIntents = clickIntents,
|
||||
getMultiWalletWarningsFactory = getMultiWalletWarningsFactory,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
|
||||
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.GetCardTokensListUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarningsFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateHolderV2
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor(
|
||||
private val stateHolder: WalletStateHolderV2,
|
||||
private val tokenListAnalyticsSender: TokenListAnalyticsSender,
|
||||
private val walletWithFundsChecker: WalletWithFundsChecker,
|
||||
private val getCardTokensListUseCase: GetCardTokensListUseCase,
|
||||
private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory,
|
||||
) {
|
||||
|
||||
fun create(
|
||||
userWallet: UserWallet,
|
||||
appCurrency: AppCurrency,
|
||||
clickIntents: WalletClickIntentsV2,
|
||||
): SingleWalletWithTokenContentLoader {
|
||||
return SingleWalletWithTokenContentLoader(
|
||||
userWallet = userWallet,
|
||||
appCurrency = appCurrency,
|
||||
clickIntents = clickIntents,
|
||||
stateHolder = stateHolder,
|
||||
tokenListAnalyticsSender = tokenListAnalyticsSender,
|
||||
walletWithFundsChecker = walletWithFundsChecker,
|
||||
getCardTokensListUseCase = getCardTokensListUseCase,
|
||||
getMultiWalletWarningsFactory = getMultiWalletWarningsFactory,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
|
||||
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.wallet.presentation.wallet.subscribers.WalletSubscriber
|
||||
|
||||
/**
|
||||
* Wallet content loader
|
||||
*
|
||||
* @property id loader id
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal abstract class WalletContentLoader(val id: UserWalletId) {
|
||||
|
||||
/** Loader's subscribers */
|
||||
val subscribers: List<WalletSubscriber<*>> get() = create()
|
||||
|
||||
protected abstract fun create(): List<WalletSubscriber<*>>
|
||||
}
|
||||
|
|
@ -17,4 +17,16 @@ internal sealed class WalletEvent {
|
|||
data class CopyAddress(val address: String, val toast: TextReference) : WalletEvent()
|
||||
|
||||
data class RateApp(val onDismissClick: () -> Unit) : WalletEvent()
|
||||
|
||||
data class DemonstrateWalletsScrollPreview(val direction: Direction) : WalletEvent() {
|
||||
|
||||
enum class Direction {
|
||||
|
||||
/** 1 -> 2 */
|
||||
LEFT,
|
||||
|
||||
/** 1 <- 2 */
|
||||
RIGHT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,187 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state2
|
||||
|
||||
import androidx.paging.PagingData
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
|
||||
import com.tangem.core.ui.components.transactions.state.TransactionState
|
||||
import com.tangem.core.ui.components.transactions.state.TxHistoryState
|
||||
import com.tangem.core.ui.event.StateEvent
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletEvent
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.components.*
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import javax.annotation.concurrent.Immutable
|
||||
|
||||
const val NOT_INITIALIZED_WALLET_INDEX = -1
|
||||
|
||||
internal data class WalletScreenState(
|
||||
val onBackClick: () -> Unit,
|
||||
val topBarConfig: WalletTopBarConfig,
|
||||
val selectedWalletIndex: Int,
|
||||
val wallets: ImmutableList<WalletState>,
|
||||
val onWalletChange: (Int) -> Unit,
|
||||
val event: StateEvent<WalletEvent>,
|
||||
val isHidingMode: Boolean,
|
||||
)
|
||||
|
||||
internal sealed class WalletState {
|
||||
|
||||
abstract val pullToRefreshConfig: WalletPullToRefreshConfig
|
||||
abstract val walletCardState: WalletCardState
|
||||
abstract val warnings: ImmutableList<WalletNotification>
|
||||
abstract val bottomSheetConfig: TangemBottomSheetConfig?
|
||||
|
||||
sealed class MultiCurrency : WalletState() {
|
||||
|
||||
abstract val tokensListState: WalletTokensListState
|
||||
abstract val manageTokensButtonConfig: ManageTokensButtonConfig?
|
||||
|
||||
data class Content(
|
||||
override val pullToRefreshConfig: WalletPullToRefreshConfig,
|
||||
override val walletCardState: WalletCardState,
|
||||
override val warnings: ImmutableList<WalletNotification>,
|
||||
override val bottomSheetConfig: TangemBottomSheetConfig?,
|
||||
override val tokensListState: WalletTokensListState,
|
||||
override val manageTokensButtonConfig: ManageTokensButtonConfig?,
|
||||
) : MultiCurrency()
|
||||
|
||||
data class Locked(
|
||||
override val walletCardState: WalletCardState,
|
||||
val onUnlockNotificationClick: () -> Unit,
|
||||
val isBottomSheetShow: Boolean = false,
|
||||
val onBottomSheetDismiss: () -> Unit = {},
|
||||
val onUnlockClick: () -> Unit,
|
||||
val onScanClick: () -> Unit,
|
||||
) : MultiCurrency() {
|
||||
|
||||
override val pullToRefreshConfig: WalletPullToRefreshConfig
|
||||
get() = WalletPullToRefreshConfig(isRefreshing = false, onRefresh = {})
|
||||
|
||||
override val warnings: ImmutableList<WalletNotification> = persistentListOf(
|
||||
WalletNotification.UnlockWallets(onUnlockNotificationClick),
|
||||
)
|
||||
|
||||
override val bottomSheetConfig = TangemBottomSheetConfig(
|
||||
isShow = isBottomSheetShow,
|
||||
onDismissRequest = onBottomSheetDismiss,
|
||||
content = WalletBottomSheetConfig.UnlockWallets(
|
||||
onUnlockClick = onUnlockClick,
|
||||
onScanClick = onScanClick,
|
||||
),
|
||||
)
|
||||
|
||||
override val tokensListState = WalletTokensListState.ContentState.Locked
|
||||
override val manageTokensButtonConfig = null
|
||||
}
|
||||
}
|
||||
|
||||
sealed class SingleCurrency : WalletState() {
|
||||
|
||||
abstract val buttons: PersistentList<WalletManageButton>
|
||||
abstract val marketPriceBlockState: MarketPriceBlockState?
|
||||
abstract val txHistoryState: TxHistoryState
|
||||
|
||||
data class Content(
|
||||
override val pullToRefreshConfig: WalletPullToRefreshConfig,
|
||||
override val walletCardState: WalletCardState,
|
||||
override val warnings: ImmutableList<WalletNotification>,
|
||||
override val bottomSheetConfig: TangemBottomSheetConfig?,
|
||||
override val buttons: PersistentList<WalletManageButton>,
|
||||
override val marketPriceBlockState: MarketPriceBlockState,
|
||||
override val txHistoryState: TxHistoryState,
|
||||
) : SingleCurrency()
|
||||
|
||||
data class Locked(
|
||||
override val walletCardState: WalletCardState,
|
||||
override val buttons: PersistentList<WalletManageButton>,
|
||||
val onUnlockNotificationClick: () -> Unit,
|
||||
val isBottomSheetShow: Boolean = false,
|
||||
val onBottomSheetDismiss: () -> Unit = {},
|
||||
val onUnlockClick: () -> Unit,
|
||||
val onScanClick: () -> Unit,
|
||||
val onExploreClick: () -> Unit,
|
||||
) : SingleCurrency() {
|
||||
|
||||
override val pullToRefreshConfig: WalletPullToRefreshConfig
|
||||
get() = WalletPullToRefreshConfig(isRefreshing = false, onRefresh = {})
|
||||
|
||||
override val warnings: ImmutableList<WalletNotification> = persistentListOf(
|
||||
WalletNotification.UnlockWallets(onUnlockNotificationClick),
|
||||
)
|
||||
|
||||
override val bottomSheetConfig = TangemBottomSheetConfig(
|
||||
isShow = isBottomSheetShow,
|
||||
onDismissRequest = onBottomSheetDismiss,
|
||||
content = WalletBottomSheetConfig.UnlockWallets(
|
||||
onUnlockClick = onUnlockClick,
|
||||
onScanClick = onScanClick,
|
||||
),
|
||||
)
|
||||
|
||||
override val marketPriceBlockState: MarketPriceBlockState? = null
|
||||
|
||||
override val txHistoryState: TxHistoryState = TxHistoryState.Content(
|
||||
contentItems = MutableStateFlow(
|
||||
value = PagingData.from(
|
||||
data = listOf(
|
||||
TxHistoryState.TxHistoryItemState.Title(onExploreClick = onExploreClick),
|
||||
TxHistoryState.TxHistoryItemState.Transaction(
|
||||
state = TransactionState.Locked(txHash = "LOCKED_TX_HASH"),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class WalletTokensListState {
|
||||
|
||||
object Empty : WalletTokensListState()
|
||||
|
||||
sealed class ContentState : WalletTokensListState() {
|
||||
|
||||
abstract val items: ImmutableList<TokensListItemState>
|
||||
abstract val organizeTokensButtonConfig: OrganizeTokensButtonConfig?
|
||||
|
||||
object Loading : ContentState() {
|
||||
override val items = persistentListOf<TokensListItemState>()
|
||||
override val organizeTokensButtonConfig = null
|
||||
}
|
||||
|
||||
data class Content(
|
||||
override val items: ImmutableList<TokensListItemState>,
|
||||
override val organizeTokensButtonConfig: OrganizeTokensButtonConfig?,
|
||||
) : ContentState()
|
||||
|
||||
object Locked : ContentState() {
|
||||
override val items = persistentListOf(
|
||||
TokensListItemState.NetworkGroupTitle(id = 42, name = TextReference.Res(id = R.string.main_tokens)),
|
||||
TokensListItemState.Token(state = TokenItemState.Locked(id = "Locked#1")),
|
||||
)
|
||||
override val organizeTokensButtonConfig = null
|
||||
}
|
||||
}
|
||||
|
||||
data class OrganizeTokensButtonConfig(val isEnabled: Boolean, val onClick: () -> Unit)
|
||||
|
||||
@Immutable
|
||||
sealed class TokensListItemState {
|
||||
|
||||
abstract val id: Any
|
||||
|
||||
data class NetworkGroupTitle(override val id: Int, val name: TextReference) : TokensListItemState()
|
||||
|
||||
data class Token(val state: TokenItemState) : TokensListItemState() {
|
||||
override val id: String = state.id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal data class ManageTokensButtonConfig(val onClick: () -> Unit)
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state2
|
||||
|
||||
import com.tangem.core.ui.event.consumedEvent
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTopBarConfig
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.WalletScreenStateTransformer
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Wallet state holder
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Singleton
|
||||
internal class WalletStateHolderV2 @Inject constructor() {
|
||||
|
||||
val uiState: StateFlow<WalletScreenState> get() = mutableUiState
|
||||
val value: WalletScreenState get() = uiState.value
|
||||
|
||||
private val mutableUiState: MutableStateFlow<WalletScreenState> = MutableStateFlow(value = getInitialState())
|
||||
|
||||
fun update(function: (WalletScreenState) -> WalletScreenState) {
|
||||
mutableUiState.update(function = function)
|
||||
}
|
||||
|
||||
fun update(transformer: WalletScreenStateTransformer) {
|
||||
mutableUiState.update(function = transformer::transform)
|
||||
}
|
||||
|
||||
fun getSelectedWallet(): WalletState {
|
||||
return with(value) { wallets[selectedWalletIndex] }
|
||||
}
|
||||
|
||||
fun getSelectedWalletId(): UserWalletId {
|
||||
return with(value) { wallets[selectedWalletIndex].walletCardState.id }
|
||||
}
|
||||
|
||||
private fun getInitialState(): WalletScreenState {
|
||||
return WalletScreenState(
|
||||
onBackClick = {},
|
||||
topBarConfig = WalletTopBarConfig(onDetailsClick = {}),
|
||||
selectedWalletIndex = NOT_INITIALIZED_WALLET_INDEX,
|
||||
wallets = persistentListOf(),
|
||||
onWalletChange = {},
|
||||
event = consumedEvent(),
|
||||
isHidingMode = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state2.transformers
|
||||
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletScreenState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.utils.WalletLoadingStateFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
internal class AddWalletTransformer(
|
||||
private val userWallet: UserWallet,
|
||||
private val clickIntents: WalletClickIntentsV2,
|
||||
) : WalletScreenStateTransformer {
|
||||
|
||||
private val walletLoadingStateFactory by lazy {
|
||||
WalletLoadingStateFactory(clickIntents = clickIntents)
|
||||
}
|
||||
|
||||
override fun transform(prevState: WalletScreenState): WalletScreenState {
|
||||
return prevState.copy(
|
||||
wallets = (prevState.wallets + walletLoadingStateFactory.create(userWallet)).toImmutableList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state2.transformers
|
||||
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletState
|
||||
|
||||
internal class CloseBottomSheetTransformer(userWalletId: UserWalletId) : WalletStateTransformer(userWalletId) {
|
||||
|
||||
override fun transform(prevState: WalletState): WalletState {
|
||||
return when (prevState) {
|
||||
is WalletState.MultiCurrency.Content -> {
|
||||
prevState.copy(bottomSheetConfig = prevState.bottomSheetConfig?.copy(isShow = false))
|
||||
}
|
||||
is WalletState.MultiCurrency.Locked -> prevState.copy(isBottomSheetShow = false)
|
||||
is WalletState.SingleCurrency.Content -> {
|
||||
prevState.copy(bottomSheetConfig = prevState.bottomSheetConfig?.copy(isShow = false))
|
||||
}
|
||||
is WalletState.SingleCurrency.Locked -> prevState.copy(isBottomSheetShow = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state2.transformers
|
||||
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletScreenState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletState
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import timber.log.Timber
|
||||
|
||||
internal class DeleteWalletTransformer(
|
||||
private val selectedWalletIndex: Int,
|
||||
private val deletedWalletId: UserWalletId,
|
||||
) : WalletScreenStateTransformer {
|
||||
|
||||
override fun transform(prevState: WalletScreenState): WalletScreenState {
|
||||
val deletedWalletState = prevState.getDeletedWalletState()
|
||||
|
||||
if (deletedWalletState == null) {
|
||||
Timber.e("Wallets does not contain deleted wallet")
|
||||
return prevState
|
||||
}
|
||||
|
||||
return prevState.copy(
|
||||
selectedWalletIndex = selectedWalletIndex,
|
||||
wallets = (prevState.wallets - deletedWalletState).toImmutableList(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun WalletScreenState.getDeletedWalletState(): WalletState? {
|
||||
return wallets.firstOrNull { it.walletCardState.id == deletedWalletId }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state2.transformers
|
||||
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletManageButton
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTopBarConfig
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletScreenState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.utils.WalletLoadingStateFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.utils.createStateByWalletType
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
internal class InitializeWalletsTransformer(
|
||||
private val selectedWalletIndex: Int,
|
||||
private val selectedWallet: UserWallet,
|
||||
private val wallets: List<UserWallet>,
|
||||
private val clickIntents: WalletClickIntentsV2,
|
||||
) : WalletScreenStateTransformer {
|
||||
|
||||
private val walletLoadingStateFactory by lazy { WalletLoadingStateFactory(clickIntents = clickIntents) }
|
||||
|
||||
override fun transform(prevState: WalletScreenState): WalletScreenState {
|
||||
return prevState.copy(
|
||||
onBackClick = clickIntents::onBackClick,
|
||||
topBarConfig = createTopBarConfig(userWallet = selectedWallet),
|
||||
selectedWalletIndex = selectedWalletIndex,
|
||||
wallets = wallets
|
||||
.map { userWallet ->
|
||||
if (userWallet.isLocked) {
|
||||
createLockedState(userWallet)
|
||||
} else {
|
||||
walletLoadingStateFactory.create(userWallet)
|
||||
}
|
||||
}
|
||||
.toImmutableList(),
|
||||
onWalletChange = clickIntents::onWalletChange,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createTopBarConfig(userWallet: UserWallet): WalletTopBarConfig {
|
||||
return WalletTopBarConfig(
|
||||
onDetailsClick = if (userWallet.isLocked) {
|
||||
clickIntents::onOpenUnlockWalletsBottomSheetClick
|
||||
} else {
|
||||
clickIntents::onDetailsClick
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun createLockedState(userWallet: UserWallet): WalletState {
|
||||
return userWallet.createStateByWalletType(
|
||||
multiCurrencyCreator = {
|
||||
WalletState.MultiCurrency.Locked(
|
||||
walletCardState = userWallet.toLockedWalletCardState(),
|
||||
onUnlockNotificationClick = clickIntents::onOpenUnlockWalletsBottomSheetClick,
|
||||
onUnlockClick = clickIntents::onUnlockWalletClick,
|
||||
onScanClick = clickIntents::onScanToUnlockWalletClick,
|
||||
)
|
||||
},
|
||||
singleCurrencyCreator = {
|
||||
WalletState.SingleCurrency.Locked(
|
||||
walletCardState = userWallet.toLockedWalletCardState(),
|
||||
buttons = createDisabledButtons(),
|
||||
onUnlockNotificationClick = clickIntents::onOpenUnlockWalletsBottomSheetClick,
|
||||
onUnlockClick = clickIntents::onUnlockWalletClick,
|
||||
onScanClick = clickIntents::onScanToUnlockWalletClick,
|
||||
onExploreClick = clickIntents::onExploreClick,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun UserWallet.toLockedWalletCardState(): WalletCardState {
|
||||
return WalletCardState.LockedContent(
|
||||
id = walletId,
|
||||
title = name,
|
||||
additionalInfo = WalletAdditionalInfoFactory.resolve(wallet = this),
|
||||
imageResId = WalletImageResolver.resolve(userWallet = this),
|
||||
onRenameClick = clickIntents::onRenameClick,
|
||||
onDeleteClick = clickIntents::onDeleteBeforeConfirmationClick,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createDisabledButtons(): PersistentList<WalletManageButton> {
|
||||
return persistentListOf(
|
||||
WalletManageButton.Buy(enabled = false, onClick = {}),
|
||||
WalletManageButton.Send(enabled = false, onClick = {}),
|
||||
WalletManageButton.Receive(enabled = false, onClick = {}),
|
||||
WalletManageButton.Sell(enabled = false, onClick = {}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state2.transformers
|
||||
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletState
|
||||
|
||||
internal class OpenBottomSheetTransformer(
|
||||
userWalletId: UserWalletId,
|
||||
private val content: TangemBottomSheetConfigContent,
|
||||
private val onDismissBottomSheet: () -> Unit,
|
||||
) : WalletStateTransformer(userWalletId) {
|
||||
|
||||
override fun transform(prevState: WalletState): WalletState {
|
||||
return when (prevState) {
|
||||
is WalletState.MultiCurrency.Content -> {
|
||||
prevState.copy(
|
||||
bottomSheetConfig = TangemBottomSheetConfig(
|
||||
isShow = true,
|
||||
onDismissRequest = onDismissBottomSheet,
|
||||
content = content,
|
||||
),
|
||||
)
|
||||
}
|
||||
is WalletState.MultiCurrency.Locked -> {
|
||||
prevState.copy(isBottomSheetShow = true, onBottomSheetDismiss = onDismissBottomSheet)
|
||||
}
|
||||
is WalletState.SingleCurrency.Content -> {
|
||||
prevState.copy(
|
||||
bottomSheetConfig = TangemBottomSheetConfig(
|
||||
isShow = true,
|
||||
onDismissRequest = onDismissBottomSheet,
|
||||
content = content,
|
||||
),
|
||||
)
|
||||
}
|
||||
is WalletState.SingleCurrency.Locked -> {
|
||||
prevState.copy(isBottomSheetShow = true, onBottomSheetDismiss = onDismissBottomSheet)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state2.transformers
|
||||
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletScreenState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.utils.WalletLoadingStateFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class ReinitializeWalletTransformer(
|
||||
private val userWallet: UserWallet,
|
||||
private val clickIntents: WalletClickIntentsV2,
|
||||
) : WalletScreenStateTransformer {
|
||||
|
||||
private val walletLoadingStateFactory by lazy { WalletLoadingStateFactory(clickIntents = clickIntents) }
|
||||
|
||||
override fun transform(prevState: WalletScreenState): WalletScreenState {
|
||||
return prevState.copy(
|
||||
wallets = persistentListOf(
|
||||
walletLoadingStateFactory.create(userWallet),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state2.transformers
|
||||
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletState
|
||||
import timber.log.Timber
|
||||
|
||||
internal class RenameWalletTransformer(
|
||||
userWalletId: UserWalletId,
|
||||
private val newName: String,
|
||||
) : WalletStateTransformer(userWalletId) {
|
||||
|
||||
override fun transform(prevState: WalletState): WalletState {
|
||||
return when (prevState) {
|
||||
is WalletState.MultiCurrency.Content -> {
|
||||
prevState.copy(walletCardState = prevState.walletCardState.copySealed(title = newName))
|
||||
}
|
||||
is WalletState.SingleCurrency.Content -> {
|
||||
prevState.copy(walletCardState = prevState.walletCardState.copySealed(title = newName))
|
||||
}
|
||||
is WalletState.MultiCurrency.Locked,
|
||||
is WalletState.SingleCurrency.Locked,
|
||||
-> {
|
||||
Timber.e("Impossible to rename wallet in locked state")
|
||||
prevState
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state2.transformers
|
||||
|
||||
import com.tangem.common.Provider
|
||||
import com.tangem.core.ui.event.consumedEvent
|
||||
import com.tangem.core.ui.event.triggeredEvent
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletEvent
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletScreenState
|
||||
|
||||
internal class ScrollToWalletTransformer(
|
||||
private val index: Int,
|
||||
private val currentStateProvider: Provider<WalletScreenState>,
|
||||
private val stateUpdater: (WalletScreenState) -> Unit,
|
||||
) : WalletScreenStateTransformer {
|
||||
|
||||
override fun transform(prevState: WalletScreenState): WalletScreenState {
|
||||
return prevState.copy(
|
||||
event = triggeredEvent(
|
||||
data = WalletEvent.ChangeWallet(index),
|
||||
onConsume = {
|
||||
stateUpdater(
|
||||
currentStateProvider().copy(
|
||||
selectedWalletIndex = index,
|
||||
event = consumedEvent(),
|
||||
),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state2.transformers
|
||||
|
||||
import com.tangem.core.ui.event.triggeredEvent
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletEvent
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletScreenState
|
||||
|
||||
internal class SendEventTransformer(
|
||||
private val event: WalletEvent,
|
||||
private val onConsume: () -> Unit,
|
||||
) : WalletScreenStateTransformer {
|
||||
|
||||
override fun transform(prevState: WalletScreenState): WalletScreenState {
|
||||
return prevState.copy(
|
||||
event = triggeredEvent(data = event, onConsume = onConsume),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state2.transformers
|
||||
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.tokens.model.TokenActionsState
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletManageButton
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletState
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import timber.log.Timber
|
||||
|
||||
internal class SetCryptoCurrencyActionsTransformer(
|
||||
private val tokenActionsState: TokenActionsState,
|
||||
private val userWallet: UserWallet,
|
||||
private val clickIntents: WalletClickIntentsV2,
|
||||
) : WalletStateTransformer(userWallet.walletId) {
|
||||
|
||||
override fun transform(prevState: WalletState): WalletState {
|
||||
return when (prevState) {
|
||||
is WalletState.SingleCurrency.Content -> {
|
||||
prevState.copy(buttons = tokenActionsState.toManageButtons())
|
||||
}
|
||||
is WalletState.SingleCurrency.Locked,
|
||||
-> {
|
||||
Timber.e("Impossible to load primary currency status for locked wallet")
|
||||
prevState
|
||||
}
|
||||
is WalletState.MultiCurrency,
|
||||
-> {
|
||||
Timber.e("Impossible to load crypto currency actions for multi-currency wallet")
|
||||
prevState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun TokenActionsState.toManageButtons(): PersistentList<WalletManageButton> {
|
||||
return states
|
||||
.filterIfS2C()
|
||||
.mapNotNull { action ->
|
||||
when (action) {
|
||||
is TokenActionsState.ActionState.Buy -> {
|
||||
WalletManageButton.Buy(
|
||||
enabled = action.enabled,
|
||||
onClick = { clickIntents.onBuyClick(cryptoCurrencyStatus) },
|
||||
)
|
||||
}
|
||||
is TokenActionsState.ActionState.Receive -> {
|
||||
WalletManageButton.Receive(
|
||||
enabled = action.enabled,
|
||||
onClick = { clickIntents.onReceiveClick(cryptoCurrencyStatus) },
|
||||
)
|
||||
}
|
||||
is TokenActionsState.ActionState.Sell -> {
|
||||
WalletManageButton.Sell(
|
||||
enabled = action.enabled,
|
||||
onClick = { clickIntents.onSellClick(cryptoCurrencyStatus) },
|
||||
)
|
||||
}
|
||||
is TokenActionsState.ActionState.Send -> {
|
||||
WalletManageButton.Send(
|
||||
enabled = action.enabled,
|
||||
onClick = { clickIntents.onSendClick(cryptoCurrencyStatus) },
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
.toPersistentList()
|
||||
}
|
||||
|
||||
private fun List<TokenActionsState.ActionState>.filterIfS2C(): List<TokenActionsState.ActionState> {
|
||||
return if (userWallet.scanResponse.cardTypesResolver.isStart2Coin()) {
|
||||
filterNot { it is TokenActionsState.ActionState.Buy || it is TokenActionsState.ActionState.Sell }
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state2.transformers
|
||||
|
||||
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.converter.SingleWalletCardStateConverter
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.converter.SingleWalletMarketPriceConverter
|
||||
import timber.log.Timber
|
||||
|
||||
internal class SetPrimaryCurrencyTransformer(
|
||||
private val userWallet: UserWallet,
|
||||
private val status: CryptoCurrencyStatus.Status,
|
||||
private val appCurrency: AppCurrency,
|
||||
) : WalletStateTransformer(userWallet.walletId) {
|
||||
|
||||
override fun transform(prevState: WalletState): WalletState {
|
||||
return when (prevState) {
|
||||
is WalletState.SingleCurrency.Content -> {
|
||||
prevState.copy(
|
||||
walletCardState = prevState.walletCardState.toLoadedState(),
|
||||
marketPriceBlockState = prevState.marketPriceBlockState.toLoadedState(),
|
||||
)
|
||||
}
|
||||
is WalletState.SingleCurrency.Locked,
|
||||
-> {
|
||||
Timber.e("Impossible to load primary currency status for locked wallet")
|
||||
prevState
|
||||
}
|
||||
is WalletState.MultiCurrency,
|
||||
-> {
|
||||
Timber.e("Impossible to load primary currency status for multi-currency wallet")
|
||||
prevState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun WalletCardState.toLoadedState(): WalletCardState {
|
||||
return SingleWalletCardStateConverter(status, userWallet, appCurrency).convert(value = this)
|
||||
}
|
||||
|
||||
private fun MarketPriceBlockState.toLoadedState(): MarketPriceBlockState {
|
||||
return SingleWalletMarketPriceConverter(status, appCurrency).convert(value = this)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state2.transformers
|
||||
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletManageButton
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletPullToRefreshConfig
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletTokensListState
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.mutate
|
||||
|
||||
internal class SetRefreshStateTransformer(
|
||||
userWalletId: UserWalletId,
|
||||
private val isRefreshing: Boolean,
|
||||
) : WalletStateTransformer(userWalletId) {
|
||||
|
||||
override fun transform(prevState: WalletState): WalletState {
|
||||
return when (prevState) {
|
||||
is WalletState.MultiCurrency.Content -> {
|
||||
prevState.copy(
|
||||
pullToRefreshConfig = prevState.pullToRefreshConfig.toUpdatedState(isRefreshing),
|
||||
tokensListState = prevState.tokensListState.toUpdatedState(),
|
||||
)
|
||||
}
|
||||
is WalletState.SingleCurrency.Content -> {
|
||||
prevState.copy(
|
||||
pullToRefreshConfig = prevState.pullToRefreshConfig.toUpdatedState(isRefreshing),
|
||||
buttons = prevState.buttons.toUpdatedState(),
|
||||
)
|
||||
}
|
||||
is WalletState.MultiCurrency.Locked,
|
||||
is WalletState.SingleCurrency.Locked,
|
||||
-> prevState
|
||||
}
|
||||
}
|
||||
|
||||
private fun WalletPullToRefreshConfig.toUpdatedState(isRefreshing: Boolean): WalletPullToRefreshConfig {
|
||||
return copy(isRefreshing = isRefreshing)
|
||||
}
|
||||
|
||||
private fun WalletTokensListState.toUpdatedState(): WalletTokensListState {
|
||||
return if (this is WalletTokensListState.ContentState.Content && organizeTokensButtonConfig != null) {
|
||||
copy(
|
||||
organizeTokensButtonConfig = organizeTokensButtonConfig.copy(
|
||||
isEnabled = !isRefreshing,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
private fun PersistentList<WalletManageButton>.toUpdatedState(): PersistentList<WalletManageButton> {
|
||||
val isButtonsEnabled = !isRefreshing
|
||||
|
||||
return mutate {
|
||||
it.mapNotNull { button ->
|
||||
when (button) {
|
||||
is WalletManageButton.Buy -> button.copy(enabled = isButtonsEnabled)
|
||||
is WalletManageButton.Send -> button.copy(enabled = isButtonsEnabled)
|
||||
is WalletManageButton.Sell -> button.copy(enabled = isButtonsEnabled)
|
||||
is WalletManageButton.Receive -> button
|
||||
is WalletManageButton.Swap -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state2.transformers
|
||||
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletTokensListState
|
||||
import timber.log.Timber
|
||||
|
||||
internal class SetTokenListErrorTransformer(
|
||||
userWalletId: UserWalletId,
|
||||
private val error: TokenListError,
|
||||
) : WalletStateTransformer(userWalletId) {
|
||||
|
||||
override fun transform(prevState: WalletState): WalletState {
|
||||
return when (error) {
|
||||
is TokenListError.EmptyTokens -> {
|
||||
when (prevState) {
|
||||
is WalletState.MultiCurrency.Content -> {
|
||||
prevState.copy(tokensListState = WalletTokensListState.Empty)
|
||||
}
|
||||
is WalletState.MultiCurrency.Locked,
|
||||
-> {
|
||||
Timber.e("Impossible to load tokens list for locked wallet")
|
||||
prevState
|
||||
}
|
||||
is WalletState.SingleCurrency,
|
||||
-> {
|
||||
Timber.e("Impossible to load tokens list for single-currency wallet")
|
||||
prevState
|
||||
}
|
||||
}
|
||||
}
|
||||
is TokenListError.DataError,
|
||||
is TokenListError.UnableToSortTokenList,
|
||||
-> prevState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state2.transformers
|
||||
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.tokens.model.TokenList
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.ManageTokensButtonConfig
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletTokensListState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.converter.MultiWalletCardStateConverter
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.converter.TokenListStateConverter
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
|
||||
import timber.log.Timber
|
||||
|
||||
internal class SetTokenListTransformer(
|
||||
private val tokenList: TokenList,
|
||||
private val userWallet: UserWallet,
|
||||
private val appCurrency: AppCurrency,
|
||||
private val clickIntents: WalletClickIntentsV2,
|
||||
) : WalletStateTransformer(userWallet.walletId) {
|
||||
|
||||
override fun transform(prevState: WalletState): WalletState {
|
||||
return when (prevState) {
|
||||
is WalletState.MultiCurrency.Content -> {
|
||||
prevState.copy(
|
||||
walletCardState = prevState.walletCardState.toLoadedState(),
|
||||
tokensListState = prevState.tokensListState.toLoadedState(),
|
||||
manageTokensButtonConfig = createManageTokensButtonConfig(),
|
||||
)
|
||||
}
|
||||
is WalletState.MultiCurrency.Locked,
|
||||
-> {
|
||||
Timber.e("Impossible to load tokens list for locked wallet")
|
||||
prevState
|
||||
}
|
||||
is WalletState.SingleCurrency,
|
||||
-> {
|
||||
Timber.e("Impossible to load tokens list for single-currency wallet")
|
||||
prevState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun WalletCardState.toLoadedState(): WalletCardState {
|
||||
return MultiWalletCardStateConverter(
|
||||
fiatBalance = tokenList.totalFiatBalance,
|
||||
selectedWallet = userWallet,
|
||||
appCurrency = appCurrency,
|
||||
).convert(value = this)
|
||||
}
|
||||
|
||||
private fun WalletTokensListState.toLoadedState(): WalletTokensListState {
|
||||
return TokenListStateConverter(
|
||||
tokenList = tokenList,
|
||||
selectedWallet = userWallet,
|
||||
appCurrency = appCurrency,
|
||||
clickIntents = clickIntents,
|
||||
).convert(value = this)
|
||||
}
|
||||
|
||||
private fun createManageTokensButtonConfig(): ManageTokensButtonConfig? {
|
||||
return if (userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()) {
|
||||
null
|
||||
} else {
|
||||
ManageTokensButtonConfig(clickIntents::onManageTokensClick)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state2.transformers
|
||||
|
||||
import com.tangem.core.ui.components.transactions.state.TxHistoryState
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.txhistory.models.TxHistoryItem
|
||||
import com.tangem.domain.txhistory.models.TxHistoryStateError
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.converter.TxHistoryItemStateConverter
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import timber.log.Timber
|
||||
|
||||
internal class SetTxHistoryCountErrorTransformer(
|
||||
private val userWallet: UserWallet,
|
||||
private val error: TxHistoryStateError,
|
||||
private val pendingTransactions: Set<TxHistoryItem>,
|
||||
private val clickIntents: WalletClickIntentsV2,
|
||||
) : WalletStateTransformer(userWallet.walletId) {
|
||||
|
||||
private val txHistoryItemConverter by lazy {
|
||||
val blockchain = userWallet.scanResponse.cardTypesResolver.getBlockchain()
|
||||
TxHistoryItemStateConverter(
|
||||
symbol = blockchain.currency,
|
||||
decimals = blockchain.decimals(),
|
||||
clickIntents = clickIntents,
|
||||
)
|
||||
}
|
||||
|
||||
override fun transform(prevState: WalletState): WalletState {
|
||||
return when (prevState) {
|
||||
is WalletState.SingleCurrency.Content -> prevState.toErrorState()
|
||||
is WalletState.SingleCurrency.Locked,
|
||||
-> {
|
||||
Timber.e("Impossible to load transactions history for locked wallet")
|
||||
prevState
|
||||
}
|
||||
is WalletState.MultiCurrency,
|
||||
-> {
|
||||
Timber.e("Impossible to load transactions history for multi-currency wallet")
|
||||
prevState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun WalletState.SingleCurrency.Content.toErrorState(): WalletState {
|
||||
return copy(
|
||||
txHistoryState = when (error) {
|
||||
is TxHistoryStateError.EmptyTxHistories -> {
|
||||
TxHistoryState.Empty(onExploreClick = clickIntents::onExploreClick)
|
||||
}
|
||||
is TxHistoryStateError.DataError -> {
|
||||
TxHistoryState.Error(
|
||||
onReloadClick = clickIntents::onReloadClick,
|
||||
onExploreClick = clickIntents::onExploreClick,
|
||||
)
|
||||
}
|
||||
is TxHistoryStateError.TxHistoryNotImplemented -> {
|
||||
TxHistoryState.NotSupported(
|
||||
pendingTransactions = txHistoryItemConverter.convertList(pendingTransactions)
|
||||
.toImmutableList(),
|
||||
onExploreClick = clickIntents::onExploreClick,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state2.transformers
|
||||
|
||||
import androidx.paging.PagingData
|
||||
import com.tangem.core.ui.components.transactions.state.TransactionState
|
||||
import com.tangem.core.ui.components.transactions.state.TxHistoryState
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletState
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import timber.log.Timber
|
||||
|
||||
internal class SetTxHistoryCountTransformer(
|
||||
userWalletId: UserWalletId,
|
||||
private val transactionsCount: Int,
|
||||
private val clickIntents: WalletClickIntentsV2,
|
||||
) : WalletStateTransformer(userWalletId) {
|
||||
|
||||
override fun transform(prevState: WalletState): WalletState {
|
||||
return when (prevState) {
|
||||
is WalletState.SingleCurrency.Content -> prevState.toLoadingState()
|
||||
is WalletState.SingleCurrency.Locked,
|
||||
-> {
|
||||
Timber.e("Impossible to load transactions history for locked wallet")
|
||||
prevState
|
||||
}
|
||||
is WalletState.MultiCurrency,
|
||||
-> {
|
||||
Timber.e("Impossible to load transactions history for multi-currency wallet")
|
||||
prevState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun WalletState.SingleCurrency.Content.toLoadingState(): WalletState {
|
||||
return if (txHistoryState is TxHistoryState.Content) {
|
||||
(txHistoryState as? TxHistoryState.Content)?.contentItems?.update {
|
||||
Timber.d("Load transactions history: $transactionsCount")
|
||||
PagingData.from(data = createLoadingItems())
|
||||
}
|
||||
this
|
||||
} else {
|
||||
val txHistoryContent = TxHistoryState.Content(
|
||||
contentItems = MutableStateFlow(
|
||||
value = PagingData.from(data = createLoadingItems()),
|
||||
),
|
||||
)
|
||||
copy(txHistoryState = txHistoryContent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createLoadingItems(): List<TxHistoryState.TxHistoryItemState> {
|
||||
return buildList {
|
||||
add(TxHistoryState.TxHistoryItemState.Title(onExploreClick = clickIntents::onExploreClick))
|
||||
(1..transactionsCount).forEach {
|
||||
add(TxHistoryState.TxHistoryItemState.Transaction(state = TransactionState.Loading(it.toString())))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state2.transformers
|
||||
|
||||
import com.tangem.core.ui.components.transactions.state.TxHistoryState
|
||||
import com.tangem.domain.txhistory.models.TxHistoryListError
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletState
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
|
||||
import timber.log.Timber
|
||||
|
||||
internal class SetTxHistoryItemsErrorTransformer(
|
||||
userWalletId: UserWalletId,
|
||||
private val error: TxHistoryListError,
|
||||
private val clickIntents: WalletClickIntentsV2,
|
||||
) : WalletStateTransformer(userWalletId) {
|
||||
|
||||
override fun transform(prevState: WalletState): WalletState {
|
||||
return when (prevState) {
|
||||
is WalletState.SingleCurrency.Content -> {
|
||||
prevState.copy(
|
||||
txHistoryState = when (error) {
|
||||
is TxHistoryListError.DataError -> {
|
||||
TxHistoryState.Error(
|
||||
onReloadClick = clickIntents::onReloadClick,
|
||||
onExploreClick = clickIntents::onExploreClick,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
is WalletState.SingleCurrency.Locked,
|
||||
-> {
|
||||
Timber.e("Impossible to load transactions history for locked wallet")
|
||||
prevState
|
||||
}
|
||||
is WalletState.MultiCurrency -> {
|
||||
Timber.e("Impossible to load transactions history for multi-currency wallet")
|
||||
prevState
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state2.transformers
|
||||
|
||||
import androidx.paging.PagingData
|
||||
import com.tangem.domain.txhistory.models.TxHistoryItem
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.converter.TxHistoryItemFlowConverter
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import timber.log.Timber
|
||||
|
||||
internal class SetTxHistoryItemsTransformer(
|
||||
private val userWallet: UserWallet,
|
||||
private val flow: Flow<PagingData<TxHistoryItem>>,
|
||||
private val clickIntents: WalletClickIntentsV2,
|
||||
) : WalletStateTransformer(userWallet.walletId) {
|
||||
|
||||
override fun transform(prevState: WalletState): WalletState {
|
||||
return when (prevState) {
|
||||
is WalletState.SingleCurrency.Content -> {
|
||||
val converter = TxHistoryItemFlowConverter(
|
||||
userWallet = userWallet,
|
||||
currentState = prevState,
|
||||
clickIntents = clickIntents,
|
||||
)
|
||||
prevState.copy(
|
||||
txHistoryState = converter.convert(value = flow),
|
||||
)
|
||||
}
|
||||
is WalletState.SingleCurrency.Locked,
|
||||
-> {
|
||||
Timber.e("Impossible to load transactions history for locked wallet")
|
||||
prevState
|
||||
}
|
||||
is WalletState.MultiCurrency -> {
|
||||
Timber.e("Impossible to load transactions history for multi-currency wallet")
|
||||
prevState
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state2.transformers
|
||||
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletNotification
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletState
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import timber.log.Timber
|
||||
|
||||
internal class SetWarningsTransformer(
|
||||
userWalletId: UserWalletId,
|
||||
private val warnings: ImmutableList<WalletNotification>,
|
||||
) : WalletStateTransformer(userWalletId) {
|
||||
|
||||
override fun transform(prevState: WalletState): WalletState {
|
||||
return when (prevState) {
|
||||
is WalletState.MultiCurrency.Content -> prevState.copy(warnings = warnings)
|
||||
is WalletState.SingleCurrency.Content -> prevState.copy(warnings = warnings)
|
||||
is WalletState.MultiCurrency.Locked,
|
||||
is WalletState.SingleCurrency.Locked,
|
||||
-> {
|
||||
Timber.e("Impossible to update notifications for locked wallet")
|
||||
prevState
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state2.transformers
|
||||
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTopBarConfig
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletScreenState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.utils.WalletLoadingStateFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import timber.log.Timber
|
||||
|
||||
internal class UnlockWalletTransformer(
|
||||
private val unlockedWallets: List<UserWallet>,
|
||||
private val clickIntents: WalletClickIntentsV2,
|
||||
) : WalletScreenStateTransformer {
|
||||
|
||||
private val walletLoadingStateFactory by lazy { WalletLoadingStateFactory(clickIntents = clickIntents) }
|
||||
|
||||
override fun transform(prevState: WalletScreenState): WalletScreenState {
|
||||
return prevState.copy(
|
||||
topBarConfig = prevState.topBarConfig.toUnlockedState(),
|
||||
wallets = prevState.wallets
|
||||
.map { state ->
|
||||
val unlockedWallet = getUnlockedWallet(state.walletCardState.id)
|
||||
if (unlockedWallet == null) state else createLoadingState(state, unlockedWallet)
|
||||
}
|
||||
.toImmutableList(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun WalletTopBarConfig.toUnlockedState(): WalletTopBarConfig {
|
||||
return copy(onDetailsClick = clickIntents::onDetailsClick)
|
||||
}
|
||||
|
||||
private fun getUnlockedWallet(walletId: UserWalletId): UserWallet? {
|
||||
return unlockedWallets.firstOrNull { it.walletId == walletId }
|
||||
}
|
||||
|
||||
private fun createLoadingState(prevState: WalletState, unlockedWallet: UserWallet): WalletState {
|
||||
return when (prevState) {
|
||||
is WalletState.MultiCurrency.Locked,
|
||||
is WalletState.SingleCurrency.Locked,
|
||||
-> walletLoadingStateFactory.create(userWallet = unlockedWallet)
|
||||
is WalletState.MultiCurrency.Content,
|
||||
is WalletState.SingleCurrency.Content,
|
||||
-> {
|
||||
Timber.e("Impossible to unlock wallet with content state")
|
||||
prevState
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state2.transformers
|
||||
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletScreenState
|
||||
|
||||
internal class UpdateBalanceHidingModeTransformer(
|
||||
private val isHidingMode: Boolean,
|
||||
) : WalletScreenStateTransformer {
|
||||
|
||||
override fun transform(prevState: WalletScreenState): WalletScreenState {
|
||||
return prevState.copy(isHidingMode = isHidingMode)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state2.transformers
|
||||
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.getCardsCount
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletState
|
||||
import timber.log.Timber
|
||||
|
||||
internal class UpdateWalletCardsCountTransformer(
|
||||
private val userWallet: UserWallet,
|
||||
) : WalletStateTransformer(userWallet.walletId) {
|
||||
|
||||
override fun transform(prevState: WalletState): WalletState {
|
||||
return when (prevState) {
|
||||
is WalletState.MultiCurrency.Content -> {
|
||||
prevState.copy(walletCardState = prevState.walletCardState.toUpdatedState())
|
||||
}
|
||||
is WalletState.SingleCurrency.Content -> {
|
||||
prevState.copy(walletCardState = prevState.walletCardState.toUpdatedState())
|
||||
}
|
||||
is WalletState.MultiCurrency.Locked,
|
||||
is WalletState.SingleCurrency.Locked,
|
||||
-> {
|
||||
Timber.e("Impossible to update wallet cards count for locked wallet")
|
||||
prevState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun WalletCardState.toUpdatedState(): WalletCardState {
|
||||
return when (this) {
|
||||
is WalletCardState.Content -> copy(
|
||||
additionalInfo = WalletAdditionalInfoFactory.resolve(wallet = userWallet),
|
||||
imageResId = WalletImageResolver.resolve(userWallet = userWallet),
|
||||
cardCount = userWallet.getCardsCount(),
|
||||
)
|
||||
else -> this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state2.transformers
|
||||
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletScreenState
|
||||
|
||||
internal interface WalletScreenStateTransformer {
|
||||
|
||||
fun transform(prevState: WalletScreenState): WalletScreenState
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state2.transformers
|
||||
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletScreenState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletState
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
internal abstract class WalletStateTransformer(
|
||||
protected val userWalletId: UserWalletId,
|
||||
) : WalletScreenStateTransformer {
|
||||
|
||||
abstract fun transform(prevState: WalletState): WalletState
|
||||
|
||||
override fun transform(prevState: WalletScreenState): WalletScreenState {
|
||||
return prevState.copy(
|
||||
wallets = prevState.wallets
|
||||
.map { state ->
|
||||
if (state.walletCardState.id == userWalletId) transform(state) else state
|
||||
}
|
||||
.toImmutableList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state2.transformers.converter
|
||||
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.model.TokenList
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.getCardsCount
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal class MultiWalletCardStateConverter(
|
||||
private val fiatBalance: TokenList.FiatBalance,
|
||||
private val selectedWallet: UserWallet,
|
||||
private val appCurrency: AppCurrency,
|
||||
) : Converter<WalletCardState, WalletCardState> {
|
||||
|
||||
override fun convert(value: WalletCardState): WalletCardState {
|
||||
return when (fiatBalance) {
|
||||
is TokenList.FiatBalance.Loading -> value.toLoadingState()
|
||||
is TokenList.FiatBalance.Failed -> value.toErrorState()
|
||||
is TokenList.FiatBalance.Loaded -> value.toWalletCardState(fiatBalance)
|
||||
}
|
||||
}
|
||||
|
||||
private fun WalletCardState.toLoadingState(): WalletCardState {
|
||||
return WalletCardState.Loading(
|
||||
id = id,
|
||||
title = title,
|
||||
additionalInfo = additionalInfo,
|
||||
imageResId = imageResId,
|
||||
onRenameClick = onRenameClick,
|
||||
onDeleteClick = onDeleteClick,
|
||||
)
|
||||
}
|
||||
|
||||
private fun WalletCardState.toErrorState(): WalletCardState {
|
||||
return WalletCardState.Error(
|
||||
id = id,
|
||||
title = title,
|
||||
additionalInfo = additionalInfo,
|
||||
imageResId = imageResId,
|
||||
onDeleteClick = onDeleteClick,
|
||||
onRenameClick = onRenameClick,
|
||||
)
|
||||
}
|
||||
|
||||
private fun WalletCardState.toWalletCardState(fiatBalance: TokenList.FiatBalance.Loaded): WalletCardState {
|
||||
return WalletCardState.Content(
|
||||
id = id,
|
||||
title = title,
|
||||
additionalInfo = WalletAdditionalInfoFactory.resolve(wallet = selectedWallet),
|
||||
imageResId = imageResId,
|
||||
onRenameClick = onRenameClick,
|
||||
onDeleteClick = onDeleteClick,
|
||||
balance = BigDecimalFormatter.formatFiatAmount(
|
||||
fiatAmount = fiatBalance.amount,
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
),
|
||||
cardCount = selectedWallet.getCardsCount(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state2.transformers.converter
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.TokenActionsState
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.TokenActionButtonConfig
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletCurrencyActionsClickIntentsImplementor
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.utils.isNullOrZero
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
internal class MultiWalletCurrencyActionsConverter(
|
||||
private val userWallet: UserWallet,
|
||||
private val clickIntents: WalletCurrencyActionsClickIntentsImplementor,
|
||||
) : Converter<TokenActionsState, ImmutableList<TokenActionButtonConfig>> {
|
||||
|
||||
override fun convert(value: TokenActionsState): ImmutableList<TokenActionButtonConfig> {
|
||||
return value.states
|
||||
.filterIfSingleWithToken()
|
||||
.mapNotNull {
|
||||
mapTokenActionState(actionsState = it, cryptoCurrencyStatus = value.cryptoCurrencyStatus)
|
||||
}
|
||||
.toImmutableList()
|
||||
}
|
||||
|
||||
private fun List<TokenActionsState.ActionState>.filterIfSingleWithToken(): List<TokenActionsState.ActionState> {
|
||||
return if (userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()) {
|
||||
filter { it !is TokenActionsState.ActionState.HideToken }
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
private fun mapTokenActionState(
|
||||
actionsState: TokenActionsState.ActionState,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
): TokenActionButtonConfig? {
|
||||
if (actionsState is TokenActionsState.ActionState.Send && cryptoCurrencyStatus.value.amount.isNullOrZero()) {
|
||||
return null
|
||||
}
|
||||
|
||||
val title: TextReference
|
||||
val icon: Int
|
||||
val action: () -> Unit
|
||||
when (actionsState) {
|
||||
is TokenActionsState.ActionState.Buy -> {
|
||||
title = resourceReference(R.string.common_buy)
|
||||
icon = R.drawable.ic_plus_24
|
||||
action = { clickIntents.onBuyClick(cryptoCurrencyStatus) }
|
||||
}
|
||||
is TokenActionsState.ActionState.Receive -> {
|
||||
title = resourceReference(R.string.common_receive)
|
||||
icon = R.drawable.ic_arrow_down_24
|
||||
action = { clickIntents.onReceiveClick(cryptoCurrencyStatus) }
|
||||
}
|
||||
is TokenActionsState.ActionState.Sell -> {
|
||||
title = resourceReference(R.string.common_sell)
|
||||
icon = R.drawable.ic_currency_24
|
||||
action = { clickIntents.onSellClick(cryptoCurrencyStatus) }
|
||||
}
|
||||
is TokenActionsState.ActionState.Send -> {
|
||||
title = resourceReference(R.string.common_send)
|
||||
icon = R.drawable.ic_arrow_up_24
|
||||
action = { clickIntents.onSendClick(cryptoCurrencyStatus) }
|
||||
}
|
||||
is TokenActionsState.ActionState.Swap -> {
|
||||
title = resourceReference(R.string.common_swap)
|
||||
icon = R.drawable.ic_exchange_horizontal_24
|
||||
action = { clickIntents.onSwapClick(cryptoCurrencyStatus) }
|
||||
}
|
||||
is TokenActionsState.ActionState.CopyAddress -> {
|
||||
title = resourceReference(R.string.common_copy_address)
|
||||
icon = R.drawable.ic_copy_24
|
||||
action = { clickIntents.onCopyAddressClick(cryptoCurrencyStatus) }
|
||||
}
|
||||
is TokenActionsState.ActionState.HideToken -> {
|
||||
title = resourceReference(R.string.token_details_hide_token)
|
||||
icon = R.drawable.ic_hide_24
|
||||
action = { clickIntents.onHideTokensClick(cryptoCurrencyStatus) }
|
||||
}
|
||||
}
|
||||
|
||||
return TokenActionButtonConfig(
|
||||
text = title,
|
||||
iconResId = icon,
|
||||
onClick = action,
|
||||
isWarning = actionsState is TokenActionsState.ActionState.HideToken,
|
||||
enabled = actionsState.enabled,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state2.transformers.converter
|
||||
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.getCardsCount
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal class SingleWalletCardStateConverter(
|
||||
private val status: CryptoCurrencyStatus.Status,
|
||||
private val selectedWallet: UserWallet,
|
||||
private val appCurrency: AppCurrency,
|
||||
) : Converter<WalletCardState, WalletCardState> {
|
||||
|
||||
override fun convert(value: WalletCardState): WalletCardState {
|
||||
return when (status) {
|
||||
is CryptoCurrencyStatus.Loading -> value.toLoadingState()
|
||||
is CryptoCurrencyStatus.Custom,
|
||||
is CryptoCurrencyStatus.MissedDerivation,
|
||||
is CryptoCurrencyStatus.Unreachable,
|
||||
-> value.toErrorState()
|
||||
is CryptoCurrencyStatus.NoQuote,
|
||||
is CryptoCurrencyStatus.Loaded,
|
||||
is CryptoCurrencyStatus.NoAccount,
|
||||
is CryptoCurrencyStatus.NoAmount,
|
||||
-> value.toContentState(status)
|
||||
}
|
||||
}
|
||||
|
||||
private fun WalletCardState.toLoadingState(): WalletCardState {
|
||||
return WalletCardState.Loading(
|
||||
id = id,
|
||||
title = title,
|
||||
imageResId = imageResId,
|
||||
onRenameClick = onRenameClick,
|
||||
onDeleteClick = onDeleteClick,
|
||||
)
|
||||
}
|
||||
|
||||
private fun WalletCardState.toErrorState(): WalletCardState {
|
||||
return WalletCardState.Error(
|
||||
id = id,
|
||||
title = title,
|
||||
imageResId = imageResId,
|
||||
onRenameClick = onRenameClick,
|
||||
onDeleteClick = onDeleteClick,
|
||||
)
|
||||
}
|
||||
|
||||
private fun WalletCardState.toContentState(status: CryptoCurrencyStatus.Status): WalletCardState {
|
||||
return WalletCardState.Content(
|
||||
id = id,
|
||||
title = title,
|
||||
additionalInfo = WalletAdditionalInfoFactory.resolve(
|
||||
wallet = selectedWallet,
|
||||
currencyAmount = status.amount,
|
||||
),
|
||||
imageResId = imageResId,
|
||||
onRenameClick = onRenameClick,
|
||||
onDeleteClick = onDeleteClick,
|
||||
balance = formatFiatAmount(status = status, appCurrency = appCurrency),
|
||||
cardCount = selectedWallet.getCardsCount(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun formatFiatAmount(status: CryptoCurrencyStatus.Status, appCurrency: AppCurrency): String {
|
||||
val fiatAmount = status.fiatAmount ?: return BigDecimalFormatter.EMPTY_BALANCE_SIGN
|
||||
|
||||
return BigDecimalFormatter.formatFiatAmount(
|
||||
fiatAmount = fiatAmount,
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state2.transformers.converter
|
||||
|
||||
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeState
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.utils.converter.Converter
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class SingleWalletMarketPriceConverter(
|
||||
private val status: CryptoCurrencyStatus.Status,
|
||||
private val appCurrency: AppCurrency,
|
||||
) : Converter<MarketPriceBlockState, MarketPriceBlockState> {
|
||||
|
||||
override fun convert(value: MarketPriceBlockState): MarketPriceBlockState {
|
||||
return when (status) {
|
||||
CryptoCurrencyStatus.Loading -> MarketPriceBlockState.Loading(value.currencySymbol)
|
||||
is CryptoCurrencyStatus.NoAccount -> value.toNoAccountState()
|
||||
is CryptoCurrencyStatus.Loaded,
|
||||
is CryptoCurrencyStatus.NoAmount,
|
||||
-> value.toContentState()
|
||||
is CryptoCurrencyStatus.Custom,
|
||||
is CryptoCurrencyStatus.MissedDerivation,
|
||||
is CryptoCurrencyStatus.NoQuote,
|
||||
is CryptoCurrencyStatus.Unreachable,
|
||||
-> MarketPriceBlockState.Error(value.currencySymbol)
|
||||
}
|
||||
}
|
||||
|
||||
private fun MarketPriceBlockState.toNoAccountState(): MarketPriceBlockState {
|
||||
return if (status.fiatRate == null) MarketPriceBlockState.Error(currencySymbol) else toContentState()
|
||||
}
|
||||
|
||||
private fun MarketPriceBlockState.toContentState(): MarketPriceBlockState {
|
||||
return MarketPriceBlockState.Content(
|
||||
currencySymbol = currencySymbol,
|
||||
price = formatPrice(status = status, appCurrency = appCurrency),
|
||||
priceChangeConfig = PriceChangeState.Content(
|
||||
valueInPercent = formatPriceChange(status = status),
|
||||
type = getPriceChangeType(status = status),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun formatPrice(status: CryptoCurrencyStatus.Status, appCurrency: AppCurrency): String {
|
||||
val fiatRate = status.fiatRate ?: return BigDecimalFormatter.EMPTY_BALANCE_SIGN
|
||||
|
||||
return BigDecimalFormatter.formatFiatAmount(
|
||||
fiatAmount = fiatRate,
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
)
|
||||
}
|
||||
|
||||
private fun formatPriceChange(status: CryptoCurrencyStatus.Status): String {
|
||||
val priceChange = status.priceChange ?: return BigDecimalFormatter.EMPTY_BALANCE_SIGN
|
||||
|
||||
return BigDecimalFormatter.formatPercent(percent = priceChange, useAbsoluteValue = true)
|
||||
}
|
||||
|
||||
private fun getPriceChangeType(status: CryptoCurrencyStatus.Status): PriceChangeType {
|
||||
val priceChange = status.priceChange ?: return PriceChangeType.DOWN
|
||||
|
||||
return if (priceChange > BigDecimal.ZERO) PriceChangeType.UP else PriceChangeType.DOWN
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state2.transformers.converter
|
||||
|
||||
import com.tangem.common.Provider
|
||||
import com.tangem.core.ui.components.currency.tokenicon.converter.CryptoCurrencyToIconStateConverter
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
|
||||
import com.tangem.utils.converter.Converter
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class TokenItemStateConverter(
|
||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||
private val clickIntents: WalletClickIntentsV2,
|
||||
) : Converter<CryptoCurrencyStatus, TokenItemState> {
|
||||
|
||||
private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter)
|
||||
|
||||
override fun convert(value: CryptoCurrencyStatus): TokenItemState {
|
||||
return when (value.value) {
|
||||
is CryptoCurrencyStatus.Loading -> value.mapToLoadingState()
|
||||
is CryptoCurrencyStatus.Loaded,
|
||||
is CryptoCurrencyStatus.Custom,
|
||||
is CryptoCurrencyStatus.NoQuote,
|
||||
is CryptoCurrencyStatus.NoAccount,
|
||||
-> value.mapToTokenItemState()
|
||||
is CryptoCurrencyStatus.MissedDerivation -> value.mapToNoAddressTokenItemState()
|
||||
is CryptoCurrencyStatus.Unreachable,
|
||||
is CryptoCurrencyStatus.NoAmount,
|
||||
-> value.mapToUnreachableTokenItemState()
|
||||
}
|
||||
}
|
||||
|
||||
private fun CryptoCurrencyStatus.mapToLoadingState(): TokenItemState.Loading {
|
||||
return TokenItemState.Loading(
|
||||
id = currency.id.value,
|
||||
iconState = iconStateConverter.convert(value = this),
|
||||
titleState = TokenItemState.TitleState.Content(text = currency.name),
|
||||
)
|
||||
}
|
||||
|
||||
private fun CryptoCurrencyStatus.mapToTokenItemState(): TokenItemState.Content {
|
||||
return TokenItemState.Content(
|
||||
id = currency.id.value,
|
||||
iconState = iconStateConverter.convert(value = this),
|
||||
titleState = TokenItemState.TitleState.Content(
|
||||
text = currency.name,
|
||||
hasPending = value.hasCurrentNetworkTransactions,
|
||||
),
|
||||
fiatAmountState = TokenItemState.FiatAmountState.Content(
|
||||
text = getFormattedFiatAmount(),
|
||||
),
|
||||
cryptoAmountState = TokenItemState.CryptoAmountState.Content(text = getFormattedAmount()),
|
||||
cryptoPriceState = getCryptoPriceState(),
|
||||
onItemClick = { clickIntents.onTokenItemClick(currency) },
|
||||
onItemLongClick = { clickIntents.onTokenItemLongClick(cryptoCurrencyStatus = this) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun CryptoCurrencyStatus.getFormattedAmount(): String {
|
||||
val amount = value.amount ?: return TokenItemState.UNKNOWN_AMOUNT_SIGN
|
||||
|
||||
return BigDecimalFormatter.formatCryptoAmount(amount, currency.symbol, currency.decimals)
|
||||
}
|
||||
|
||||
private fun CryptoCurrencyStatus.getFormattedFiatAmount(): String {
|
||||
val fiatAmount = value.fiatAmount ?: return TokenItemState.UNKNOWN_AMOUNT_SIGN
|
||||
val appCurrency = appCurrencyProvider()
|
||||
|
||||
return BigDecimalFormatter.formatFiatAmount(fiatAmount, appCurrency.code, appCurrency.symbol)
|
||||
}
|
||||
|
||||
private fun CryptoCurrencyStatus.mapToUnreachableTokenItemState() = TokenItemState.Unreachable(
|
||||
id = currency.id.value,
|
||||
iconState = iconStateConverter.convert(value = this),
|
||||
titleState = TokenItemState.TitleState.Content(text = currency.name),
|
||||
onItemClick = { clickIntents.onTokenItemClick(currency) },
|
||||
onItemLongClick = { clickIntents.onTokenItemLongClick(cryptoCurrencyStatus = this) },
|
||||
)
|
||||
|
||||
private fun CryptoCurrencyStatus.mapToNoAddressTokenItemState() = TokenItemState.NoAddress(
|
||||
id = currency.id.value,
|
||||
iconState = iconStateConverter.convert(this),
|
||||
titleState = TokenItemState.TitleState.Content(text = currency.name),
|
||||
onItemLongClick = { clickIntents.onTokenItemLongClick(cryptoCurrencyStatus = this) },
|
||||
)
|
||||
|
||||
private fun CryptoCurrencyStatus.getCryptoPriceState(): TokenItemState.CryptoPriceState {
|
||||
val fiatRate = value.fiatRate
|
||||
val priceChange = value.priceChange
|
||||
|
||||
return if (fiatRate != null && priceChange != null) {
|
||||
TokenItemState.CryptoPriceState.Content(
|
||||
price = fiatRate.getFormattedCryptoPrice(),
|
||||
priceChangePercent = BigDecimalFormatter.formatPercent(
|
||||
percent = priceChange,
|
||||
useAbsoluteValue = true,
|
||||
maxFractionDigits = 1,
|
||||
minFractionDigits = 1,
|
||||
),
|
||||
type = priceChange.getPriceChangeType(),
|
||||
)
|
||||
} else {
|
||||
TokenItemState.CryptoPriceState.Unknown
|
||||
}
|
||||
}
|
||||
|
||||
private fun BigDecimal.getFormattedCryptoPrice(): String {
|
||||
val appCurrency = appCurrencyProvider()
|
||||
return BigDecimalFormatter.formatFiatAmount(
|
||||
fiatAmount = this,
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
)
|
||||
}
|
||||
|
||||
private fun BigDecimal.getPriceChangeType(): PriceChangeType {
|
||||
return if (this > BigDecimal.ZERO) PriceChangeType.UP else PriceChangeType.DOWN
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state2.transformers.converter
|
||||
|
||||
import com.tangem.common.Provider
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.NetworkGroup
|
||||
import com.tangem.domain.tokens.model.TokenList
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletTokensListState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletTokensListState.TokensListItemState
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.mutate
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletTokensListState.OrganizeTokensButtonConfig as WalletOrganizeTokensButtonConfig
|
||||
|
||||
internal class TokenListStateConverter(
|
||||
private val tokenList: TokenList,
|
||||
private val selectedWallet: UserWallet,
|
||||
private val appCurrency: AppCurrency,
|
||||
private val clickIntents: WalletClickIntentsV2,
|
||||
) : Converter<WalletTokensListState, WalletTokensListState> {
|
||||
|
||||
private val tokenStatusConverter = TokenItemStateConverter(
|
||||
appCurrencyProvider = Provider { appCurrency },
|
||||
clickIntents = clickIntents,
|
||||
)
|
||||
|
||||
override fun convert(value: WalletTokensListState): WalletTokensListState {
|
||||
return when (tokenList) {
|
||||
is TokenList.Empty -> WalletTokensListState.Empty
|
||||
is TokenList.GroupedByNetwork -> WalletTokensListState.ContentState.Content(
|
||||
items = tokenList.toGroupedItems(),
|
||||
organizeTokensButtonConfig = getOrganizeTokensButtonState(
|
||||
currenciesSize = tokenList.groups.flatMap(NetworkGroup::currencies).size,
|
||||
),
|
||||
)
|
||||
is TokenList.Ungrouped -> WalletTokensListState.ContentState.Content(
|
||||
items = tokenList.toUngroupedItems(),
|
||||
organizeTokensButtonConfig = getOrganizeTokensButtonState(currenciesSize = tokenList.currencies.size),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun TokenList.GroupedByNetwork.toGroupedItems(): PersistentList<TokensListItemState> {
|
||||
return groups.fold(initial = persistentListOf()) { acc, group ->
|
||||
acc.mutate { it.addGroup(group) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun TokenList.Ungrouped.toUngroupedItems(): PersistentList<TokensListItemState> {
|
||||
return currencies.fold(initial = persistentListOf()) { acc, token ->
|
||||
acc.mutate { it.addToken(token) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<TokensListItemState>.addGroup(group: NetworkGroup): List<TokensListItemState> {
|
||||
val groupTitle = TokensListItemState.NetworkGroupTitle(
|
||||
id = group.network.hashCode(),
|
||||
name = stringReference(group.network.name),
|
||||
)
|
||||
|
||||
add(groupTitle)
|
||||
group.currencies.forEach { token -> addToken(token) }
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
private fun MutableList<TokensListItemState>.addToken(token: CryptoCurrencyStatus): List<TokensListItemState> {
|
||||
val tokenItemState = tokenStatusConverter.convert(token)
|
||||
add(TokensListItemState.Token(tokenItemState))
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
private fun getOrganizeTokensButtonState(currenciesSize: Int): WalletOrganizeTokensButtonConfig? {
|
||||
return if (currenciesSize > 1 && !isSingleCurrencyWalletWithToken()) {
|
||||
WalletOrganizeTokensButtonConfig(
|
||||
isEnabled = tokenList.totalFiatBalance !is TokenList.FiatBalance.Loading,
|
||||
onClick = clickIntents::onOrganizeTokensClick,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun isSingleCurrencyWalletWithToken(): Boolean {
|
||||
return selectedWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state2.transformers.converter
|
||||
|
||||
import androidx.paging.*
|
||||
import com.tangem.core.ui.components.transactions.state.TransactionState
|
||||
import com.tangem.core.ui.components.transactions.state.TxHistoryState
|
||||
import com.tangem.core.ui.components.transactions.state.TxHistoryState.TxHistoryItemState
|
||||
import com.tangem.core.ui.utils.toDateFormat
|
||||
import com.tangem.core.ui.utils.toTimeFormat
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.txhistory.models.TxHistoryItem
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletState
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.*
|
||||
import java.util.UUID
|
||||
|
||||
private val scope = CoroutineScope(Dispatchers.IO)
|
||||
|
||||
internal class TxHistoryItemFlowConverter(
|
||||
private val userWallet: UserWallet,
|
||||
private val currentState: WalletState.SingleCurrency.Content,
|
||||
private val clickIntents: WalletClickIntentsV2,
|
||||
) : Converter<Flow<PagingData<TxHistoryItem>>, TxHistoryState?> {
|
||||
|
||||
private val txHistoryItemConverter by lazy {
|
||||
val blockchain = userWallet.scanResponse.cardTypesResolver.getBlockchain()
|
||||
TxHistoryItemStateConverter(
|
||||
symbol = blockchain.currency,
|
||||
decimals = blockchain.decimals(),
|
||||
clickIntents = clickIntents,
|
||||
)
|
||||
}
|
||||
|
||||
override fun convert(value: Flow<PagingData<TxHistoryItem>>): TxHistoryState {
|
||||
val txHistoryContent = currentState.txHistoryState as? TxHistoryState.Content
|
||||
?: TxHistoryState.Content(contentItems = MutableStateFlow(PagingData.empty()))
|
||||
|
||||
// FIXME: TxHistoryRepository should send loading transactions
|
||||
// [REDACTED_JIRA]
|
||||
value
|
||||
.onEach { txHistoryStatePagingData ->
|
||||
txHistoryContent.contentItems.update {
|
||||
txHistoryStatePagingData
|
||||
.map<TxHistoryItem, TxHistoryItemState> { item ->
|
||||
// [createTransactionState] returns timestamp without formatting
|
||||
TxHistoryItemState.Transaction(state = createTransactionState(item))
|
||||
}
|
||||
.insertHeaderItem(
|
||||
terminalSeparatorType = TerminalSeparatorType.SOURCE_COMPLETE,
|
||||
item = TxHistoryItemState.Title(clickIntents::onExploreClick),
|
||||
)
|
||||
.insertGroupTitle() // method uses the raw timestamp
|
||||
.formatTransactionsTimestamp() // method formats the timestamp
|
||||
}
|
||||
}
|
||||
.cachedIn(scope)
|
||||
.launchIn(scope)
|
||||
|
||||
return txHistoryContent
|
||||
}
|
||||
|
||||
private fun createTransactionState(item: TxHistoryItem): TransactionState {
|
||||
return txHistoryItemConverter.convert(value = item)
|
||||
}
|
||||
|
||||
private fun PagingData<TxHistoryItemState>.insertGroupTitle(): PagingData<TxHistoryItemState> {
|
||||
return insertSeparators(terminalSeparatorType = TerminalSeparatorType.SOURCE_COMPLETE) { before, after ->
|
||||
// Use raw timestamp to get date
|
||||
|
||||
// If [afterDate] is the first transaction in the flow, add the group title
|
||||
val afterDate = after.getTimestamp()?.toDateFormat() ?: return@insertSeparators null
|
||||
if (before is TxHistoryItemState.Title) {
|
||||
return@insertSeparators TxHistoryItemState.GroupTitle(afterDate, itemKey = UUID.randomUUID().toString())
|
||||
}
|
||||
|
||||
/*
|
||||
* If [beforeDate] is not equals to [afterDate], then [afterDate] is first transaction in
|
||||
* the new group
|
||||
*/
|
||||
val beforeDate = before.getTimestamp()?.toDateFormat() ?: return@insertSeparators null
|
||||
return@insertSeparators if (beforeDate != afterDate) {
|
||||
TxHistoryItemState.GroupTitle(afterDate, itemKey = UUID.randomUUID().toString())
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map the [PagingData] to format the [TxHistoryItemState] timestamp
|
||||
*/
|
||||
private fun PagingData<TxHistoryItemState>.formatTransactionsTimestamp(): PagingData<TxHistoryItemState> {
|
||||
return map { txHistoryItemState ->
|
||||
if (txHistoryItemState is TxHistoryItemState.Transaction &&
|
||||
txHistoryItemState.state is TransactionState.Content
|
||||
) {
|
||||
val txContent = txHistoryItemState.state as TransactionState.Content
|
||||
txHistoryItemState.copy(
|
||||
state = txContent.copy(timestamp = txContent.timestamp.toLong().toTimeFormat()),
|
||||
)
|
||||
} else {
|
||||
txHistoryItemState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun TxHistoryItemState?.getTimestamp(): Long? {
|
||||
return if (this is TxHistoryItemState.Transaction && this.state is TransactionState.Content) {
|
||||
val txContent = this.state as TransactionState.Content
|
||||
requireNotNull(txContent.timestamp.toLongOrNull()) { "Timestamp must be Long type" }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state2.transformers.converter
|
||||
|
||||
import com.tangem.core.ui.components.transactions.state.TransactionState
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.domain.txhistory.models.TxHistoryItem
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.utils.toBriefAddressFormat
|
||||
import com.tangem.utils.toFormattedCurrencyString
|
||||
|
||||
internal class TxHistoryItemStateConverter(
|
||||
private val symbol: String,
|
||||
private val decimals: Int,
|
||||
private val clickIntents: WalletClickIntentsV2,
|
||||
) : Converter<TxHistoryItem, TransactionState> {
|
||||
|
||||
override fun convert(value: TxHistoryItem): TransactionState {
|
||||
return createTransactionStateItem(item = value)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
private fun createTransactionStateItem(item: TxHistoryItem): TransactionState {
|
||||
return TransactionState.Content(
|
||||
txHash = item.txHash,
|
||||
amount = item.getAmount(),
|
||||
timestamp = item.getRawTimestamp(),
|
||||
status = item.status.tiUiStatus(),
|
||||
direction = item.extractDirection(),
|
||||
iconRes = item.extractIcon(),
|
||||
title = item.extractTitle(),
|
||||
subtitle = item.extractSubtitle(),
|
||||
onClick = { clickIntents.onTransactionClick(item.txHash) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun TxHistoryItem.extractIcon(): Int = if (status == TxHistoryItem.TransactionStatus.Failed) {
|
||||
R.drawable.ic_close_24
|
||||
} else {
|
||||
when (type) {
|
||||
is TxHistoryItem.TransactionType.Approve -> R.drawable.ic_doc_24
|
||||
is TxHistoryItem.TransactionType.Operation,
|
||||
is TxHistoryItem.TransactionType.Swap,
|
||||
is TxHistoryItem.TransactionType.Transfer,
|
||||
is TxHistoryItem.TransactionType.UnknownOperation,
|
||||
-> if (isOutgoing) R.drawable.ic_arrow_up_24 else R.drawable.ic_arrow_down_24
|
||||
}
|
||||
}
|
||||
|
||||
private fun TxHistoryItem.extractTitle(): TextReference = when (val type = type) {
|
||||
is TxHistoryItem.TransactionType.Approve -> resourceReference(R.string.common_approval)
|
||||
is TxHistoryItem.TransactionType.Operation -> stringReference(type.name)
|
||||
is TxHistoryItem.TransactionType.Swap -> resourceReference(R.string.common_swap)
|
||||
is TxHistoryItem.TransactionType.Transfer -> resourceReference(R.string.common_transfer)
|
||||
is TxHistoryItem.TransactionType.UnknownOperation -> resourceReference(R.string.transaction_history_operation)
|
||||
}
|
||||
|
||||
private fun TxHistoryItem.extractSubtitle(): TextReference =
|
||||
when (val interactionAddress = interactionAddressType) {
|
||||
is TxHistoryItem.InteractionAddressType.Contract -> resourceReference(
|
||||
id = R.string.transaction_history_contract_address,
|
||||
formatArgs = wrappedList(interactionAddress.address.toBriefAddressFormat()),
|
||||
)
|
||||
is TxHistoryItem.InteractionAddressType.Multiple -> resourceReference(
|
||||
id = if (isOutgoing) {
|
||||
R.string.transaction_history_transaction_to_address
|
||||
} else {
|
||||
R.string.transaction_history_transaction_from_address
|
||||
},
|
||||
formatArgs = wrappedList(resourceReference(R.string.transaction_history_multiple_addresses)),
|
||||
)
|
||||
is TxHistoryItem.InteractionAddressType.User -> resourceReference(
|
||||
id = if (isOutgoing) {
|
||||
R.string.transaction_history_transaction_to_address
|
||||
} else {
|
||||
R.string.transaction_history_transaction_from_address
|
||||
},
|
||||
formatArgs = wrappedList(interactionAddress.address.toBriefAddressFormat()),
|
||||
)
|
||||
}
|
||||
|
||||
private fun TxHistoryItem.extractDirection() =
|
||||
if (isOutgoing) TransactionState.Content.Direction.OUTGOING else TransactionState.Content.Direction.INCOMING
|
||||
|
||||
/**
|
||||
* Get timestamp without formatting.
|
||||
* It's life hack that help us to add transaction's group title to flow.
|
||||
*
|
||||
* @see [convert]
|
||||
*/
|
||||
private fun TxHistoryItem.getRawTimestamp() = this.timestampInMillis.toString()
|
||||
|
||||
private fun TxHistoryItem.TransactionStatus.tiUiStatus() = when (this) {
|
||||
TxHistoryItem.TransactionStatus.Confirmed -> TransactionState.Content.Status.Confirmed
|
||||
TxHistoryItem.TransactionStatus.Failed -> TransactionState.Content.Status.Failed
|
||||
TxHistoryItem.TransactionStatus.Unconfirmed -> TransactionState.Content.Status.Unconfirmed
|
||||
}
|
||||
|
||||
private fun TxHistoryItem.getAmount(): String {
|
||||
val prefix = when (status) {
|
||||
TxHistoryItem.TransactionStatus.Failed -> ""
|
||||
else -> if (isOutgoing) "-" else "+"
|
||||
}
|
||||
return prefix + amount.toFormattedCurrencyString(currency = symbol, decimals = decimals)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state2.utils
|
||||
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletState
|
||||
|
||||
internal inline fun UserWallet.createStateByWalletType(
|
||||
multiCurrencyCreator: () -> WalletState.MultiCurrency,
|
||||
singleCurrencyCreator: () -> WalletState.SingleCurrency,
|
||||
): WalletState {
|
||||
return if (isWalletWithTokens()) multiCurrencyCreator() else singleCurrencyCreator()
|
||||
}
|
||||
|
||||
private fun UserWallet.isWalletWithTokens(): Boolean {
|
||||
return isMultiCurrency || scanResponse.cardTypesResolver.isSingleWalletWithToken()
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state2.utils
|
||||
|
||||
import com.tangem.core.ui.event.consumedEvent
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletEvent
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateHolderV2
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.SendEventTransformer
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Component for sending events [WalletEvent] on WalletScreen
|
||||
*
|
||||
* @property stateHolder state holder for changing state
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class WalletEventSender @Inject constructor(
|
||||
private val stateHolder: WalletStateHolderV2,
|
||||
) {
|
||||
|
||||
fun send(event: WalletEvent) {
|
||||
stateHolder.update(transformer = SendEventTransformer(event = event, onConsume = ::onConsume))
|
||||
}
|
||||
|
||||
private fun onConsume() {
|
||||
stateHolder.update {
|
||||
it.copy(event = consumedEvent())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state2.utils
|
||||
|
||||
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
|
||||
import com.tangem.core.ui.components.transactions.state.TxHistoryState
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletManageButton
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletPullToRefreshConfig
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.ManageTokensButtonConfig
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletTokensListState
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
/**
|
||||
* Factory for creating loading state [WalletState]
|
||||
*
|
||||
* @property clickIntents click intents
|
||||
*/
|
||||
internal class WalletLoadingStateFactory(private val clickIntents: WalletClickIntentsV2) {
|
||||
|
||||
fun create(userWallet: UserWallet): WalletState {
|
||||
return userWallet.createStateByWalletType(
|
||||
multiCurrencyCreator = { createLoadingMultiCurrencyContent(userWallet) },
|
||||
singleCurrencyCreator = { createLoadingSingleCurrencyContent(userWallet) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun createLoadingMultiCurrencyContent(userWallet: UserWallet): WalletState.MultiCurrency.Content {
|
||||
return WalletState.MultiCurrency.Content(
|
||||
pullToRefreshConfig = createPullToRefreshConfig(),
|
||||
walletCardState = userWallet.toLoadingWalletCardState(),
|
||||
warnings = persistentListOf(),
|
||||
bottomSheetConfig = null,
|
||||
tokensListState = WalletTokensListState.ContentState.Loading,
|
||||
manageTokensButtonConfig = ManageTokensButtonConfig(clickIntents::onManageTokensClick),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createLoadingSingleCurrencyContent(userWallet: UserWallet): WalletState.SingleCurrency.Content {
|
||||
val currencySymbol = userWallet.scanResponse.cardTypesResolver.getBlockchain().currency
|
||||
return WalletState.SingleCurrency.Content(
|
||||
pullToRefreshConfig = createPullToRefreshConfig(),
|
||||
walletCardState = userWallet.toLoadingWalletCardState(),
|
||||
warnings = persistentListOf(),
|
||||
bottomSheetConfig = null,
|
||||
buttons = createDisabledButtons(),
|
||||
marketPriceBlockState = MarketPriceBlockState.Loading(currencySymbol = currencySymbol),
|
||||
txHistoryState = TxHistoryState.Content(
|
||||
contentItems = MutableStateFlow(
|
||||
value = TxHistoryState.getDefaultLoadingTransactions(clickIntents::onExploreClick),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createPullToRefreshConfig(): WalletPullToRefreshConfig {
|
||||
return WalletPullToRefreshConfig(onRefresh = clickIntents::onRefreshSwipe, isRefreshing = false)
|
||||
}
|
||||
|
||||
private fun UserWallet.toLoadingWalletCardState(): WalletCardState {
|
||||
return WalletCardState.Loading(
|
||||
id = walletId,
|
||||
title = name,
|
||||
additionalInfo = if (isMultiCurrency) WalletAdditionalInfoFactory.resolve(wallet = this) else null,
|
||||
imageResId = WalletImageResolver.resolve(userWallet = this),
|
||||
onRenameClick = clickIntents::onRenameClick,
|
||||
onDeleteClick = clickIntents::onDeleteBeforeConfirmationClick,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createDisabledButtons(): PersistentList<WalletManageButton> {
|
||||
return persistentListOf(
|
||||
WalletManageButton.Buy(enabled = false, onClick = {}),
|
||||
WalletManageButton.Send(enabled = false, onClick = {}),
|
||||
WalletManageButton.Receive(enabled = false, onClick = {}),
|
||||
WalletManageButton.Sell(enabled = false, onClick = {}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.subscribers
|
||||
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarningsFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletNotification
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateHolderV2
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.SetWarningsTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.conflate
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
internal class MultiWalletWarningsSubscriber(
|
||||
private val userWalletId: UserWalletId,
|
||||
private val stateHolder: WalletStateHolderV2,
|
||||
private val clickIntents: WalletClickIntentsV2,
|
||||
private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory,
|
||||
) : WalletSubscriber<ImmutableList<WalletNotification>>(name = "multi_wallet_warnings") {
|
||||
|
||||
override fun create(
|
||||
coroutineScope: CoroutineScope,
|
||||
uiDispatcher: CoroutineContext,
|
||||
): Flow<ImmutableList<WalletNotification>> {
|
||||
return getMultiWalletWarningsFactory.create(clickIntents)
|
||||
.conflate()
|
||||
.distinctUntilChanged()
|
||||
.onEach {
|
||||
stateHolder.update(
|
||||
SetWarningsTransformer(userWalletId = userWalletId, warnings = it),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.subscribers
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.settings.SetWalletWithFundsFoundUseCase
|
||||
import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase
|
||||
import com.tangem.domain.tokens.error.CurrencyStatusError
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateHolderV2
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.SetPrimaryCurrencyTransformer
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.conflate
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import java.math.BigDecimal
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
internal class PrimaryCurrencySubscriber(
|
||||
private val userWallet: UserWallet,
|
||||
private val appCurrency: AppCurrency,
|
||||
private val stateHolder: WalletStateHolderV2,
|
||||
private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase,
|
||||
private val setWalletWithFundsFoundUseCase: SetWalletWithFundsFoundUseCase,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
) : WalletSubscriber<Either<CurrencyStatusError, CryptoCurrencyStatus>>(name = "primary_currency") {
|
||||
|
||||
override fun create(
|
||||
coroutineScope: CoroutineScope,
|
||||
uiDispatcher: CoroutineContext,
|
||||
): Flow<Either<CurrencyStatusError, CryptoCurrencyStatus>> {
|
||||
return getPrimaryCurrencyStatusUpdatesUseCase(userWallet.walletId)
|
||||
.conflate()
|
||||
.distinctUntilChanged()
|
||||
.onEach(::updateContent)
|
||||
.onEach(::sendAnalyticsEvent)
|
||||
.onEach(::checkWalletWithFunds)
|
||||
}
|
||||
|
||||
private fun updateContent(maybeCurrencyStatus: Either<CurrencyStatusError, CryptoCurrencyStatus>) {
|
||||
val status = (maybeCurrencyStatus as? Either.Right)?.value ?: return
|
||||
|
||||
stateHolder.update(
|
||||
SetPrimaryCurrencyTransformer(
|
||||
status = status.value,
|
||||
userWallet = userWallet,
|
||||
appCurrency = appCurrency,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun sendAnalyticsEvent(maybeCurrencyStatus: Either<CurrencyStatusError, CryptoCurrencyStatus>) {
|
||||
val status = (maybeCurrencyStatus as? Either.Right)?.value ?: return
|
||||
|
||||
val fiatAmount = status.value.fiatAmount
|
||||
val cardBalanceState = when (status.value) {
|
||||
is CryptoCurrencyStatus.Loaded,
|
||||
is CryptoCurrencyStatus.NoAccount,
|
||||
is CryptoCurrencyStatus.NoAmount,
|
||||
-> createCardBalanceState(fiatAmount)
|
||||
is CryptoCurrencyStatus.NoQuote -> AnalyticsParam.CardBalanceState.NoRate
|
||||
is CryptoCurrencyStatus.Unreachable -> AnalyticsParam.CardBalanceState.BlockchainError
|
||||
is CryptoCurrencyStatus.MissedDerivation,
|
||||
is CryptoCurrencyStatus.Loading,
|
||||
is CryptoCurrencyStatus.Custom,
|
||||
-> null
|
||||
}
|
||||
|
||||
cardBalanceState?.let {
|
||||
analyticsEventHandler.send(event = WalletScreenAnalyticsEvent.Basic.BalanceLoaded(balance = it))
|
||||
}
|
||||
}
|
||||
|
||||
private fun createCardBalanceState(fiatAmount: BigDecimal?): AnalyticsParam.CardBalanceState? {
|
||||
return when {
|
||||
fiatAmount == null -> null
|
||||
fiatAmount.isZero() -> AnalyticsParam.CardBalanceState.Empty
|
||||
else -> AnalyticsParam.CardBalanceState.Full
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun checkWalletWithFunds(maybeCurrencyStatus: Either<CurrencyStatusError, CryptoCurrencyStatus>) {
|
||||
val status = (maybeCurrencyStatus as? Either.Right)?.value ?: return
|
||||
|
||||
if (status.value.amount?.isZero() == false) setWalletWithFundsFoundUseCase()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.subscribers
|
||||
|
||||
import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase
|
||||
import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase
|
||||
import com.tangem.domain.tokens.model.TokenActionsState
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.collectLatest
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateHolderV2
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.SetCryptoCurrencyActionsTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
internal class SingleWalletButtonsSubscriber(
|
||||
private val userWallet: UserWallet,
|
||||
private val stateHolder: WalletStateHolderV2,
|
||||
private val clickIntents: WalletClickIntentsV2,
|
||||
private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase,
|
||||
private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCase,
|
||||
) : WalletSubscriber<TokenActionsState>(name = "single_wallet_buttons") {
|
||||
|
||||
override fun create(coroutineScope: CoroutineScope, uiDispatcher: CoroutineContext): Flow<TokenActionsState> {
|
||||
return channelFlow {
|
||||
getPrimaryCurrencyStatusUpdatesUseCase.collectLatest(userWalletId = userWallet.walletId) { status ->
|
||||
getCryptoCurrencyActionsUseCase(userWallet = userWallet, cryptoCurrencyStatus = status)
|
||||
.conflate()
|
||||
.distinctUntilChanged()
|
||||
.firstOrNull()
|
||||
?.let { send(it) }
|
||||
}
|
||||
}
|
||||
.onEach(::updateContent)
|
||||
}
|
||||
|
||||
private fun updateContent(tokenActionsState: TokenActionsState) {
|
||||
stateHolder.update(
|
||||
SetCryptoCurrencyActionsTransformer(
|
||||
tokenActionsState = tokenActionsState,
|
||||
userWallet = userWallet,
|
||||
clickIntents = clickIntents,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.subscribers
|
||||
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.GetSingleWalletWarningsFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletNotification
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateHolderV2
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.SetWarningsTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.conflate
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class SingleWalletNotificationsSubscriber(
|
||||
private val userWalletId: UserWalletId,
|
||||
private val stateHolder: WalletStateHolderV2,
|
||||
private val getSingleWalletWarningsFactory: GetSingleWalletWarningsFactory,
|
||||
private val clickIntents: WalletClickIntentsV2,
|
||||
) : WalletSubscriber<ImmutableList<WalletNotification>>(name = "single_wallet_warnings") {
|
||||
|
||||
override fun create(
|
||||
coroutineScope: CoroutineScope,
|
||||
uiDispatcher: CoroutineContext,
|
||||
): Flow<ImmutableList<WalletNotification>> {
|
||||
return getSingleWalletWarningsFactory.create(clickIntents)
|
||||
.conflate()
|
||||
.distinctUntilChanged()
|
||||
.onEach {
|
||||
stateHolder.update(
|
||||
SetWarningsTransformer(userWalletId = userWalletId, warnings = it),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.subscribers
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.GetCardTokensListUseCase
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.tokens.model.TokenList
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateHolderV2
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.SetTokenListErrorTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.SetTokenListTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.conflate
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class SingleWalletWithTokenListSubscriber(
|
||||
private val userWallet: UserWallet,
|
||||
private val appCurrency: AppCurrency,
|
||||
private val stateHolder: WalletStateHolderV2,
|
||||
private val clickIntents: WalletClickIntentsV2,
|
||||
private val tokenListAnalyticsSender: TokenListAnalyticsSender,
|
||||
private val walletWithFundsChecker: WalletWithFundsChecker,
|
||||
private val getCardTokensListUseCase: GetCardTokensListUseCase,
|
||||
) : WalletSubscriber<Either<TokenListError, TokenList>>(name = "single_wallet_with_token_list") {
|
||||
|
||||
override fun create(
|
||||
coroutineScope: CoroutineScope,
|
||||
uiDispatcher: CoroutineContext,
|
||||
): Flow<Either<TokenListError, TokenList>> {
|
||||
return getCardTokensListUseCase(userWalletId = userWallet.walletId)
|
||||
.conflate()
|
||||
.distinctUntilChanged()
|
||||
.onEach(::updateContent)
|
||||
.onEach(tokenListAnalyticsSender::send)
|
||||
.onEach(walletWithFundsChecker::check)
|
||||
}
|
||||
|
||||
private fun updateContent(maybeTokenList: Either<TokenListError, TokenList>) {
|
||||
stateHolder.update(
|
||||
maybeTokenList.fold(
|
||||
ifLeft = { SetTokenListErrorTransformer(userWalletId = userWallet.walletId, error = it) },
|
||||
ifRight = {
|
||||
SetTokenListTransformer(
|
||||
tokenList = it,
|
||||
userWallet = userWallet,
|
||||
appCurrency = appCurrency,
|
||||
clickIntents = clickIntents,
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.subscribers
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.GetTokenListUseCase
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.tokens.model.TokenList
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateHolderV2
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.SetTokenListErrorTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.SetTokenListTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.conflate
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
typealias MaybeTokenListFlow = Flow<Either<TokenListError, TokenList>>
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class TokenListSubscriber(
|
||||
private val userWallet: UserWallet,
|
||||
private val appCurrency: AppCurrency,
|
||||
private val stateHolder: WalletStateHolderV2,
|
||||
private val clickIntents: WalletClickIntentsV2,
|
||||
private val tokenListAnalyticsSender: TokenListAnalyticsSender,
|
||||
private val walletWithFundsChecker: WalletWithFundsChecker,
|
||||
private val getTokenListUseCase: GetTokenListUseCase,
|
||||
) : WalletSubscriber<Either<TokenListError, TokenList>>(name = "token_list") {
|
||||
|
||||
override fun create(
|
||||
coroutineScope: CoroutineScope,
|
||||
uiDispatcher: CoroutineContext,
|
||||
): Flow<Either<TokenListError, TokenList>> {
|
||||
return getTokenListUseCase(userWalletId = userWallet.walletId)
|
||||
.conflate()
|
||||
.distinctUntilChanged()
|
||||
.onEach(::updateContent)
|
||||
.onEach(tokenListAnalyticsSender::send)
|
||||
.onEach(walletWithFundsChecker::check)
|
||||
}
|
||||
|
||||
private fun updateContent(maybeTokenList: Either<TokenListError, TokenList>) {
|
||||
stateHolder.update(
|
||||
maybeTokenList.fold(
|
||||
ifLeft = { SetTokenListErrorTransformer(userWalletId = userWallet.walletId, error = it) },
|
||||
ifRight = {
|
||||
SetTokenListTransformer(
|
||||
tokenList = it,
|
||||
userWallet = userWallet,
|
||||
appCurrency = appCurrency,
|
||||
clickIntents = clickIntents,
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.subscribers
|
||||
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.cachedIn
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.txhistory.models.TxHistoryItem
|
||||
import com.tangem.domain.txhistory.models.TxHistoryListError
|
||||
import com.tangem.domain.txhistory.models.TxHistoryStateError
|
||||
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase
|
||||
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.collectLatest
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateHolderV2
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.SetTxHistoryCountErrorTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.SetTxHistoryCountTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.SetTxHistoryItemsErrorTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.SetTxHistoryItemsTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
typealias MaybeTxHistoryCount = Either<TxHistoryStateError, Int>
|
||||
typealias MaybeTxHistoryItems = Either<TxHistoryListError, Flow<PagingData<TxHistoryItem>>>
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class TxHistorySubscriber(
|
||||
private val userWallet: UserWallet,
|
||||
private val isRefresh: Boolean,
|
||||
private val stateHolder: WalletStateHolderV2,
|
||||
private val clickIntents: WalletClickIntentsV2,
|
||||
private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase,
|
||||
private val txHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase,
|
||||
private val txHistoryItemsUseCase: GetTxHistoryItemsUseCase,
|
||||
) : WalletSubscriber<PagingData<TxHistoryItem>>(name = "tx_history") {
|
||||
|
||||
override fun create(
|
||||
coroutineScope: CoroutineScope,
|
||||
uiDispatcher: CoroutineContext,
|
||||
): Flow<PagingData<TxHistoryItem>> {
|
||||
return flow {
|
||||
getPrimaryCurrencyStatusUpdatesUseCase.collectLatest(userWalletId = userWallet.walletId) { status ->
|
||||
val maybeTxHistoryItemCount = txHistoryItemsCountUseCase(
|
||||
userWalletId = userWallet.walletId,
|
||||
currency = status.currency,
|
||||
)
|
||||
|
||||
setLoadingTxHistoryState(maybeTxHistoryItemCount, status)
|
||||
|
||||
maybeTxHistoryItemCount.onRight {
|
||||
val maybeTxHistoryItems = txHistoryItemsUseCase(
|
||||
userWalletId = userWallet.walletId,
|
||||
currency = status.currency,
|
||||
refresh = isRefresh,
|
||||
).map { it.cachedIn(coroutineScope) }
|
||||
|
||||
setLoadedTxHistoryState(maybeTxHistoryItems)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setLoadingTxHistoryState(maybeTxHistoryItemCount: MaybeTxHistoryCount, status: CryptoCurrencyStatus) {
|
||||
stateHolder.update(
|
||||
maybeTxHistoryItemCount.fold(
|
||||
ifLeft = {
|
||||
SetTxHistoryCountErrorTransformer(
|
||||
userWallet = userWallet,
|
||||
error = it,
|
||||
pendingTransactions = status.value.pendingTransactions,
|
||||
clickIntents = clickIntents,
|
||||
)
|
||||
},
|
||||
ifRight = {
|
||||
SetTxHistoryCountTransformer(
|
||||
userWalletId = userWallet.walletId,
|
||||
transactionsCount = it,
|
||||
clickIntents = clickIntents,
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun setLoadedTxHistoryState(maybeTxHistoryItems: MaybeTxHistoryItems) {
|
||||
stateHolder.update(
|
||||
maybeTxHistoryItems.fold(
|
||||
ifLeft = {
|
||||
SetTxHistoryItemsErrorTransformer(
|
||||
userWalletId = userWallet.walletId,
|
||||
error = it,
|
||||
clickIntents = clickIntents,
|
||||
)
|
||||
},
|
||||
ifRight = {
|
||||
SetTxHistoryItemsTransformer(
|
||||
userWallet = userWallet,
|
||||
flow = it,
|
||||
clickIntents = clickIntents,
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.subscribers
|
||||
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import timber.log.Timber
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
/**
|
||||
* Component for implementation of flow subscription
|
||||
*
|
||||
* @property name unique name of subscriber
|
||||
* [T] - type of flow
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal abstract class WalletSubscriber<T>(val name: String) {
|
||||
|
||||
protected abstract fun create(coroutineScope: CoroutineScope, uiDispatcher: CoroutineContext): Flow<T>
|
||||
|
||||
fun subscribe(coroutineScope: CoroutineScope, dispatchers: CoroutineDispatcherProvider): Job {
|
||||
Timber.d("Subscribe on $name")
|
||||
return create(coroutineScope, dispatchers.main)
|
||||
.flowOn(dispatchers.main)
|
||||
.launchIn(coroutineScope)
|
||||
}
|
||||
}
|
||||
|
|
@ -66,6 +66,7 @@ internal fun WalletEventEffect(
|
|||
}
|
||||
.addOnFailureListener(Timber::e)
|
||||
}
|
||||
is WalletEvent.DemonstrateWalletsScrollPreview -> Unit
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.ui
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import com.tangem.core.ui.event.EventEffect
|
||||
import com.tangem.core.ui.event.StateEvent
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletAlertState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletEvent
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.utils.ReviewManagerRequester
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.utils.animateScrollByIndex
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.utils.demonstrateScrolling
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
internal fun WalletEventEffectV2(
|
||||
walletsListState: LazyListState,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
event: StateEvent<WalletEvent>,
|
||||
selectedWalletIndex: Int,
|
||||
onAutoScrollSet: () -> Unit,
|
||||
onAlertConfigSet: (WalletAlertState) -> Unit,
|
||||
) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val context = LocalContext.current
|
||||
val resources = LocalContext.current.resources
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
EventEffect(
|
||||
event = event,
|
||||
onTrigger = { value ->
|
||||
when (value) {
|
||||
is WalletEvent.ChangeWallet -> {
|
||||
onAutoScrollSet()
|
||||
walletsListState.animateScrollByIndex(prevIndex = selectedWalletIndex, newIndex = value.index)
|
||||
}
|
||||
is WalletEvent.ShowError -> {
|
||||
snackbarHostState.showSnackbar(message = value.text.resolveReference(resources))
|
||||
}
|
||||
is WalletEvent.ShowToast -> {
|
||||
Toast.makeText(context, value.text.resolveReference(resources), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
is WalletEvent.CopyAddress -> {
|
||||
clipboardManager.setText(AnnotatedString(value.address))
|
||||
Toast.makeText(context, value.toast.resolveReference(resources), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
is WalletEvent.ShowAlert -> onAlertConfigSet(value.state)
|
||||
is WalletEvent.RateApp -> {
|
||||
ReviewManagerRequester.request(context = context, onDismissClick = value.onDismissClick)
|
||||
}
|
||||
is WalletEvent.DemonstrateWalletsScrollPreview -> {
|
||||
walletsListState.demonstrateScrolling(coroutineScope = coroutineScope, direction = value.direction)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,243 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.ui
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.material3.FabPosition
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.bottomsheets.chooseaddress.ChooseAddressBottomSheet
|
||||
import com.tangem.core.ui.components.bottomsheets.chooseaddress.ChooseAddressBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.tokenreceive.TokenReceiveBottomSheet
|
||||
import com.tangem.core.ui.components.bottomsheets.tokenreceive.TokenReceiveBottomSheetConfig
|
||||
import com.tangem.core.ui.components.transactions.state.TxHistoryState
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.ActionsBottomSheetConfig
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletAlertState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletBottomSheetConfig
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.NOT_INITIALIZED_WALLET_INDEX
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletScreenState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletTokensListState
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.TokenActionsBottomSheet
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletsList
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.common.*
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency.organizeTokensButton
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.controlButtons
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.marketPriceBlock
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.utils.changeWalletAnimator
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
@Composable
|
||||
internal fun WalletScreenV2(state: WalletScreenState) {
|
||||
BackHandler(onBack = state.onBackClick)
|
||||
|
||||
// It means that screen is still initializing
|
||||
if (state.selectedWalletIndex == NOT_INITIALIZED_WALLET_INDEX) return
|
||||
|
||||
val walletsListState = rememberLazyListState(initialFirstVisibleItemIndex = state.selectedWalletIndex)
|
||||
val snackbarHostState = remember(::SnackbarHostState)
|
||||
val isAutoScroll = remember { mutableStateOf(value = false) }
|
||||
|
||||
WalletContent(
|
||||
state = state,
|
||||
walletsListState = walletsListState,
|
||||
snackbarHostState = snackbarHostState,
|
||||
isAutoScroll = isAutoScroll,
|
||||
onAutoScrollReset = { isAutoScroll.value = false },
|
||||
)
|
||||
|
||||
var alertConfig by remember { mutableStateOf<WalletAlertState?>(value = null) }
|
||||
|
||||
alertConfig?.let {
|
||||
WalletAlert(state = it, onDismiss = { alertConfig = null })
|
||||
}
|
||||
|
||||
WalletEventEffectV2(
|
||||
event = state.event,
|
||||
selectedWalletIndex = state.selectedWalletIndex,
|
||||
walletsListState = walletsListState,
|
||||
snackbarHostState = snackbarHostState,
|
||||
onAlertConfigSet = { alertConfig = it },
|
||||
onAutoScrollSet = { isAutoScroll.value = true },
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
private fun WalletContent(
|
||||
state: WalletScreenState,
|
||||
walletsListState: LazyListState,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
isAutoScroll: State<Boolean>,
|
||||
onAutoScrollReset: () -> Unit,
|
||||
) {
|
||||
var selectedWalletIndex by remember { mutableIntStateOf(state.selectedWalletIndex) }
|
||||
val selectedWallet = state.wallets[selectedWalletIndex]
|
||||
|
||||
BaseScaffold(state = state, selectedWallet = selectedWallet, snackbarHostState = snackbarHostState) {
|
||||
val movableItemModifier = Modifier.changeWalletAnimator(walletsListState)
|
||||
|
||||
val lazyTxHistoryItems = (selectedWallet as? WalletState.SingleCurrency)?.let { walletState ->
|
||||
(walletState.txHistoryState as? TxHistoryState.Content)?.contentItems?.collectAsLazyPagingItems()
|
||||
}
|
||||
|
||||
val txHistoryItems by remember(selectedWallet.walletCardState.id, lazyTxHistoryItems?.itemCount) {
|
||||
mutableStateOf(value = lazyTxHistoryItems)
|
||||
}
|
||||
|
||||
val betweenItemsPadding = TangemTheme.dimens.spacing14
|
||||
val horizontalPadding = TangemTheme.dimens.spacing16
|
||||
val itemModifier = movableItemModifier
|
||||
.padding(top = betweenItemsPadding)
|
||||
.padding(horizontal = horizontalPadding)
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(
|
||||
top = TangemTheme.dimens.spacing8,
|
||||
bottom = TangemTheme.dimens.spacing92,
|
||||
),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
item(
|
||||
key = state.wallets.map { it.walletCardState.id },
|
||||
contentType = state.wallets.map { it.walletCardState.id },
|
||||
) {
|
||||
WalletsList(
|
||||
lazyListState = walletsListState,
|
||||
wallets = state.wallets.map(WalletState::walletCardState).toImmutableList(),
|
||||
isBalanceHidden = state.isHidingMode,
|
||||
)
|
||||
}
|
||||
|
||||
(selectedWallet as? WalletState.SingleCurrency)?.let {
|
||||
controlButtons(
|
||||
configs = it.buttons,
|
||||
selectedWalletIndex = selectedWalletIndex,
|
||||
modifier = movableItemModifier.padding(top = betweenItemsPadding),
|
||||
)
|
||||
}
|
||||
|
||||
notifications(configs = selectedWallet.warnings, modifier = itemModifier)
|
||||
|
||||
(selectedWallet as? WalletState.SingleCurrency)?.let { walletState ->
|
||||
walletState.marketPriceBlockState?.let { marketPriceBlockState ->
|
||||
marketPriceBlock(state = marketPriceBlockState, modifier = itemModifier)
|
||||
}
|
||||
}
|
||||
|
||||
contentItemsV2(
|
||||
state = selectedWallet,
|
||||
txHistoryItems = txHistoryItems,
|
||||
isBalanceHidden = state.isHidingMode,
|
||||
modifier = movableItemModifier,
|
||||
)
|
||||
|
||||
organizeTokens(state = selectedWallet, itemModifier = itemModifier)
|
||||
}
|
||||
|
||||
val bottomSheetConfig = selectedWallet.bottomSheetConfig
|
||||
if (bottomSheetConfig != null) {
|
||||
when (bottomSheetConfig.content) {
|
||||
is WalletBottomSheetConfig -> WalletBottomSheet(config = bottomSheetConfig)
|
||||
is TokenReceiveBottomSheetConfig -> TokenReceiveBottomSheet(config = bottomSheetConfig)
|
||||
is ActionsBottomSheetConfig -> TokenActionsBottomSheet(config = bottomSheetConfig)
|
||||
is ChooseAddressBottomSheetConfig -> ChooseAddressBottomSheet(config = bottomSheetConfig)
|
||||
}
|
||||
}
|
||||
|
||||
WalletsListEffectsV2(
|
||||
lazyListState = walletsListState,
|
||||
selectedWalletIndex = selectedWalletIndex,
|
||||
onWalletChange = state.onWalletChange,
|
||||
onSelectedWalletIndexSet = { selectedWalletIndex = it },
|
||||
isAutoScroll = isAutoScroll,
|
||||
onAutoScrollReset = onAutoScrollReset,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
private fun BaseScaffold(
|
||||
state: WalletScreenState,
|
||||
selectedWallet: WalletState,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = { WalletTopBar(config = state.topBarConfig) },
|
||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||
floatingActionButton = {
|
||||
val manageTokensButtonConfig by remember(state.selectedWalletIndex) {
|
||||
mutableStateOf(
|
||||
(state.wallets[state.selectedWalletIndex] as? WalletState.MultiCurrency)?.manageTokensButtonConfig,
|
||||
)
|
||||
}
|
||||
|
||||
manageTokensButtonConfig?.let { ManageTokensButton(onClick = it.onClick) }
|
||||
},
|
||||
floatingActionButtonPosition = FabPosition.Center,
|
||||
containerColor = TangemTheme.colors.background.secondary,
|
||||
content = {
|
||||
val pullRefreshState = rememberPullRefreshState(
|
||||
refreshing = selectedWallet.pullToRefreshConfig.isRefreshing,
|
||||
onRefresh = selectedWallet.pullToRefreshConfig.onRefresh,
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.pullRefresh(pullRefreshState)
|
||||
.padding(it),
|
||||
) {
|
||||
content()
|
||||
|
||||
WalletPullToRefreshIndicator(
|
||||
isRefreshing = selectedWallet.pullToRefreshConfig.isRefreshing,
|
||||
state = pullRefreshState,
|
||||
modifier = Modifier.align(Alignment.TopCenter),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ManageTokensButton(onClick: () -> Unit) {
|
||||
PrimaryButton(
|
||||
text = stringResource(id = R.string.main_manage_tokens),
|
||||
onClick = onClick,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||
)
|
||||
}
|
||||
|
||||
internal fun LazyListScope.organizeTokens(state: WalletState, itemModifier: Modifier) {
|
||||
(state as? WalletState.MultiCurrency)?.let {
|
||||
(state.tokensListState as? WalletTokensListState.ContentState)?.let {
|
||||
it.organizeTokensButtonConfig?.let { config ->
|
||||
organizeTokensButton(
|
||||
modifier = itemModifier,
|
||||
isEnabled = config.isEnabled,
|
||||
onClick = config.onClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.ui
|
||||
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.utils.ScrollOffsetCollectorV2
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.utils.WalletsListInteractionsCollector
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
internal fun WalletsListEffectsV2(
|
||||
lazyListState: LazyListState,
|
||||
selectedWalletIndex: Int,
|
||||
onWalletChange: (Int) -> Unit,
|
||||
onSelectedWalletIndexSet: (Int) -> Unit,
|
||||
isAutoScroll: State<Boolean>,
|
||||
onAutoScrollReset: () -> Unit,
|
||||
) {
|
||||
LaunchedEffect(key1 = lazyListState, key2 = onWalletChange) {
|
||||
snapshotFlow { lazyListState.layoutInfo.visibleItemsInfo }
|
||||
.collect(
|
||||
collector = ScrollOffsetCollectorV2(
|
||||
selectedWalletIndex = selectedWalletIndex,
|
||||
lazyListState = lazyListState,
|
||||
onWalletChange = { newIndex ->
|
||||
// Auto scroll must not change wallet
|
||||
if (isAutoScroll.value) {
|
||||
onSelectedWalletIndexSet(newIndex)
|
||||
} else {
|
||||
onSelectedWalletIndexSet(newIndex)
|
||||
onWalletChange(newIndex)
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
lazyListState.interactionSource.interactions.collect(
|
||||
collector = WalletsListInteractionsCollector(onDragStart = onAutoScrollReset),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -23,8 +23,10 @@ import androidx.compose.ui.unit.Dp
|
|||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletsListConfig
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.common.WalletCard
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
private const val SHORT_SNAP_ELEMENT_COUNT = 50
|
||||
|
||||
|
|
@ -66,6 +68,40 @@ internal fun WalletsList(config: WalletsListConfig, lazyListState: LazyListState
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
internal fun WalletsList(
|
||||
lazyListState: LazyListState,
|
||||
wallets: ImmutableList<WalletCardState>,
|
||||
isBalanceHidden: Boolean,
|
||||
) {
|
||||
val horizontalCardPadding = TangemTheme.dimens.spacing16
|
||||
val screenWidth = LocalConfiguration.current.screenWidthDp.dp
|
||||
val itemWidth by remember(screenWidth) { derivedStateOf { screenWidth - horizontalCardPadding * 2 } }
|
||||
|
||||
LazyRow(
|
||||
modifier = Modifier.background(color = TangemTheme.colors.background.secondary),
|
||||
state = lazyListState,
|
||||
contentPadding = PaddingValues(horizontal = TangemTheme.dimens.spacing16),
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
||||
flingBehavior = rememberWalletsFlingBehaviour(lazyListState = lazyListState, itemWidth = itemWidth),
|
||||
) {
|
||||
items(
|
||||
items = wallets,
|
||||
key = { it.id.stringValue },
|
||||
contentType = { it.id.stringValue },
|
||||
) { state ->
|
||||
WalletCard(
|
||||
state = state,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
modifier = Modifier
|
||||
.animateItemPlacement()
|
||||
.width(itemWidth),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom implementation of fling behaviour that overrides 'shortSnapVelocityThreshold'.
|
||||
* Every user's drag action will similar to a short snap
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import com.tangem.feature.wallet.presentation.wallet.state.WalletMultiCurrencySt
|
|||
import com.tangem.feature.wallet.presentation.wallet.state.WalletSingleCurrencyState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletState
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency.tokensListItems
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency.tokensListItemsV2
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletState as WalletStateV2
|
||||
|
||||
/**
|
||||
* Wallet content
|
||||
|
|
@ -29,4 +31,20 @@ internal fun LazyListScope.contentItems(
|
|||
is WalletMultiCurrencyState -> tokensListItems(state.tokensListState, modifier, isBalanceHidden)
|
||||
is WalletSingleCurrencyState -> txHistoryItems(state.txHistoryState, txHistoryItems, isBalanceHidden, modifier)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun LazyListScope.contentItemsV2(
|
||||
state: WalletStateV2,
|
||||
txHistoryItems: LazyPagingItems<TxHistoryState.TxHistoryItemState>?,
|
||||
isBalanceHidden: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
when (state) {
|
||||
is WalletStateV2.MultiCurrency -> {
|
||||
tokensListItemsV2(state.tokensListState, modifier, isBalanceHidden)
|
||||
}
|
||||
is WalletStateV2.SingleCurrency -> {
|
||||
txHistoryItems(state.txHistoryState, txHistoryItems, isBalanceHidden, modifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -22,7 +22,7 @@ internal fun LazyListScope.notifications(configs: ImmutableList<WalletNotificati
|
|||
items(
|
||||
items = configs,
|
||||
key = { it::class.java },
|
||||
contentType = { it.config::class.java },
|
||||
contentType = { it::class.java },
|
||||
itemContent = {
|
||||
Notification(
|
||||
config = it.config,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ import com.tangem.core.ui.res.TangemTheme
|
|||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletTokensListState as WalletTokensListStateV2
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletTokensListState.TokensListItemState as TokensListItemStateV2
|
||||
|
||||
private const val NON_CONTENT_TOKENS_LIST_KEY = "NON_CONTENT_TOKENS_LIST"
|
||||
|
||||
|
|
@ -45,6 +47,23 @@ internal fun LazyListScope.tokensListItems(
|
|||
}
|
||||
}
|
||||
|
||||
internal fun LazyListScope.tokensListItemsV2(
|
||||
state: WalletTokensListStateV2,
|
||||
modifier: Modifier = Modifier,
|
||||
isBalanceHidden: Boolean,
|
||||
) {
|
||||
when (state) {
|
||||
is WalletTokensListStateV2.ContentState -> {
|
||||
contentItemsV2(
|
||||
items = state.items,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
WalletTokensListStateV2.Empty -> nonContentItem(modifier = modifier)
|
||||
}
|
||||
}
|
||||
|
||||
private fun LazyListScope.contentItems(
|
||||
items: ImmutableList<WalletTokensListState.TokensListItemState>,
|
||||
modifier: Modifier = Modifier,
|
||||
|
|
@ -67,6 +86,28 @@ private fun LazyListScope.contentItems(
|
|||
)
|
||||
}
|
||||
|
||||
private fun LazyListScope.contentItemsV2(
|
||||
items: ImmutableList<TokensListItemStateV2>,
|
||||
modifier: Modifier = Modifier,
|
||||
isBalanceHidden: Boolean,
|
||||
) {
|
||||
itemsIndexed(
|
||||
items = items,
|
||||
key = { _, item -> item.id },
|
||||
contentType = { _, item -> item::class.java },
|
||||
itemContent = { index, item ->
|
||||
MultiCurrencyContentItem(
|
||||
state = item,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
modifier = modifier.roundedShapeItemDecoration(
|
||||
currentIndex = index,
|
||||
lastIndex = items.lastIndex,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
private fun LazyListScope.nonContentItem(modifier: Modifier = Modifier) {
|
||||
item(
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.tangem.core.ui.extensions.resolveReference
|
|||
import com.tangem.feature.wallet.presentation.common.component.NetworkGroupItem
|
||||
import com.tangem.feature.wallet.presentation.common.component.TokenItem
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletTokensListState.TokensListItemState
|
||||
|
||||
/**
|
||||
* Multi-currency content item
|
||||
|
|
@ -29,4 +30,20 @@ internal fun MultiCurrencyContentItem(
|
|||
TokenItem(state = state.state, isBalanceHidden = isBalanceHidden, modifier = modifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun MultiCurrencyContentItem(
|
||||
state: TokensListItemState,
|
||||
isBalanceHidden: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
when (state) {
|
||||
is TokensListItemState.NetworkGroupTitle -> {
|
||||
NetworkGroupItem(networkName = state.name.resolveReference(), modifier = modifier)
|
||||
}
|
||||
is TokensListItemState.Token -> {
|
||||
TokenItem(state = state.state, isBalanceHidden = isBalanceHidden, modifier = modifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.ui.utils
|
||||
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.gestures.animateScrollBy
|
||||
import androidx.compose.foundation.lazy.LazyListLayoutInfo
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
|
||||
/**
|
||||
* Animate scroll [LazyListState].
|
||||
*
|
||||
* [LazyListState] method for scroll with animation by index isn't supported custom animation.
|
||||
* This extension method calculate offset between [prevIndex] and [newIndex],
|
||||
* and scroll by it with default animation.
|
||||
*/
|
||||
internal suspend fun LazyListState.animateScrollByIndex(prevIndex: Int, newIndex: Int) {
|
||||
animateScrollBy(
|
||||
value = calculateOffset(layoutInfo, prevIndex, newIndex),
|
||||
animationSpec = tween(durationMillis = 1000),
|
||||
)
|
||||
}
|
||||
|
||||
private fun calculateOffset(layoutInfo: LazyListLayoutInfo, prevIndex: Int, newIndex: Int): Float {
|
||||
return layoutInfo.viewportSize.width.times(other = newIndex - prevIndex).toFloat()
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.ui.utils
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import com.google.android.play.core.review.ReviewInfo
|
||||
import com.google.android.play.core.review.ReviewManager
|
||||
import com.google.android.play.core.review.ReviewManagerFactory
|
||||
import com.google.android.play.core.tasks.Task
|
||||
import timber.log.Timber
|
||||
|
||||
internal object ReviewManagerRequester {
|
||||
|
||||
fun request(context: Context, onDismissClick: () -> Unit) {
|
||||
val reviewManager = ReviewManagerFactory.create(context)
|
||||
val requestTask = reviewManager.requestReviewFlow()
|
||||
|
||||
requestTask
|
||||
.addOnCompleteListener {
|
||||
handleOnCompleteRequestTask(
|
||||
reviewManager = reviewManager,
|
||||
activity = context.findActivity(),
|
||||
task = it,
|
||||
onDismissClick = onDismissClick,
|
||||
)
|
||||
}
|
||||
.addOnFailureListener(Timber::e)
|
||||
}
|
||||
|
||||
private fun handleOnCompleteRequestTask(
|
||||
reviewManager: ReviewManager,
|
||||
activity: Activity,
|
||||
task: Task<ReviewInfo>,
|
||||
onDismissClick: () -> Unit,
|
||||
) {
|
||||
if (task.isSuccessful) {
|
||||
val reviewFlow = reviewManager.launchReviewFlow(activity, task.result)
|
||||
reviewFlow
|
||||
.addOnCompleteListener { resultReviewTask ->
|
||||
if (!resultReviewTask.isSuccessful) onDismissClick()
|
||||
}
|
||||
.addOnFailureListener(Timber::e)
|
||||
} else {
|
||||
Timber.e(task.exception)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Context.findActivity(): Activity {
|
||||
var context = this
|
||||
while (context is ContextWrapper) {
|
||||
if (context is Activity) return context
|
||||
context = context.baseContext
|
||||
}
|
||||
error("Permissions should be called in the context of an Activity")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.ui.utils
|
||||
|
||||
import androidx.compose.foundation.lazy.LazyListItemInfo
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import kotlinx.coroutines.flow.FlowCollector
|
||||
import kotlin.math.abs
|
||||
|
||||
/**
|
||||
* Flow collector for scroll items tracking.
|
||||
* If first visible item offset is greater than half item size, then change selected wallet index.
|
||||
* If last visible item offset is greater than half item size, then change selected wallet index.
|
||||
*
|
||||
* @param selectedWalletIndex selected wallet index
|
||||
* @property lazyListState lazy list state
|
||||
* @property onWalletChange callback that will be invoked on wallet change
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class ScrollOffsetCollectorV2(
|
||||
selectedWalletIndex: Int,
|
||||
private val lazyListState: LazyListState,
|
||||
private val onWalletChange: (Int) -> Unit,
|
||||
) : FlowCollector<List<LazyListItemInfo>> {
|
||||
|
||||
private val LazyListItemInfo.halfItemSize
|
||||
get() = size.div(other = 2)
|
||||
|
||||
private var currentIndex = selectedWalletIndex
|
||||
|
||||
override suspend fun emit(value: List<LazyListItemInfo>) {
|
||||
if (!lazyListState.isScrollInProgress || value.size <= 1) return
|
||||
|
||||
val firstItem = value.firstOrNull() ?: return
|
||||
val lastItem = value.lastOrNull() ?: return
|
||||
|
||||
if (abs(firstItem.offset) > firstItem.halfItemSize) {
|
||||
selectIndex(newIndex = firstItem.index + 1)
|
||||
} else if (abs(lastItem.offset) > lastItem.halfItemSize) {
|
||||
selectIndex(newIndex = lastItem.index - 1)
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectIndex(newIndex: Int) {
|
||||
if (currentIndex != newIndex) {
|
||||
currentIndex = newIndex
|
||||
onWalletChange(newIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.ui.utils
|
||||
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.foundation.gestures.animateScrollBy
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletEvent.DemonstrateWalletsScrollPreview
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
private const val VISIBLE_PART_OF_WALLET_CARD = 0.2f
|
||||
|
||||
internal fun LazyListState.demonstrateScrolling(
|
||||
coroutineScope: CoroutineScope,
|
||||
direction: DemonstrateWalletsScrollPreview.Direction,
|
||||
) {
|
||||
coroutineScope.launch {
|
||||
animateScrollBy(
|
||||
value = calculateOffset(direction = direction, isReverse = false),
|
||||
animationSpec = spring(stiffness = Spring.StiffnessMediumLow),
|
||||
)
|
||||
}
|
||||
.invokeOnCompletion {
|
||||
coroutineScope.launch {
|
||||
animateScrollBy(
|
||||
value = calculateOffset(direction = direction, isReverse = true),
|
||||
animationSpec = spring(
|
||||
dampingRatio = Spring.DampingRatioMediumBouncy,
|
||||
stiffness = Spring.StiffnessLow,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun LazyListState.calculateOffset(
|
||||
direction: DemonstrateWalletsScrollPreview.Direction,
|
||||
isReverse: Boolean,
|
||||
): Float {
|
||||
val sign = when (direction) {
|
||||
DemonstrateWalletsScrollPreview.Direction.LEFT -> 1
|
||||
DemonstrateWalletsScrollPreview.Direction.RIGHT -> -1
|
||||
}.times(other = if (isReverse) -1 else 1)
|
||||
|
||||
return layoutInfo.viewportSize.width.toFloat() * VISIBLE_PART_OF_WALLET_CARD * sign
|
||||
}
|
||||
|
|
@ -68,6 +68,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.*
|
|||
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.factory.TokenListWithWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.factory.WalletStateFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.subscribers.MaybeTokenListFlow
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
|
|
@ -129,10 +130,10 @@ internal class WalletViewModel @Inject constructor(
|
|||
private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase,
|
||||
private val isDemoCardUseCase: IsDemoCardUseCase,
|
||||
private val scanCardToUnlockWalletUseCase: ScanCardToUnlockWalletClickHandler,
|
||||
hasSingleWalletSignedHashesUseCase: HasSingleWalletSignedHashesUseCase,
|
||||
isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase,
|
||||
isNeedToBackupUseCase: IsNeedToBackupUseCase,
|
||||
getMissedAddressesCryptoCurrenciesUseCase: GetMissedAddressesCryptoCurrenciesUseCase,
|
||||
hasSingleWalletSignedHashesUseCase: HasSingleWalletSignedHashesUseCase,
|
||||
// endregion Parameters
|
||||
) : ViewModel(), DefaultLifecycleObserver, WalletClickIntents {
|
||||
|
||||
|
|
@ -1352,7 +1353,7 @@ internal class WalletViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun updateButtons(userWallet: UserWallet, currencyStatus: CryptoCurrencyStatus) {
|
||||
private fun updateButtons(userWallet: UserWallet, currencyStatus: CryptoCurrencyStatus) {
|
||||
getCryptoCurrencyActionsUseCase(
|
||||
userWallet = userWallet,
|
||||
cryptoCurrencyStatus = currencyStatus,
|
||||
|
|
@ -1416,6 +1417,4 @@ internal class WalletViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun getCardTypeResolver(index: Int): CardTypesResolver = getWallet(index).scanResponse.cardTypesResolver
|
||||
}
|
||||
|
||||
typealias MaybeTokenListFlow = Flow<Either<TokenListError, TokenList>>
|
||||
}
|
||||
|
|
@ -0,0 +1,311 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.viewmodels
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.common.Provider
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.settings.CanUseBiometryUseCase
|
||||
import com.tangem.domain.settings.IsWalletsScrollPreviewEnabled
|
||||
import com.tangem.domain.settings.ShouldShowSaveWalletScreenUseCase
|
||||
import com.tangem.domain.walletconnect.WalletConnectActions
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
|
||||
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
|
||||
import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletEvent
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletEvent.DemonstrateWalletsScrollPreview.Direction
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletScreenState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateHolderV2
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.*
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.utils.WalletEventSender
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveIn
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@HiltViewModel
|
||||
internal class WalletViewModelV2 @Inject constructor(
|
||||
private val stateHolder: WalletStateHolderV2,
|
||||
private val clickIntents: WalletClickIntentsV2,
|
||||
private val walletEventSender: WalletEventSender,
|
||||
private val walletsUpdateActionResolver: WalletsUpdateActionResolverV2,
|
||||
private val walletScreenContentLoader: WalletScreenContentLoader,
|
||||
private val getSelectedWalletUseCase: GetSelectedWalletUseCase,
|
||||
private val getWalletsUseCase: GetWalletsUseCase,
|
||||
private val shouldShowSaveWalletScreenUseCase: ShouldShowSaveWalletScreenUseCase,
|
||||
private val canUseBiometryUseCase: CanUseBiometryUseCase,
|
||||
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val isWalletsScrollPreviewEnabled: IsWalletsScrollPreviewEnabled,
|
||||
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
||||
analyticsEventsHandler: AnalyticsEventHandler,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val reduxStateHolder: ReduxStateHolder,
|
||||
) : ViewModel() {
|
||||
|
||||
val uiState: StateFlow<WalletScreenState> = stateHolder.uiState
|
||||
|
||||
private val selectedAppCurrencyFlow = createSelectedAppCurrencyFlow()
|
||||
|
||||
private var router: InnerWalletRouter by Delegates.notNull()
|
||||
private var walletsUpdateJobHolder: JobHolder = JobHolder()
|
||||
|
||||
init {
|
||||
analyticsEventsHandler.send(WalletScreenAnalyticsEvent.MainScreen.ScreenOpened)
|
||||
|
||||
suggestToEnableBiometrics()
|
||||
|
||||
subscribeOnWalletsUpdateFlow()
|
||||
subscribeOnBalanceHiding()
|
||||
subscribeOnSelectedWalletFlow()
|
||||
}
|
||||
|
||||
fun setWalletRouter(router: InnerWalletRouter) {
|
||||
this.router = router
|
||||
clickIntents.initialize(router, viewModelScope)
|
||||
}
|
||||
|
||||
private fun suggestToEnableBiometrics() {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
withContext(dispatchers.io) { delay(timeMillis = 1_800) }
|
||||
|
||||
if (isShowSaveWalletScreenEnabled()) router.openSaveUserWalletScreen()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun isShowSaveWalletScreenEnabled(): Boolean {
|
||||
return router.isWalletLastScreen() && shouldShowSaveWalletScreenUseCase() && canUseBiometryUseCase()
|
||||
}
|
||||
|
||||
private fun subscribeOnWalletsUpdateFlow() {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
shouldSaveUserWalletsUseCase()
|
||||
.conflate()
|
||||
.distinctUntilChanged()
|
||||
.collectLatest { shouldSaveUserWallet ->
|
||||
getWalletsUseCase()
|
||||
.distinctUntilChanged()
|
||||
.conflate()
|
||||
.map {
|
||||
walletsUpdateActionResolver.resolve(
|
||||
wallets = it,
|
||||
currentState = stateHolder.value,
|
||||
canSaveWallets = shouldSaveUserWallet,
|
||||
)
|
||||
}
|
||||
.onEach(::updateWallets)
|
||||
.flowOn(dispatchers.main)
|
||||
.launchIn(viewModelScope)
|
||||
.saveIn(walletsUpdateJobHolder)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun subscribeOnBalanceHiding() {
|
||||
getBalanceHidingSettingsUseCase()
|
||||
.conflate()
|
||||
.distinctUntilChanged()
|
||||
.onEach {
|
||||
stateHolder.update(transformer = UpdateBalanceHidingModeTransformer(it.isBalanceHidden))
|
||||
}
|
||||
.flowOn(dispatchers.main)
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
private fun subscribeOnSelectedWalletFlow() {
|
||||
getSelectedWalletUseCase().onRight {
|
||||
it
|
||||
.conflate()
|
||||
.distinctUntilChanged()
|
||||
.onEach { selectedWallet ->
|
||||
if (selectedWallet.isMultiCurrency) {
|
||||
reduxStateHolder.dispatch(
|
||||
action = WalletConnectActions.New.Initialize(userWallet = selectedWallet),
|
||||
)
|
||||
}
|
||||
}
|
||||
.flowOn(dispatchers.main)
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateWallets(action: WalletsUpdateActionResolverV2.Action) {
|
||||
when (action) {
|
||||
is WalletsUpdateActionResolverV2.Action.InitializeWallets -> initializeWallets(action)
|
||||
is WalletsUpdateActionResolverV2.Action.ReinitializeWallets -> {
|
||||
walletScreenContentLoader.load(
|
||||
userWallet = action.selectedWallet,
|
||||
appCurrency = selectedAppCurrencyFlow.value,
|
||||
clickIntents = clickIntents,
|
||||
coroutineScope = viewModelScope,
|
||||
isRefresh = true,
|
||||
)
|
||||
}
|
||||
is WalletsUpdateActionResolverV2.Action.ReinitializeWallet -> reinitializeWallet(action)
|
||||
is WalletsUpdateActionResolverV2.Action.AddWallet -> addWallet(action)
|
||||
is WalletsUpdateActionResolverV2.Action.DeleteWallet -> deleteWallet(action)
|
||||
is WalletsUpdateActionResolverV2.Action.UnlockWallet -> unlockWallet(action)
|
||||
is WalletsUpdateActionResolverV2.Action.UpdateWalletCardCount -> {
|
||||
stateHolder.update(transformer = UpdateWalletCardsCountTransformer(action.selectedWallet))
|
||||
}
|
||||
is WalletsUpdateActionResolverV2.Action.UpdateWalletName -> {
|
||||
stateHolder.update(transformer = RenameWalletTransformer(action.selectedWalletId, action.name))
|
||||
}
|
||||
is WalletsUpdateActionResolverV2.Action.Unknown -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeWallets(action: WalletsUpdateActionResolverV2.Action.InitializeWallets) {
|
||||
walletScreenContentLoader.load(
|
||||
userWallet = action.selectedWallet,
|
||||
appCurrency = selectedAppCurrencyFlow.value,
|
||||
clickIntents = clickIntents,
|
||||
coroutineScope = viewModelScope,
|
||||
)
|
||||
|
||||
stateHolder.update(
|
||||
transformer = InitializeWalletsTransformer(
|
||||
selectedWalletIndex = action.selectedWalletIndex,
|
||||
selectedWallet = action.selectedWallet,
|
||||
wallets = action.wallets,
|
||||
clickIntents = clickIntents,
|
||||
),
|
||||
)
|
||||
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
if (action.wallets.size > 1 && isWalletsScrollPreviewEnabled()) {
|
||||
withContext(dispatchers.io) {
|
||||
delay(timeMillis = 1_800)
|
||||
}
|
||||
|
||||
walletEventSender.send(
|
||||
event = WalletEvent.DemonstrateWalletsScrollPreview(
|
||||
direction = if (action.selectedWalletIndex == action.wallets.lastIndex) {
|
||||
Direction.RIGHT
|
||||
} else {
|
||||
Direction.LEFT
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun reinitializeWallet(action: WalletsUpdateActionResolverV2.Action.ReinitializeWallet) {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
walletScreenContentLoader.cancel(action.prevWalletId)
|
||||
|
||||
walletScreenContentLoader.load(
|
||||
userWallet = action.selectedWallet,
|
||||
appCurrency = selectedAppCurrencyFlow.value,
|
||||
clickIntents = clickIntents,
|
||||
coroutineScope = viewModelScope,
|
||||
)
|
||||
|
||||
stateHolder.update(
|
||||
ReinitializeWalletTransformer(userWallet = action.selectedWallet, clickIntents = clickIntents),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addWallet(action: WalletsUpdateActionResolverV2.Action.AddWallet) {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
stateHolder.update(
|
||||
AddWalletTransformer(
|
||||
userWallet = action.selectedWallet,
|
||||
clickIntents = clickIntents,
|
||||
),
|
||||
)
|
||||
|
||||
walletScreenContentLoader.load(
|
||||
userWallet = action.selectedWallet,
|
||||
appCurrency = selectedAppCurrencyFlow.value,
|
||||
clickIntents = clickIntents,
|
||||
coroutineScope = viewModelScope,
|
||||
)
|
||||
|
||||
withContext(dispatchers.io) { delay(timeMillis = 700) }
|
||||
|
||||
scrollToWallet(index = action.selectedWalletIndex)
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteWallet(action: WalletsUpdateActionResolverV2.Action.DeleteWallet) {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
walletScreenContentLoader.load(
|
||||
userWallet = action.selectedWallet,
|
||||
appCurrency = selectedAppCurrencyFlow.value,
|
||||
clickIntents = clickIntents,
|
||||
coroutineScope = viewModelScope,
|
||||
)
|
||||
|
||||
scrollToWallet(index = action.selectedWalletIndex)
|
||||
|
||||
withContext(dispatchers.io) { delay(timeMillis = 700) }
|
||||
|
||||
stateHolder.update(
|
||||
DeleteWalletTransformer(
|
||||
selectedWalletIndex = action.selectedWalletIndex,
|
||||
deletedWalletId = action.deletedWalletId,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun unlockWallet(action: WalletsUpdateActionResolverV2.Action.UnlockWallet) {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
withContext(dispatchers.io) { delay(timeMillis = 700) }
|
||||
|
||||
stateHolder.update(
|
||||
transformer = UnlockWalletTransformer(
|
||||
unlockedWallets = action.unlockedWallets,
|
||||
clickIntents = clickIntents,
|
||||
),
|
||||
)
|
||||
|
||||
walletScreenContentLoader.load(
|
||||
userWallet = action.selectedWallet,
|
||||
appCurrency = selectedAppCurrencyFlow.value,
|
||||
clickIntents = clickIntents,
|
||||
coroutineScope = viewModelScope,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun scrollToWallet(index: Int) {
|
||||
stateHolder.update(
|
||||
ScrollToWalletTransformer(
|
||||
index = index,
|
||||
currentStateProvider = Provider(action = stateHolder::value),
|
||||
stateUpdater = { newState -> stateHolder.update { newState } },
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createSelectedAppCurrencyFlow(): StateFlow<AppCurrency> {
|
||||
return getSelectedAppCurrencyUseCase()
|
||||
.map { maybeAppCurrency ->
|
||||
maybeAppCurrency.getOrElse { AppCurrency.Default }
|
||||
}
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = AppCurrency.Default,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,305 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.viewmodels
|
||||
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.getCardsCount
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.NOT_INITIALIZED_WALLET_INDEX
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletScreenState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletState
|
||||
import dagger.hilt.android.scopes.ViewModelScoped
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Resolver that determines which update action will be performed
|
||||
*
|
||||
* @property getSelectedWalletSyncUseCase use case that returns selected wallet
|
||||
*/
|
||||
@ViewModelScoped
|
||||
internal class WalletsUpdateActionResolverV2 @Inject constructor(
|
||||
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||
) {
|
||||
|
||||
private var isInitialized: Boolean = false
|
||||
private var canSaveWallets: Boolean = false
|
||||
|
||||
fun resolve(wallets: List<UserWallet>, currentState: WalletScreenState, canSaveWallets: Boolean): Action {
|
||||
val selectedWallet = wallets.getSelectedWallet() ?: return Action.Unknown
|
||||
|
||||
val action = when {
|
||||
isFirstInitialization(currentState) -> {
|
||||
createInitializeWalletsAction(wallets, selectedWallet, canSaveWallets)
|
||||
}
|
||||
isReinitialization(canSaveWallets) -> {
|
||||
this.canSaveWallets = canSaveWallets
|
||||
Action.ReinitializeWallets(selectedWallet = selectedWallet)
|
||||
}
|
||||
else -> getUpdateContentAction(currentState, wallets, selectedWallet)
|
||||
}
|
||||
|
||||
Timber.d("Resolved action: $action")
|
||||
|
||||
return action
|
||||
}
|
||||
|
||||
private fun List<UserWallet>.getSelectedWallet(): UserWallet? {
|
||||
return when {
|
||||
isEmpty() -> null
|
||||
size == 1 -> first()
|
||||
else -> getSelectedWalletSyncUseCase().fold(ifLeft = { null }, ifRight = { it })
|
||||
}
|
||||
}
|
||||
|
||||
private fun isFirstInitialization(state: WalletScreenState): Boolean {
|
||||
return state.selectedWalletIndex == NOT_INITIALIZED_WALLET_INDEX
|
||||
}
|
||||
|
||||
private fun createInitializeWalletsAction(
|
||||
wallets: List<UserWallet>,
|
||||
selectedWallet: UserWallet,
|
||||
canSaveWallets: Boolean,
|
||||
): Action {
|
||||
this.isInitialized = true
|
||||
this.canSaveWallets = canSaveWallets
|
||||
|
||||
return Action.InitializeWallets(
|
||||
selectedWalletIndex = wallets.indexOfWallet(selectedWallet.walletId),
|
||||
selectedWallet = selectedWallet,
|
||||
wallets = wallets,
|
||||
)
|
||||
}
|
||||
|
||||
private fun isReinitialization(canSaveWallets: Boolean): Boolean {
|
||||
return isInitialized && this.canSaveWallets != canSaveWallets
|
||||
}
|
||||
|
||||
private fun getUpdateContentAction(
|
||||
state: WalletScreenState,
|
||||
wallets: List<UserWallet>,
|
||||
selectedWallet: UserWallet,
|
||||
): Action {
|
||||
return when {
|
||||
isWalletsCountChanged(state, wallets) -> {
|
||||
getChangeWalletsListAction(state, wallets, selectedWallet)
|
||||
}
|
||||
isAnotherWalletSelected(state, selectedWallet) -> {
|
||||
Action.ReinitializeWallet(
|
||||
prevWalletId = state.getPrevSelectedWallet().id,
|
||||
selectedWallet = selectedWallet,
|
||||
)
|
||||
}
|
||||
else -> getUpdateSelectedWalletAction(state, wallets, selectedWallet)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isWalletsCountChanged(state: WalletScreenState, wallets: List<UserWallet>): Boolean {
|
||||
val prevWalletsSize = state.wallets.size
|
||||
val walletsSize = wallets.size
|
||||
|
||||
return prevWalletsSize != walletsSize
|
||||
}
|
||||
|
||||
private fun getChangeWalletsListAction(
|
||||
state: WalletScreenState,
|
||||
wallets: List<UserWallet>,
|
||||
selectedWallet: UserWallet,
|
||||
): Action {
|
||||
val prevWalletsSize = state.wallets.size
|
||||
|
||||
return when {
|
||||
prevWalletsSize > wallets.size -> {
|
||||
Action.DeleteWallet(
|
||||
selectedWallet = selectedWallet,
|
||||
selectedWalletIndex = wallets.indexOfWallet(id = selectedWallet.walletId),
|
||||
deletedWalletId = state.wallets.getDeletedWalletId(wallets),
|
||||
)
|
||||
}
|
||||
prevWalletsSize < wallets.size -> {
|
||||
val newUserWallet = state.wallets.getAddedWallet(wallets)
|
||||
Action.AddWallet(
|
||||
selectedWalletIndex = wallets.indexOfWallet(id = newUserWallet.walletId),
|
||||
selectedWallet = newUserWallet,
|
||||
)
|
||||
}
|
||||
else -> error("Wallets list is not changed")
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<WalletState>.getDeletedWalletId(wallets: List<UserWallet>): UserWalletId {
|
||||
return this
|
||||
.map { it.walletCardState.id }
|
||||
.firstOrNull { !wallets.map(UserWallet::walletId).contains(it) }
|
||||
?: error("Deleted wallet id is not found. Wallets contains all previous wallets ids")
|
||||
}
|
||||
|
||||
private fun List<WalletState>.getAddedWallet(wallets: List<UserWallet>): UserWallet {
|
||||
return wallets
|
||||
.firstOrNull { wallet -> !this.map { it.walletCardState.id }.contains(wallet.walletId) }
|
||||
?: error("Added wallet id is not found. Wallets contains all previous wallets ids")
|
||||
}
|
||||
|
||||
private fun isAnotherWalletSelected(state: WalletScreenState, selectedWallet: UserWallet): Boolean {
|
||||
return state.getPrevSelectedWallet().id != selectedWallet.walletId
|
||||
}
|
||||
|
||||
private fun getUpdateSelectedWalletAction(
|
||||
state: WalletScreenState,
|
||||
wallets: List<UserWallet>,
|
||||
selectedWallet: UserWallet,
|
||||
): Action {
|
||||
return when {
|
||||
isSelectedWalletNameChanged(state, selectedWallet) -> {
|
||||
Action.UpdateWalletName(selectedWalletId = selectedWallet.walletId, name = selectedWallet.name)
|
||||
}
|
||||
isSelectedWalletUnlocked(state, selectedWallet) -> {
|
||||
Action.UnlockWallet(
|
||||
selectedWallet = selectedWallet,
|
||||
unlockedWallets = wallets.filterNot(UserWallet::isLocked),
|
||||
)
|
||||
}
|
||||
isSelectedWalletCardsCountChanged(state, selectedWallet) -> Action.UpdateWalletCardCount(selectedWallet)
|
||||
else -> Action.Unknown
|
||||
}
|
||||
}
|
||||
|
||||
private fun isSelectedWalletNameChanged(state: WalletScreenState, selectedWallet: UserWallet): Boolean {
|
||||
return state.getPrevSelectedWallet().title != selectedWallet.name
|
||||
}
|
||||
|
||||
private fun isSelectedWalletUnlocked(state: WalletScreenState, selectedWallet: UserWallet): Boolean {
|
||||
return state.isSelectedWalletLocked() && !selectedWallet.isLocked
|
||||
}
|
||||
|
||||
private fun isSelectedWalletCardsCountChanged(state: WalletScreenState, selectedWallet: UserWallet): Boolean {
|
||||
val prevSelectedWallet = state.getPrevSelectedWallet()
|
||||
return prevSelectedWallet is WalletCardState.Content &&
|
||||
prevSelectedWallet.cardCount != selectedWallet.getCardsCount()
|
||||
}
|
||||
|
||||
private fun WalletScreenState.isSelectedWalletLocked(): Boolean {
|
||||
val selectedWalletState = wallets.getOrNull(selectedWalletIndex) ?: error("Selected wallet is not found")
|
||||
return selectedWalletState is WalletState.MultiCurrency.Locked ||
|
||||
selectedWalletState is WalletState.SingleCurrency.Locked
|
||||
}
|
||||
|
||||
private fun WalletScreenState.getPrevSelectedWallet(): WalletCardState {
|
||||
return wallets
|
||||
.map(WalletState::walletCardState)
|
||||
.getOrNull(selectedWalletIndex)
|
||||
?: error("Previous selected wallet is not found")
|
||||
}
|
||||
|
||||
private fun List<UserWallet>.indexOfWallet(id: UserWalletId): Int {
|
||||
val selectedIndex = indexOfFirst { it.walletId == id }
|
||||
|
||||
return if (selectedIndex == -1) {
|
||||
error("Wallets don't contain a wallet with id: $id")
|
||||
} else {
|
||||
selectedIndex
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Action {
|
||||
|
||||
data class InitializeWallets(
|
||||
val selectedWalletIndex: Int,
|
||||
val selectedWallet: UserWallet,
|
||||
val wallets: List<UserWallet>,
|
||||
) : Action() {
|
||||
|
||||
override fun toString(): String {
|
||||
return """
|
||||
Initialize(
|
||||
selectedWalletIndex=$selectedWalletIndex,
|
||||
selectedWallet=${selectedWallet.walletId},
|
||||
wallets=${wallets.joinToString { it.walletId.toString() }}
|
||||
)
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reinitialize wallets. Example, if user turned on wallets saving
|
||||
*
|
||||
* @property selectedWallet selected wallet
|
||||
*/
|
||||
data class ReinitializeWallets(val selectedWallet: UserWallet) : Action() {
|
||||
|
||||
override fun toString(): String {
|
||||
return "Reinitialize(selectedWallet=${selectedWallet.walletId})"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reinitialize selected wallet. Example, scanning a new card if wallets saving is turned off
|
||||
*
|
||||
* @property prevWalletId previous selected wallet id
|
||||
* @property selectedWallet selected wallet
|
||||
*/
|
||||
data class ReinitializeWallet(val prevWalletId: UserWalletId, val selectedWallet: UserWallet) : Action() {
|
||||
|
||||
override fun toString(): String {
|
||||
return "ReinitializeWallet(prevWalletId=$prevWalletId, selectedWallet=${selectedWallet.walletId})"
|
||||
}
|
||||
}
|
||||
|
||||
data class UpdateWalletName(val selectedWalletId: UserWalletId, val name: String) : Action() {
|
||||
|
||||
override fun toString(): String {
|
||||
return "UpdateWalletName(selectedWalletId=$selectedWalletId, name=$name)"
|
||||
}
|
||||
}
|
||||
|
||||
data class UnlockWallet(val selectedWallet: UserWallet, val unlockedWallets: List<UserWallet>) : Action() {
|
||||
|
||||
override fun toString(): String {
|
||||
return """
|
||||
UnlockWallet(
|
||||
selectedWallet=${selectedWallet.walletId},
|
||||
unlockedWallets=${unlockedWallets.joinToString { it.walletId.toString() }}
|
||||
)
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
|
||||
data class DeleteWallet(
|
||||
val selectedWallet: UserWallet,
|
||||
val selectedWalletIndex: Int,
|
||||
val deletedWalletId: UserWalletId,
|
||||
) : Action() {
|
||||
|
||||
override fun toString(): String {
|
||||
return """
|
||||
DeleteWallet(
|
||||
selectedWallet=${selectedWallet.walletId},
|
||||
selectedWalletIndex=$selectedWalletIndex,
|
||||
deletedWalletId=$deletedWalletId
|
||||
)
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
|
||||
data class AddWallet(val selectedWalletIndex: Int, val selectedWallet: UserWallet) : Action() {
|
||||
|
||||
override fun toString(): String {
|
||||
return "AddWallet(selectedWalletIndex=$selectedWalletIndex, selectedWallet=${selectedWallet.walletId})"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update wallet card count. Example, if user backed up wallet
|
||||
*
|
||||
* @property selectedWallet selected wallet
|
||||
*/
|
||||
data class UpdateWalletCardCount(val selectedWallet: UserWallet) : Action() {
|
||||
|
||||
override fun toString(): String {
|
||||
return "UpdateWalletCardCount(selectedWallet=${selectedWallet.walletId})"
|
||||
}
|
||||
}
|
||||
|
||||
object Unknown : Action()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents
|
||||
|
||||
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
/**
|
||||
* Base wallet click intents component.
|
||||
* Provides router and viewModelScope to child classes.
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Suppress("UnnecessaryAbstractClass")
|
||||
internal abstract class BaseWalletClickIntents {
|
||||
|
||||
protected val router: InnerWalletRouter get() = _router
|
||||
protected val viewModelScope: CoroutineScope get() = _viewModelScope
|
||||
|
||||
private var _router: InnerWalletRouter by Delegates.notNull()
|
||||
private var _viewModelScope: CoroutineScope by Delegates.notNull()
|
||||
|
||||
open fun initialize(router: InnerWalletRouter, coroutineScope: CoroutineScope) {
|
||||
_router = router
|
||||
_viewModelScope = coroutineScope
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents
|
||||
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.DeleteWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.UpdateWalletUseCase
|
||||
import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletAlertState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletEvent
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateHolderV2
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.utils.WalletEventSender
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface WalletCardClickIntents {
|
||||
|
||||
fun onRenameClick(userWalletId: UserWalletId, name: String)
|
||||
|
||||
fun onDeleteBeforeConfirmationClick(userWalletId: UserWalletId)
|
||||
|
||||
fun onDeleteAfterConfirmationClick(userWalletId: UserWalletId)
|
||||
}
|
||||
|
||||
internal class WalletCardClickIntentsImplementor @Inject constructor(
|
||||
private val stateHolder: WalletStateHolderV2,
|
||||
private val walletEventSender: WalletEventSender,
|
||||
private val walletScreenContentLoader: WalletScreenContentLoader,
|
||||
private val updateWalletUseCase: UpdateWalletUseCase,
|
||||
private val deleteWalletUseCase: DeleteWalletUseCase,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : BaseWalletClickIntents(), WalletCardClickIntents {
|
||||
|
||||
override fun onRenameClick(userWalletId: UserWalletId, name: String) {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
updateWalletUseCase(userWalletId = userWalletId, update = { it.copy(name) })
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDeleteBeforeConfirmationClick(userWalletId: UserWalletId) {
|
||||
walletEventSender.send(
|
||||
event = WalletEvent.ShowAlert(
|
||||
state = WalletAlertState.RemoveWalletAlert(
|
||||
onConfirmClick = { onDeleteAfterConfirmationClick(userWalletId) },
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override fun onDeleteAfterConfirmationClick(userWalletId: UserWalletId) {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
walletScreenContentLoader.cancel(userWalletId)
|
||||
deleteWalletUseCase(userWalletId)
|
||||
.onRight { popBackIfAllWalletsIsLocked() }
|
||||
.onLeft { Timber.e(it.toString()) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun popBackIfAllWalletsIsLocked() {
|
||||
val wallets = stateHolder.value.wallets.map(WalletState::walletCardState)
|
||||
val unlockedWallet = wallets.count { it !is WalletCardState.LockedContent }
|
||||
|
||||
if (unlockedWallet == 1) {
|
||||
router.popBackStack(
|
||||
screen = if (wallets.size > 1) AppScreen.Welcome else AppScreen.Home,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents
|
||||
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.settings.NeverToShowWalletsScrollPreview
|
||||
import com.tangem.domain.tokens.FetchCardTokenListUseCase
|
||||
import com.tangem.domain.tokens.FetchCurrencyStatusUseCase
|
||||
import com.tangem.domain.tokens.FetchTokenListUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.domain.wallets.usecase.SelectWalletUseCase
|
||||
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.PortfolioEvent
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.unwrap
|
||||
import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateHolderV2
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.SetRefreshStateTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.SetTokenListErrorTransformer
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.hilt.android.scopes.ViewModelScoped
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@ViewModelScoped
|
||||
internal class WalletClickIntentsV2 @Inject constructor(
|
||||
private val walletCardClickIntentsImplementor: WalletCardClickIntentsImplementor,
|
||||
private val warningsClickIntentsImplementer: WalletWarningsClickIntentsImplementer,
|
||||
private val currencyActionsClickIntentsImplementor: WalletCurrencyActionsClickIntentsImplementor,
|
||||
private val contentClickIntentsImplementor: WalletContentClickIntentsImplementor,
|
||||
private val stateHolder: WalletStateHolderV2,
|
||||
private val walletScreenContentLoader: WalletScreenContentLoader,
|
||||
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||
private val selectWalletUseCase: SelectWalletUseCase,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val fetchTokenListUseCase: FetchTokenListUseCase,
|
||||
private val fetchCardTokenListUseCase: FetchCardTokenListUseCase,
|
||||
private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase,
|
||||
private val neverToShowWalletsScrollPreview: NeverToShowWalletsScrollPreview,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : BaseWalletClickIntents(),
|
||||
WalletCardClickIntents by walletCardClickIntentsImplementor,
|
||||
WalletWarningsClickIntents by warningsClickIntentsImplementer,
|
||||
WalletCurrencyActionsClickIntents by currencyActionsClickIntentsImplementor,
|
||||
WalletContentClickIntents by contentClickIntentsImplementor {
|
||||
|
||||
override fun initialize(router: InnerWalletRouter, coroutineScope: CoroutineScope) {
|
||||
super.initialize(router, coroutineScope)
|
||||
|
||||
walletCardClickIntentsImplementor.initialize(router, coroutineScope)
|
||||
warningsClickIntentsImplementer.initialize(router, coroutineScope)
|
||||
currencyActionsClickIntentsImplementor.initialize(router, coroutineScope)
|
||||
contentClickIntentsImplementor.initialize(router, coroutineScope)
|
||||
}
|
||||
|
||||
fun onWalletChange(index: Int) {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
launch(dispatchers.main) { neverToShowWalletsScrollPreview() }
|
||||
|
||||
val maybeUserWallet = selectWalletUseCase(
|
||||
userWalletId = stateHolder.value.wallets[index].walletCardState.id,
|
||||
)
|
||||
|
||||
stateHolder.update { it.copy(selectedWalletIndex = index) }
|
||||
|
||||
maybeUserWallet.onRight {
|
||||
walletScreenContentLoader.load(
|
||||
userWallet = it,
|
||||
appCurrency = getSelectedAppCurrencyUseCase.unwrap(),
|
||||
clickIntents = this@WalletClickIntentsV2,
|
||||
coroutineScope = viewModelScope,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onRefreshSwipe() {
|
||||
when (stateHolder.getSelectedWallet()) {
|
||||
is WalletState.MultiCurrency.Content -> {
|
||||
analyticsEventHandler.send(PortfolioEvent.Refreshed)
|
||||
refreshMultiCurrencyContent()
|
||||
}
|
||||
is WalletState.SingleCurrency.Content -> {
|
||||
analyticsEventHandler.send(PortfolioEvent.Refreshed)
|
||||
refreshSingleCurrencyContent()
|
||||
}
|
||||
is WalletState.MultiCurrency.Locked,
|
||||
is WalletState.SingleCurrency.Locked,
|
||||
-> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshMultiCurrencyContent() {
|
||||
val userWallet = getSelectedWalletSyncUseCase.unwrap() ?: return
|
||||
|
||||
stateHolder.update(
|
||||
SetRefreshStateTransformer(userWalletId = userWallet.walletId, isRefreshing = true),
|
||||
)
|
||||
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
val maybeFetchResult = if (userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()) {
|
||||
fetchCardTokenListUseCase(userWalletId = userWallet.walletId, refresh = true)
|
||||
} else {
|
||||
fetchTokenListUseCase(userWalletId = userWallet.walletId, refresh = true)
|
||||
}
|
||||
|
||||
maybeFetchResult.onLeft {
|
||||
stateHolder.update(SetTokenListErrorTransformer(userWalletId = userWallet.walletId, error = it))
|
||||
}
|
||||
|
||||
stateHolder.update(
|
||||
SetRefreshStateTransformer(userWalletId = userWallet.walletId, isRefreshing = false),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun onReloadClick() {
|
||||
refreshSingleCurrencyContent()
|
||||
}
|
||||
|
||||
// FIXME: refreshSingleCurrencyContent mustn't update the TxHistory and Buttons. It only must fetch primary
|
||||
// currency. Now it not works because GetPrimaryCurrency's subscriber uses .distinctUntilChanged()
|
||||
private fun refreshSingleCurrencyContent() {
|
||||
val userWallet = getSelectedWalletSyncUseCase.unwrap() ?: return
|
||||
|
||||
stateHolder.update(
|
||||
SetRefreshStateTransformer(userWalletId = userWallet.walletId, isRefreshing = true),
|
||||
)
|
||||
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
fetchCurrencyStatusUseCase(userWallet.walletId, refresh = true)
|
||||
|
||||
walletScreenContentLoader.load(
|
||||
userWallet = userWallet,
|
||||
appCurrency = getSelectedAppCurrencyUseCase.unwrap(),
|
||||
clickIntents = this@WalletClickIntentsV2,
|
||||
coroutineScope = viewModelScope,
|
||||
isRefresh = true,
|
||||
)
|
||||
|
||||
stateHolder.update(
|
||||
SetRefreshStateTransformer(userWalletId = userWallet.walletId, isRefreshing = false),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents
|
||||
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase
|
||||
import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase
|
||||
import com.tangem.domain.tokens.TokensAction
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.TokenActionsState
|
||||
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.PortfolioEvent
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.unwrap
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.ActionsBottomSheetConfig
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateHolderV2
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.CloseBottomSheetTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.OpenBottomSheetTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.converter.MultiWalletCurrencyActionsConverter
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.hilt.android.scopes.ViewModelScoped
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.take
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface WalletContentClickIntents {
|
||||
|
||||
fun onBackClick()
|
||||
|
||||
fun onDetailsClick()
|
||||
|
||||
fun onManageTokensClick()
|
||||
|
||||
fun onOrganizeTokensClick()
|
||||
|
||||
fun onTokenItemClick(currency: CryptoCurrency)
|
||||
|
||||
fun onTokenItemLongClick(cryptoCurrencyStatus: CryptoCurrencyStatus)
|
||||
|
||||
fun onTransactionClick(txHash: String)
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@ViewModelScoped
|
||||
internal class WalletContentClickIntentsImplementor @Inject constructor(
|
||||
private val stateHolder: WalletStateHolderV2,
|
||||
private val currencyActionsClickIntentsImplementor: WalletCurrencyActionsClickIntentsImplementor,
|
||||
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||
private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase,
|
||||
private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCase,
|
||||
private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val reduxStateHolder: ReduxStateHolder,
|
||||
) : BaseWalletClickIntents(), WalletContentClickIntents {
|
||||
|
||||
override fun onBackClick() = router.popBackStack()
|
||||
|
||||
override fun onDetailsClick() = router.openDetailsScreen()
|
||||
|
||||
override fun onManageTokensClick() {
|
||||
analyticsEventHandler.send(PortfolioEvent.ButtonManageTokens)
|
||||
reduxStateHolder.dispatch(action = TokensAction.SetArgs.ManageAccess)
|
||||
router.openManageTokensScreen()
|
||||
}
|
||||
|
||||
override fun onOrganizeTokensClick() {
|
||||
analyticsEventHandler.send(PortfolioEvent.OrganizeTokens)
|
||||
router.openOrganizeTokensScreen(userWalletId = stateHolder.getSelectedWalletId())
|
||||
}
|
||||
|
||||
override fun onTokenItemClick(currency: CryptoCurrency) {
|
||||
analyticsEventHandler.send(PortfolioEvent.TokenTapped)
|
||||
router.openTokenDetails(stateHolder.getSelectedWalletId(), currency)
|
||||
}
|
||||
|
||||
override fun onTokenItemLongClick(cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
val userWallet = getSelectedWalletSyncUseCase.unwrap() ?: return
|
||||
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
getCryptoCurrencyActionsUseCase(userWallet = userWallet, cryptoCurrencyStatus = cryptoCurrencyStatus)
|
||||
.take(count = 1)
|
||||
.collectLatest {
|
||||
showActionsBottomSheet(it, userWallet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showActionsBottomSheet(tokenActionsState: TokenActionsState, userWallet: UserWallet) {
|
||||
stateHolder.update(
|
||||
OpenBottomSheetTransformer(
|
||||
userWalletId = userWallet.walletId,
|
||||
content = ActionsBottomSheetConfig(
|
||||
actions = MultiWalletCurrencyActionsConverter(
|
||||
userWallet = userWallet,
|
||||
clickIntents = currencyActionsClickIntentsImplementor,
|
||||
).convert(tokenActionsState),
|
||||
),
|
||||
onDismissBottomSheet = {
|
||||
stateHolder.update(
|
||||
CloseBottomSheetTransformer(userWalletId = userWallet.walletId),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override fun onTransactionClick(txHash: String) {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
val currency = getPrimaryCurrencyStatusUpdatesUseCase.unwrap(
|
||||
userWalletId = stateHolder.getSelectedWalletId(),
|
||||
)
|
||||
?.currency
|
||||
?: return@launch
|
||||
|
||||
router.openUrl(
|
||||
url = getExplorerTransactionUrlUseCase(txHash = txHash, networkId = currency.network.id),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,418 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents
|
||||
|
||||
import com.tangem.blockchain.common.address.Address
|
||||
import com.tangem.blockchain.common.address.AddressType
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.components.bottomsheets.chooseaddress.ChooseAddressBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.tokenreceive.AddressModel
|
||||
import com.tangem.core.ui.components.bottomsheets.tokenreceive.TokenReceiveBottomSheetConfig
|
||||
import com.tangem.core.ui.extensions.WrappedList
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.demo.IsDemoCardUseCase
|
||||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.tokens.GetNetworkCoinStatusUseCase
|
||||
import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase
|
||||
import com.tangem.domain.tokens.IsCryptoCurrencyCoinCouldHideUseCase
|
||||
import com.tangem.domain.tokens.RemoveCurrencyUseCase
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.models.analytics.TokenReceiveAnalyticsEvent
|
||||
import com.tangem.domain.tokens.models.analytics.TokenScreenAnalyticsEvent
|
||||
import com.tangem.domain.walletconnect.WalletConnectActions
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.GetExploreUrlUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.unwrap
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletAlertState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletEvent
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateHolderV2
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.CloseBottomSheetTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.OpenBottomSheetTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.utils.WalletEventSender
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.hilt.android.scopes.ViewModelScoped
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.take
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
interface WalletCurrencyActionsClickIntents {
|
||||
|
||||
fun onSendClick(cryptoCurrencyStatus: CryptoCurrencyStatus)
|
||||
|
||||
fun onReceiveClick(cryptoCurrencyStatus: CryptoCurrencyStatus)
|
||||
|
||||
fun onCopyAddressClick(cryptoCurrencyStatus: CryptoCurrencyStatus)
|
||||
|
||||
fun onHideTokensClick(cryptoCurrencyStatus: CryptoCurrencyStatus)
|
||||
|
||||
fun onPerformHideToken(cryptoCurrencyStatus: CryptoCurrencyStatus)
|
||||
|
||||
fun onSellClick(cryptoCurrencyStatus: CryptoCurrencyStatus)
|
||||
|
||||
fun onBuyClick(cryptoCurrencyStatus: CryptoCurrencyStatus)
|
||||
|
||||
fun onSwapClick(cryptoCurrencyStatus: CryptoCurrencyStatus)
|
||||
|
||||
fun onExploreClick()
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList", "LargeClass")
|
||||
@ViewModelScoped
|
||||
internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
||||
private val stateHolder: WalletStateHolderV2,
|
||||
private val walletEventSender: WalletEventSender,
|
||||
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val isDemoCardUseCase: IsDemoCardUseCase,
|
||||
private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase,
|
||||
private val isCryptoCurrencyCoinCouldHide: IsCryptoCurrencyCoinCouldHideUseCase,
|
||||
private val removeCurrencyUseCase: RemoveCurrencyUseCase,
|
||||
private val getNetworkCoinStatusUseCase: GetNetworkCoinStatusUseCase,
|
||||
private val getExploreUrlUseCase: GetExploreUrlUseCase,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val reduxStateHolder: ReduxStateHolder,
|
||||
) : BaseWalletClickIntents(), WalletCurrencyActionsClickIntents {
|
||||
|
||||
override fun onSendClick(cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
val userWallet = getSelectedWalletSyncUseCase.unwrap() ?: return
|
||||
|
||||
analyticsEventHandler.send(
|
||||
event = TokenScreenAnalyticsEvent.ButtonSend(cryptoCurrencyStatus.currency.symbol),
|
||||
)
|
||||
|
||||
stateHolder.update(CloseBottomSheetTransformer(userWalletId = userWallet.walletId))
|
||||
|
||||
when (val currency = cryptoCurrencyStatus.currency) {
|
||||
is CryptoCurrency.Coin -> sendCoin(cryptoCurrencyStatus, userWallet)
|
||||
is CryptoCurrency.Token -> sendToken(currency, cryptoCurrencyStatus.value, userWallet)
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendCoin(cryptoCurrencyStatus: CryptoCurrencyStatus, userWallet: UserWallet) {
|
||||
reduxStateHolder.dispatch(
|
||||
action = TradeCryptoAction.New.SendCoin(userWallet = userWallet, coinStatus = cryptoCurrencyStatus),
|
||||
)
|
||||
}
|
||||
|
||||
private fun sendToken(
|
||||
cryptoCurrency: CryptoCurrency.Token,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus.Status,
|
||||
userWallet: UserWallet,
|
||||
) {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
getNetworkCoinStatusUseCase(
|
||||
userWalletId = userWallet.walletId,
|
||||
networkId = cryptoCurrency.network.id,
|
||||
derivationPath = cryptoCurrency.network.derivationPath,
|
||||
isSingleWalletWithTokens = userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken(),
|
||||
)
|
||||
.take(count = 1)
|
||||
.collectLatest {
|
||||
it.onRight { coinStatus ->
|
||||
reduxStateHolder.dispatch(
|
||||
action = TradeCryptoAction.New.SendToken(
|
||||
userWallet = userWallet,
|
||||
tokenCurrency = cryptoCurrency,
|
||||
tokenFiatRate = cryptoCurrencyStatus.fiatRate,
|
||||
coinFiatRate = coinStatus.value.fiatRate,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onReceiveClick(cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
val userWalletId = stateHolder.getSelectedWalletId()
|
||||
|
||||
analyticsEventHandler.send(
|
||||
event = TokenScreenAnalyticsEvent.ButtonReceive(cryptoCurrencyStatus.currency.symbol),
|
||||
)
|
||||
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
val currency = cryptoCurrencyStatus.currency
|
||||
val addresses = walletManagersFacade.getAddress(userWalletId = userWalletId, network = currency.network)
|
||||
|
||||
analyticsEventHandler.send(event = TokenReceiveAnalyticsEvent.ReceiveScreenOpened)
|
||||
|
||||
stateHolder.update(
|
||||
OpenBottomSheetTransformer(
|
||||
userWalletId = userWalletId,
|
||||
content = createReceiveBottomSheetContent(currency, addresses),
|
||||
onDismissBottomSheet = {
|
||||
stateHolder.update(CloseBottomSheetTransformer(userWalletId = userWalletId))
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createReceiveBottomSheetContent(
|
||||
currency: CryptoCurrency,
|
||||
addresses: List<Address>,
|
||||
): TangemBottomSheetConfigContent {
|
||||
return TokenReceiveBottomSheetConfig(
|
||||
name = currency.name,
|
||||
symbol = currency.symbol,
|
||||
network = currency.network.name,
|
||||
addresses = addresses.map { address ->
|
||||
AddressModel(
|
||||
value = address.value,
|
||||
type = AddressModel.Type.valueOf(address.type.name),
|
||||
)
|
||||
},
|
||||
onCopyClick = {
|
||||
analyticsEventHandler.send(TokenReceiveAnalyticsEvent.ButtonCopyAddress(currency.symbol))
|
||||
},
|
||||
onShareClick = {
|
||||
analyticsEventHandler.send(TokenReceiveAnalyticsEvent.ButtonShareAddress(currency.symbol))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
override fun onCopyAddressClick(cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
analyticsEventHandler.send(
|
||||
event = TokenScreenAnalyticsEvent.ButtonCopyAddress(cryptoCurrencyStatus.currency.symbol),
|
||||
)
|
||||
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
walletManagersFacade.getAddress(
|
||||
userWalletId = stateHolder.getSelectedWalletId(),
|
||||
network = cryptoCurrencyStatus.currency.network,
|
||||
)
|
||||
.find { it.type == AddressType.Default }
|
||||
?.value
|
||||
?.let {
|
||||
walletEventSender.send(
|
||||
event = WalletEvent.CopyAddress(
|
||||
address = it,
|
||||
toast = resourceReference(R.string.wallet_notification_address_copied),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onHideTokensClick(cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
analyticsEventHandler.send(
|
||||
event = TokenScreenAnalyticsEvent.ButtonRemoveToken(cryptoCurrencyStatus.currency.symbol),
|
||||
)
|
||||
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
walletEventSender.send(
|
||||
event = WalletEvent.ShowAlert(
|
||||
state = getHideTokeAlertConfig(stateHolder.getSelectedWalletId(), cryptoCurrencyStatus),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getHideTokeAlertConfig(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
): WalletAlertState.DefaultAlert {
|
||||
val currency = cryptoCurrencyStatus.currency
|
||||
return if (currency is CryptoCurrency.Coin && !isCryptoCurrencyCoinCouldHide(userWalletId, currency)) {
|
||||
WalletAlertState.DefaultAlert(
|
||||
title = resourceReference(
|
||||
id = R.string.token_details_unable_hide_alert_title,
|
||||
formatArgs = WrappedList(listOf(cryptoCurrencyStatus.currency.name)),
|
||||
),
|
||||
message = resourceReference(
|
||||
id = R.string.token_details_unable_hide_alert_message,
|
||||
formatArgs = WrappedList(
|
||||
listOf(
|
||||
cryptoCurrencyStatus.currency.name,
|
||||
cryptoCurrencyStatus.currency.network.name,
|
||||
),
|
||||
),
|
||||
),
|
||||
onConfirmClick = null,
|
||||
)
|
||||
} else {
|
||||
WalletAlertState.DefaultAlert(
|
||||
title = resourceReference(
|
||||
id = R.string.token_details_hide_alert_title,
|
||||
formatArgs = WrappedList(listOf(cryptoCurrencyStatus.currency.name)),
|
||||
),
|
||||
message = resourceReference(R.string.token_details_hide_alert_message),
|
||||
onConfirmClick = { onPerformHideToken(cryptoCurrencyStatus) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPerformHideToken(cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
val userWalletId = stateHolder.getSelectedWalletId()
|
||||
|
||||
viewModelScope.launch(dispatchers.io) {
|
||||
removeCurrencyUseCase(userWalletId, cryptoCurrencyStatus.currency)
|
||||
.fold(
|
||||
ifLeft = {
|
||||
walletEventSender.send(
|
||||
event = WalletEvent.ShowToast(text = resourceReference(R.string.common_error)),
|
||||
)
|
||||
},
|
||||
ifRight = {
|
||||
getSelectedWalletSyncUseCase.unwrap()?.let { userWallet ->
|
||||
reduxStateHolder.dispatch(
|
||||
action = WalletConnectActions.New.SetupUserChains(userWallet = userWallet),
|
||||
)
|
||||
}
|
||||
|
||||
stateHolder.update(CloseBottomSheetTransformer(userWalletId = userWalletId))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSellClick(cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
analyticsEventHandler.send(
|
||||
event = TokenScreenAnalyticsEvent.ButtonSell(cryptoCurrencyStatus.currency.symbol),
|
||||
)
|
||||
|
||||
showErrorIfDemoModeOrElse {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
reduxStateHolder.dispatch(
|
||||
action = TradeCryptoAction.New.Sell(
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
appCurrencyCode = getSelectedAppCurrencyUseCase.unwrap().code,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBuyClick(cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
val userWallet = getSelectedWalletSyncUseCase.unwrap() ?: return
|
||||
|
||||
analyticsEventHandler.send(
|
||||
event = TokenScreenAnalyticsEvent.ButtonBuy(cryptoCurrencyStatus.currency.symbol),
|
||||
)
|
||||
|
||||
showErrorIfDemoModeOrElse {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
reduxStateHolder.dispatch(
|
||||
TradeCryptoAction.New.Buy(
|
||||
userWallet = userWallet,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
appCurrencyCode = getSelectedAppCurrencyUseCase.unwrap().code,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSwapClick(cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
analyticsEventHandler.send(
|
||||
event = TokenScreenAnalyticsEvent.ButtonExchange(cryptoCurrencyStatus.currency.symbol),
|
||||
)
|
||||
|
||||
reduxStateHolder.dispatch(TradeCryptoAction.New.Swap(cryptoCurrencyStatus.currency))
|
||||
}
|
||||
|
||||
override fun onExploreClick() {
|
||||
showErrorIfDemoModeOrElse(action = ::openExplorer)
|
||||
}
|
||||
|
||||
private fun openExplorer() {
|
||||
val userWalletId = stateHolder.getSelectedWalletId()
|
||||
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
val currency = getPrimaryCurrencyStatusUpdatesUseCase.unwrap(userWalletId)?.currency ?: return@launch
|
||||
val addresses = walletManagersFacade.getAddress(userWalletId = userWalletId, network = currency.network)
|
||||
|
||||
if (addresses.size == 1) {
|
||||
router.openUrl(
|
||||
url = getExploreUrlUseCase(
|
||||
userWalletId = userWalletId,
|
||||
currency = currency,
|
||||
addressType = AddressType.Default,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
showChooseAddressBottomSheet(userWalletId, addresses, currency)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showChooseAddressBottomSheet(
|
||||
userWalletId: UserWalletId,
|
||||
addresses: List<Address>,
|
||||
currency: CryptoCurrency,
|
||||
) {
|
||||
stateHolder.update(
|
||||
OpenBottomSheetTransformer(
|
||||
userWalletId = userWalletId,
|
||||
content = ChooseAddressBottomSheetConfig(
|
||||
addressModels = addresses
|
||||
.map { address ->
|
||||
AddressModel(
|
||||
value = address.value,
|
||||
type = AddressModel.Type.valueOf(address.type.name),
|
||||
)
|
||||
}
|
||||
.toImmutableList(),
|
||||
onClick = {
|
||||
onAddressTypeSelected(
|
||||
userWalletId = userWalletId,
|
||||
currency = currency,
|
||||
addressModel = it,
|
||||
)
|
||||
},
|
||||
),
|
||||
onDismissBottomSheet = {
|
||||
stateHolder.update(
|
||||
CloseBottomSheetTransformer(userWalletId = userWalletId),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun onAddressTypeSelected(
|
||||
userWalletId: UserWalletId,
|
||||
currency: CryptoCurrency,
|
||||
addressModel: AddressModel,
|
||||
) {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
router.openUrl(
|
||||
url = getExploreUrlUseCase(
|
||||
userWalletId = userWalletId,
|
||||
currency = currency,
|
||||
addressType = AddressType.valueOf(addressModel.type.name),
|
||||
),
|
||||
)
|
||||
|
||||
stateHolder.update(
|
||||
CloseBottomSheetTransformer(userWalletId = userWalletId),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showErrorIfDemoModeOrElse(action: () -> Unit) {
|
||||
val cardId = getSelectedWalletSyncUseCase.unwrap()?.cardId ?: return
|
||||
|
||||
if (isDemoCardUseCase(cardId = cardId)) {
|
||||
stateHolder.update(CloseBottomSheetTransformer(userWalletId = stateHolder.getSelectedWalletId()))
|
||||
|
||||
walletEventSender.send(
|
||||
event = WalletEvent.ShowError(
|
||||
text = resourceReference(id = R.string.alert_demo_feature_disabled),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
action()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,287 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents
|
||||
|
||||
import com.tangem.blockchain.blockchains.cardano.CardanoUtils
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.common.extensions.toMapKey
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.domain.card.DerivePublicKeysUseCase
|
||||
import com.tangem.domain.card.SetCardWasScannedUseCase
|
||||
import com.tangem.domain.common.configs.CardConfig
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.redux.LegacyAction
|
||||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.settings.NeverToSuggestRateAppUseCase
|
||||
import com.tangem.domain.settings.RemindToRateAppLaterUseCase
|
||||
import com.tangem.domain.tokens.FetchTokenListUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UnlockWalletsError
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.domain.wallets.usecase.UnlockWalletsUseCase
|
||||
import com.tangem.domain.wallets.usecase.UpdateWalletUseCase
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.Basic
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.ScanCardToUnlockWalletClickHandler
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.ScanCardToUnlockWalletError
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.unwrap
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletAlertState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletEvent
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateHolderV2
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.CloseBottomSheetTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.OpenBottomSheetTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.state2.utils.WalletEventSender
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.hilt.android.scopes.ViewModelScoped
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface WalletWarningsClickIntents {
|
||||
|
||||
fun onAddBackupCardClick()
|
||||
|
||||
fun onCloseAlreadySignedHashesWarningClick()
|
||||
|
||||
fun onGenerateMissedAddressesClick(missedAddressCurrencies: List<CryptoCurrency>)
|
||||
|
||||
fun onOpenUnlockWalletsBottomSheetClick()
|
||||
|
||||
fun onUnlockWalletClick()
|
||||
|
||||
fun onScanToUnlockWalletClick()
|
||||
|
||||
fun onLikeAppClick()
|
||||
|
||||
fun onDislikeAppClick()
|
||||
|
||||
fun onCloseRateAppWarningClick()
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@ViewModelScoped
|
||||
internal class WalletWarningsClickIntentsImplementer @Inject constructor(
|
||||
private val stateHolder: WalletStateHolderV2,
|
||||
private val walletEventSender: WalletEventSender,
|
||||
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||
private val updateWalletUseCase: UpdateWalletUseCase,
|
||||
private val unlockWalletsUseCase: UnlockWalletsUseCase,
|
||||
private val derivePublicKeysUseCase: DerivePublicKeysUseCase,
|
||||
private val scanCardToUnlockWalletClickHandler: ScanCardToUnlockWalletClickHandler,
|
||||
private val fetchTokenListUseCase: FetchTokenListUseCase,
|
||||
private val setCardWasScannedUseCase: SetCardWasScannedUseCase,
|
||||
private val neverToSuggestRateAppUseCase: NeverToSuggestRateAppUseCase,
|
||||
private val remindToRateAppLaterUseCase: RemindToRateAppLaterUseCase,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val reduxStateHolder: ReduxStateHolder,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : BaseWalletClickIntents(), WalletWarningsClickIntents {
|
||||
|
||||
override fun onAddBackupCardClick() {
|
||||
analyticsEventHandler.send(MainScreen.NoticeBackupYourWalletTapped)
|
||||
router.openOnboardingScreen()
|
||||
}
|
||||
|
||||
override fun onCloseAlreadySignedHashesWarningClick() {
|
||||
val userWallet = getSelectedWalletSyncUseCase.unwrap() ?: return
|
||||
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
setCardWasScannedUseCase(cardId = userWallet.cardId)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onGenerateMissedAddressesClick(missedAddressCurrencies: List<CryptoCurrency>) {
|
||||
val userWallet = getSelectedWalletSyncUseCase.unwrap() ?: return
|
||||
|
||||
analyticsEventHandler.send(Basic.CardWasScanned(AnalyticsParam.ScannedFrom.Main))
|
||||
analyticsEventHandler.send(MainScreen.NoticeScanYourCardTapped)
|
||||
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
deriveMissingCurrencies(
|
||||
scanResponse = userWallet.scanResponse,
|
||||
currencyList = missedAddressCurrencies,
|
||||
) { scannedCardResponse ->
|
||||
updateWalletUseCase(
|
||||
userWalletId = userWallet.walletId,
|
||||
update = { it.copy(scanResponse = scannedCardResponse) },
|
||||
)
|
||||
.onRight { fetchTokenListUseCase(userWalletId = it.walletId) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: [REDACTED_JIRA]
|
||||
private fun deriveMissingCurrencies(
|
||||
scanResponse: ScanResponse,
|
||||
currencyList: List<CryptoCurrency>,
|
||||
onSuccess: suspend (ScanResponse) -> Unit,
|
||||
) {
|
||||
val config = CardConfig.createConfig(scanResponse.card)
|
||||
val derivationDataList = currencyList.mapNotNull {
|
||||
config.primaryCurve(blockchain = Blockchain.fromId(it.network.id.value))?.let { curve ->
|
||||
getNewDerivations(curve, scanResponse, it)
|
||||
}
|
||||
}
|
||||
|
||||
val derivations = buildMap<ByteArrayKey, MutableList<DerivationPath>> {
|
||||
derivationDataList.forEach {
|
||||
val current = this[it.derivations.first]
|
||||
if (current != null) {
|
||||
current.addAll(it.derivations.second)
|
||||
current.distinct()
|
||||
} else {
|
||||
this[it.derivations.first] = it.derivations.second.toMutableList()
|
||||
}
|
||||
}
|
||||
}.ifEmpty { return }
|
||||
|
||||
viewModelScope.launch(dispatchers.io) {
|
||||
derivePublicKeysUseCase(cardId = null, derivations = derivations)
|
||||
.onRight {
|
||||
val newDerivedKeys = it.entries
|
||||
val oldDerivedKeys = scanResponse.derivedKeys
|
||||
|
||||
val walletKeys = (newDerivedKeys.keys + oldDerivedKeys.keys).toSet()
|
||||
|
||||
val updatedDerivedKeys = walletKeys.associateWith { walletKey ->
|
||||
val oldDerivations = ExtendedPublicKeysMap(oldDerivedKeys[walletKey] ?: emptyMap())
|
||||
val newDerivations = newDerivedKeys[walletKey] ?: ExtendedPublicKeysMap(emptyMap())
|
||||
ExtendedPublicKeysMap(oldDerivations + newDerivations)
|
||||
}
|
||||
val updatedScanResponse = scanResponse.copy(derivedKeys = updatedDerivedKeys)
|
||||
|
||||
onSuccess(updatedScanResponse)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getNewDerivations(
|
||||
curve: EllipticCurve,
|
||||
scanResponse: ScanResponse,
|
||||
currency: CryptoCurrency,
|
||||
): DerivationData? {
|
||||
val wallet = scanResponse.card.wallets.firstOrNull { it.curve == curve } ?: return null
|
||||
|
||||
val blockchain = Blockchain.fromId(currency.network.id.value)
|
||||
val supportedCurves = blockchain.getSupportedCurves()
|
||||
val path = blockchain.derivationPath(scanResponse.derivationStyleProvider.getDerivationStyle())
|
||||
.takeIf { supportedCurves.contains(curve) }
|
||||
|
||||
val customPath = currency.network.derivationPath.value?.let {
|
||||
DerivationPath(it)
|
||||
}.takeIf { supportedCurves.contains(curve) }
|
||||
|
||||
val bothCandidates = listOfNotNull(path, customPath).distinct().toMutableList()
|
||||
if (bothCandidates.isEmpty()) return null
|
||||
|
||||
if (currency is CryptoCurrency.Coin && blockchain == Blockchain.Cardano) {
|
||||
currency.network.derivationPath.value?.let {
|
||||
bothCandidates.add(CardanoUtils.extendedDerivationPath(DerivationPath(it)))
|
||||
}
|
||||
}
|
||||
|
||||
val mapKeyOfWalletPublicKey = wallet.publicKey.toMapKey()
|
||||
val alreadyDerivedKeys: ExtendedPublicKeysMap =
|
||||
scanResponse.derivedKeys[mapKeyOfWalletPublicKey] ?: ExtendedPublicKeysMap(emptyMap())
|
||||
val alreadyDerivedPaths = alreadyDerivedKeys.keys.toList()
|
||||
|
||||
val toDerive = bothCandidates.filterNot { alreadyDerivedPaths.contains(it) }
|
||||
if (toDerive.isEmpty()) return null
|
||||
|
||||
return DerivationData(derivations = mapKeyOfWalletPublicKey to toDerive)
|
||||
}
|
||||
|
||||
class DerivationData(val derivations: Pair<ByteArrayKey, List<DerivationPath>>)
|
||||
|
||||
override fun onOpenUnlockWalletsBottomSheetClick() {
|
||||
stateHolder.update(
|
||||
OpenBottomSheetTransformer(
|
||||
content = requireNotNull(stateHolder.getSelectedWallet().bottomSheetConfig).content,
|
||||
userWalletId = stateHolder.getSelectedWalletId(),
|
||||
onDismissBottomSheet = {
|
||||
stateHolder.update(
|
||||
CloseBottomSheetTransformer(userWalletId = stateHolder.getSelectedWalletId()),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override fun onUnlockWalletClick() {
|
||||
analyticsEventHandler.send(MainScreen.NoticeWalletLocked)
|
||||
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
unlockWalletsUseCase(throwIfNotAllWalletsUnlocked = true)
|
||||
.onRight { stateHolder.update(CloseBottomSheetTransformer(stateHolder.getSelectedWalletId())) }
|
||||
.onLeft(::handleUnlockWalletsError)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleUnlockWalletsError(error: UnlockWalletsError) {
|
||||
val event = when (error) {
|
||||
is UnlockWalletsError.DataError,
|
||||
is UnlockWalletsError.UnableToUnlockWallets,
|
||||
-> WalletEvent.ShowToast(resourceReference(R.string.user_wallet_list_error_unable_to_unlock))
|
||||
is UnlockWalletsError.NoUserWalletSelected,
|
||||
is UnlockWalletsError.NotAllUserWalletsUnlocked,
|
||||
-> WalletEvent.ShowAlert(WalletAlertState.RescanWallets)
|
||||
}
|
||||
|
||||
walletEventSender.send(event)
|
||||
}
|
||||
|
||||
override fun onScanToUnlockWalletClick() {
|
||||
analyticsEventHandler.send(event = MainScreen.WalletUnlockTapped)
|
||||
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
scanCardToUnlockWalletClickHandler(walletId = stateHolder.getSelectedWalletId())
|
||||
.onLeft { error ->
|
||||
when (error) {
|
||||
ScanCardToUnlockWalletError.WrongCardIsScanned -> {
|
||||
walletEventSender.send(
|
||||
event = WalletEvent.ShowAlert(WalletAlertState.WrongCardIsScanned),
|
||||
)
|
||||
}
|
||||
ScanCardToUnlockWalletError.ManyScanFails -> router.openScanFailedDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLikeAppClick() {
|
||||
analyticsEventHandler.send(MainScreen.NoticeRateAppButton(AnalyticsParam.RateApp.Liked))
|
||||
|
||||
walletEventSender.send(
|
||||
event = WalletEvent.RateApp(
|
||||
onDismissClick = {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
neverToSuggestRateAppUseCase()
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override fun onDislikeAppClick() {
|
||||
analyticsEventHandler.send(MainScreen.NoticeRateAppButton(AnalyticsParam.RateApp.Disliked))
|
||||
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
neverToSuggestRateAppUseCase()
|
||||
|
||||
reduxStateHolder.dispatch(LegacyAction.SendEmailRateCanBeBetter)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCloseRateAppWarningClick() {
|
||||
analyticsEventHandler.send(MainScreen.NoticeRateAppButton(AnalyticsParam.RateApp.Closed))
|
||||
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
remindToRateAppLaterUseCase()
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue