Updated on 2026-08-14

This commit is contained in:
Tangem 2023-10-10 08:58:37 +03:00
parent 7d9da40464
commit 519f5332f1
23 changed files with 379 additions and 132 deletions

View file

@ -20,6 +20,12 @@ internal object WalletsDomainModule {
return GetWalletsUseCase(walletsStateHolder = walletsStateHolder)
}
@Provides
@ViewModelScoped
fun providesGetUserWalletUseCase(walletsStateHolder: WalletsStateHolder): GetUserWalletUseCase {
return GetUserWalletUseCase(walletsStateHolder = walletsStateHolder)
}
@Provides
@ViewModelScoped
fun providesGetSelectedWalletUseCase(walletsStateHolder: WalletsStateHolder): GetSelectedWalletUseCase {

View file

@ -39,9 +39,13 @@ class MultiWalletMiddleware {
when (action) {
is WalletAction.MultiWallet.SelectWallet -> {
if (action.currency != null) {
val userWalletId = userWalletsListManager.selectedUserWalletSync?.walletId
val bundle = bundleOf(
TokenDetailsRouter.USER_WALLET_ID_KEY to userWalletId?.stringValue,
TokenDetailsRouter.CRYPTO_CURRENCY_KEY to cryptoCurrencyConverter.convert(action.currency),
)
store.dispatch(NavigationAction.NavigateTo(screen = AppScreen.WalletDetails, bundle = bundle))
}
}

View file

@ -15,6 +15,7 @@ dependencies {
implementation(projects.domain.tokens.models)
implementation(projects.domain.wallets.models)
implementation(projects.domain.balanceHiding.models)
implementation(projects.domain.txhistory.models)
/** Tangem libraries */
implementation(deps.tangem.blockchain)

View file

@ -0,0 +1,21 @@
package com.tangem.datasource.di
import com.tangem.datasource.local.datastore.RuntimeDataStore
import com.tangem.datasource.local.txhistory.DefaultTxHistoryItemsStore
import com.tangem.datasource.local.txhistory.TxHistoryItemsStore
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
internal object TxHistoryItemsStoreModule {
@Provides
fun provideTxHistoryItemsStore(): TxHistoryItemsStore {
return DefaultTxHistoryItemsStore(
dataStore = RuntimeDataStore(),
)
}
}

View file

@ -0,0 +1,42 @@
package com.tangem.datasource.local.txhistory
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
import com.tangem.datasource.local.datastore.core.StringKeyDataStoreDecorator
import com.tangem.domain.txhistory.models.PaginationWrapper
import com.tangem.domain.txhistory.models.TxHistoryItem
import com.tangem.utils.extensions.addOrReplace
internal class DefaultTxHistoryItemsStore(
dataStore: StringKeyDataStore<Set<PaginationWrapper<TxHistoryItem>>>,
) : TxHistoryItemsStore,
StringKeyDataStoreDecorator<TxHistoryItemsStore.Key, Set<PaginationWrapper<TxHistoryItem>>>(dataStore) {
override fun provideStringKey(key: TxHistoryItemsStore.Key): String = key.toString()
override suspend fun getNextPageSyncOrNull(key: TxHistoryItemsStore.Key): Int? {
val storedValue = getSyncOrNull(key) ?: return null
val lastWrappedItems = storedValue.maxBy(PaginationWrapper<*>::page)
val lastPage = lastWrappedItems.page
return if (lastPage <= lastWrappedItems.totalPages) {
lastPage
} else {
null
}
}
override suspend fun getSyncOrNull(key: TxHistoryItemsStore.Key, page: Int): PaginationWrapper<TxHistoryItem>? {
val storedValue = getSyncOrNull(key)
return storedValue?.firstOrNull { it.page == page }
}
override suspend fun store(key: TxHistoryItemsStore.Key, value: PaginationWrapper<TxHistoryItem>) {
val oldValue = getSyncOrNull(key).orEmpty()
val newValue = oldValue.addOrReplace(value) {
it.page == value.page
}
store(key, newValue)
}
}

View file

@ -0,0 +1,22 @@
package com.tangem.datasource.local.txhistory
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.txhistory.models.PaginationWrapper
import com.tangem.domain.txhistory.models.TxHistoryItem
import com.tangem.domain.wallets.models.UserWalletId
interface TxHistoryItemsStore {
suspend fun getNextPageSyncOrNull(key: Key): Int?
suspend fun getSyncOrNull(key: Key, page: Int): PaginationWrapper<TxHistoryItem>?
suspend fun remove(key: Key)
suspend fun store(key: Key, value: PaginationWrapper<TxHistoryItem>)
data class Key(
val userWalletId: UserWalletId,
val currency: CryptoCurrency,
)
}

View file

@ -10,6 +10,8 @@ android {
}
dependencies {
implementation(projects.data.common)
implementation(projects.core.utils)
implementation(projects.core.datasource)
implementation(projects.domain.legacy)
@ -20,7 +22,8 @@ dependencies {
implementation(deps.kotlin.coroutines)
implementation(deps.androidx.paging.runtime)
implementation(deps.arrow.core)
implementation(deps.timber)
implementation(deps.jodatime)
implementation(deps.hilt.core)
kapt(deps.hilt.kapt)

View file

@ -1,6 +1,8 @@
package com.tangem.data.txhistory.di
import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.txhistory.repository.DefaultTxHistoryRepository
import com.tangem.datasource.local.txhistory.TxHistoryItemsStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.txhistory.repository.TxHistoryRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
@ -17,10 +19,14 @@ internal object TxHistoryDataModule {
@Provides
@Singleton
fun provideTxHistoryRepository(
cacheRegistry: CacheRegistry,
walletManagersFacade: WalletManagersFacade,
userWalletsStore: UserWalletsStore,
txHistoryItemsStore: TxHistoryItemsStore,
): TxHistoryRepository = DefaultTxHistoryRepository(
walletManagersFacade = walletManagersFacade,
userWalletsStore = userWalletsStore,
cacheRegistry,
walletManagersFacade,
userWalletsStore,
txHistoryItemsStore,
)
}

View file

@ -3,7 +3,9 @@ package com.tangem.data.txhistory.repository
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.txhistory.repository.paging.TxHistoryPagingSource
import com.tangem.datasource.local.txhistory.TxHistoryItemsStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
@ -13,50 +15,57 @@ import com.tangem.domain.txhistory.models.TxHistoryStateError
import com.tangem.domain.txhistory.repository.TxHistoryRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
class DefaultTxHistoryRepository(
private val cacheRegistry: CacheRegistry,
private val walletManagersFacade: WalletManagersFacade,
private val userWalletsStore: UserWalletsStore,
private val txHistoryItemsStore: TxHistoryItemsStore,
) : TxHistoryRepository {
override suspend fun getTxHistoryItemsCount(network: Network): Int {
val userWallet = getUserWallet()
override suspend fun getTxHistoryItemsCount(userWalletId: UserWalletId, network: Network): Int {
val userWallet = getUserWallet(userWalletId)
val state = walletManagersFacade.getTxHistoryState(
userWalletId = userWallet.walletId,
network = network,
)
return when (state) {
is TxHistoryState.Failed.FetchError -> throw TxHistoryStateError.DataError(state.exception)
TxHistoryState.NotImplemented -> throw TxHistoryStateError.TxHistoryNotImplemented
TxHistoryState.Success.Empty -> throw TxHistoryStateError.EmptyTxHistories
is TxHistoryState.NotImplemented -> throw TxHistoryStateError.TxHistoryNotImplemented
is TxHistoryState.Success.Empty -> throw TxHistoryStateError.EmptyTxHistories
is TxHistoryState.Success.HasTransactions -> state.txCount
}
}
override fun getTxHistoryItems(currency: CryptoCurrency, pageSize: Int): Flow<PagingData<TxHistoryItem>> {
val userWallet = getUserWallet()
return Pager(
override fun getTxHistoryItems(
userWalletId: UserWalletId,
currency: CryptoCurrency,
pageSize: Int,
refresh: Boolean,
): Flow<PagingData<TxHistoryItem>> {
val pager = Pager(
config = PagingConfig(
pageSize = pageSize,
initialLoadSize = pageSize,
),
pagingSourceFactory = {
TxHistoryPagingSource(
loadPage = { page: Int, pageSize: Int ->
walletManagersFacade.getTxHistoryItems(
userWalletId = userWallet.walletId,
currency = currency,
page = page,
pageSize = pageSize,
)
},
sourceParams = TxHistoryPagingSource.Params(userWalletId, currency, pageSize, refresh),
txHistoryItemsStore = txHistoryItemsStore,
walletManagersFacade = walletManagersFacade,
cacheRegistry = cacheRegistry,
)
},
).flow
)
return pager.flow
}
private fun getUserWallet(): UserWallet = requireNotNull(userWalletsStore.selectedUserWalletOrNull) {
"Selected wallet must not be null"
private suspend fun getUserWallet(userWalletId: UserWalletId): UserWallet {
return requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) {
"Unable to find user wallet with provided ID: $userWalletId"
}
}
}

View file

@ -2,34 +2,96 @@ package com.tangem.data.txhistory.repository.paging
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.tangem.data.common.cache.CacheRegistry
import com.tangem.datasource.local.txhistory.TxHistoryItemsStore
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.txhistory.models.PaginationWrapper
import com.tangem.domain.txhistory.models.TxHistoryItem
private const val INITIAL_PAGE = 1
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.models.UserWalletId
import timber.log.Timber
internal class TxHistoryPagingSource(
private val loadPage: suspend (page: Int, pageSize: Int) -> PaginationWrapper<TxHistoryItem>,
private val sourceParams: Params,
private val txHistoryItemsStore: TxHistoryItemsStore,
private val walletManagersFacade: WalletManagersFacade,
private val cacheRegistry: CacheRegistry,
) : PagingSource<Int, TxHistoryItem>() {
private val storeKey = TxHistoryItemsStore.Key(sourceParams.userWalletId, sourceParams.currency)
override fun getRefreshKey(state: PagingState<Int, TxHistoryItem>): Int? {
return state.anchorPosition?.let { anchorPosition ->
state.closestPageToPosition(anchorPosition)?.prevKey?.plus(other = 1)
?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(other = 1)
val anchorPage = state.closestPageToPosition(anchorPosition)
anchorPage?.prevKey?.inc() ?: anchorPage?.nextKey?.dec()
}
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, TxHistoryItem> {
val currentPage = params.key ?: INITIAL_PAGE
return try {
val result = loadPage(currentPage, params.loadSize)
val pageToLoad = params.key ?: INITIAL_PAGE
LoadResult.Page(
data = result.items,
prevKey = if (currentPage > INITIAL_PAGE) currentPage.minus(1) else null,
nextKey = if (result.page < result.totalPages) currentPage.plus(1) else null,
return try {
val wrappedItems = loadItems(
pageToLoad = pageToLoad,
pageSize = sourceParams.pageSize,
refresh = sourceParams.refresh && params is LoadParams.Refresh,
)
} catch (e: Exception) {
val items = wrappedItems.items
val prevPage = when {
items.isEmpty() -> null
pageToLoad > INITIAL_PAGE -> pageToLoad.dec()
else -> null
}
val nextPage = when {
items.isEmpty() -> INITIAL_PAGE
pageToLoad < wrappedItems.totalPages -> pageToLoad.inc()
else -> null
}
LoadResult.Page(items, prevKey = prevPage, nextKey = nextPage)
} catch (e: Throwable) {
Timber.e(e, "Unable to load the transaction history for the requested page: $pageToLoad")
LoadResult.Error(e)
}
}
private suspend fun loadItems(pageToLoad: Int, pageSize: Int, refresh: Boolean): PaginationWrapper<TxHistoryItem> {
cacheRegistry.invokeOnExpire(
key = getTxHistoryPageKey(pageToLoad),
skipCache = refresh,
block = { fetch(pageToLoad, pageSize) },
)
return requireNotNull(txHistoryItemsStore.getSyncOrNull(storeKey, pageToLoad)) {
"The transaction history page #$pageToLoad could not be retrieved"
}
}
private suspend fun fetch(pageToLoad: Int, pageSize: Int) {
val wrappedItems = walletManagersFacade.getTxHistoryItems(
userWalletId = sourceParams.userWalletId,
currency = sourceParams.currency,
page = pageToLoad,
pageSize = pageSize,
)
txHistoryItemsStore.store(storeKey, wrappedItems)
}
private fun getTxHistoryPageKey(page: Int): String {
return "tx_history_page_${sourceParams.currency}_${sourceParams.userWalletId}_$page"
}
data class Params(
val userWalletId: UserWalletId,
val currency: CryptoCurrency,
val pageSize: Int,
val refresh: Boolean,
)
private companion object {
private const val INITIAL_PAGE = 1
}
}

View file

@ -9,11 +9,15 @@ android {
}
dependencies {
implementation(deps.arrow.core)
implementation(deps.kotlin.coroutines)
implementation(deps.androidx.paging.runtime)
implementation(projects.core.utils)
/** Project - Domain */
implementation(projects.domain.core)
implementation(projects.domain.tokens.models)
implementation(projects.domain.txhistory.models)
implementation(projects.domain.wallets.models)
/** Project - Other */
implementation(projects.core.utils)
/** Android - Other */
implementation(deps.androidx.paging.runtime)
}

View file

@ -6,13 +6,19 @@ import com.tangem.domain.tokens.model.Network
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.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
interface TxHistoryRepository {
@Throws(TxHistoryStateError::class)
suspend fun getTxHistoryItemsCount(network: Network): Int
suspend fun getTxHistoryItemsCount(userWalletId: UserWalletId, network: Network): Int
@Throws(TxHistoryListError::class)
fun getTxHistoryItems(currency: CryptoCurrency, pageSize: Int): Flow<PagingData<TxHistoryItem>>
fun getTxHistoryItems(
userWalletId: UserWalletId,
currency: CryptoCurrency,
pageSize: Int,
refresh: Boolean,
): Flow<PagingData<TxHistoryItem>>
}

View file

@ -6,14 +6,15 @@ import arrow.core.raise.either
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.txhistory.models.TxHistoryStateError
import com.tangem.domain.txhistory.repository.TxHistoryRepository
import com.tangem.domain.wallets.models.UserWalletId
// TODO: Add tests
class GetTxHistoryItemsCountUseCase(private val repository: TxHistoryRepository) {
// FIXME: Provide UserWalletId
suspend operator fun invoke(network: Network): Either<TxHistoryStateError, Int> {
suspend operator fun invoke(userWalletId: UserWalletId, network: Network): Either<TxHistoryStateError, Int> {
return either {
catch(
block = { repository.getTxHistoryItemsCount(network) },
block = { repository.getTxHistoryItemsCount(userWalletId, network) },
catch = { throwable ->
raise(
when (throwable) {

View file

@ -7,21 +7,25 @@ import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.txhistory.models.TxHistoryItem
import com.tangem.domain.txhistory.models.TxHistoryListError
import com.tangem.domain.txhistory.repository.TxHistoryRepository
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
private const val DEFAULT_PAGE_SIZE = 50
// TODO: Add tests
class GetTxHistoryItemsUseCase(private val repository: TxHistoryRepository) {
// FIXME: Provide UserWalletId
operator fun invoke(
userWalletId: UserWalletId,
currency: CryptoCurrency,
pageSize: Int = DEFAULT_PAGE_SIZE,
refresh: Boolean = false,
): Either<TxHistoryListError, Flow<PagingData<TxHistoryItem>>> {
return either {
repository
.getTxHistoryItems(currency = currency, pageSize = pageSize)
.getTxHistoryItems(userWalletId, currency, pageSize, refresh)
.catch { raise(TxHistoryListError.DataError(it)) }
}
}

View file

@ -1,8 +0,0 @@
package com.tangem.domain.wallets.models
sealed interface GetSelectedWalletError {
object DataError : GetSelectedWalletError
object NoUserWalletSelected : GetSelectedWalletError
}

View file

@ -0,0 +1,8 @@
package com.tangem.domain.wallets.models
sealed class GetUserWalletError {
data class DataError(val cause: Throwable) : GetUserWalletError()
object UserWalletNotFound : GetUserWalletError()
}

View file

@ -4,7 +4,7 @@ import arrow.core.Either
import arrow.core.raise.either
import arrow.core.raise.ensureNotNull
import com.tangem.domain.wallets.legacy.WalletsStateHolder
import com.tangem.domain.wallets.models.GetSelectedWalletError
import com.tangem.domain.wallets.models.GetUserWalletError
import com.tangem.domain.wallets.models.UserWallet
/**
@ -17,16 +17,20 @@ import com.tangem.domain.wallets.models.UserWallet
*/
class GetSelectedWalletUseCase(private val walletsStateHolder: WalletsStateHolder) {
operator fun invoke(): Either<GetSelectedWalletError, UserWallet> {
operator fun invoke(): Either<GetUserWalletError, UserWallet> {
return either {
val userWalletsListManager = ensureNotNull(
value = walletsStateHolder.userWalletsListManager,
raise = { GetSelectedWalletError.DataError },
raise = {
val error = IllegalStateException("User wallets list manager not initialized")
GetUserWalletError.DataError(error)
},
)
ensureNotNull(
value = userWalletsListManager.selectedUserWalletSync,
raise = { GetSelectedWalletError.NoUserWalletSelected },
raise = { GetUserWalletError.UserWalletNotFound },
)
}
}

View file

@ -0,0 +1,30 @@
package com.tangem.domain.wallets.usecase
import arrow.core.Either
import arrow.core.raise.either
import arrow.core.raise.ensureNotNull
import com.tangem.domain.wallets.legacy.WalletsStateHolder
import com.tangem.domain.wallets.models.GetUserWalletError
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.firstOrNull
class GetUserWalletUseCase(private val walletsStateHolder: WalletsStateHolder) {
suspend operator fun invoke(userWalletId: UserWalletId): Either<Any, UserWallet> = either {
val userWalletsListManager = ensureNotNull(
value = walletsStateHolder.userWalletsListManager,
raise = {
val error = IllegalStateException("User wallets list manager not initialized")
GetUserWalletError.DataError(error)
},
)
val userWallets = userWalletsListManager.userWallets.firstOrNull().orEmpty()
ensureNotNull(userWallets.firstOrNull { it.walletId == userWalletId }) {
raise(GetUserWalletError.UserWalletNotFound)
}
}
}

View file

@ -7,6 +7,7 @@ interface TokenDetailsRouter {
fun getEntryFragment(): Fragment
companion object {
const val USER_WALLET_ID_KEY = "token_details_user_wallet_id"
const val CRYPTO_CURRENCY_KEY = "token_details_crypto_currency"
}
}

View file

@ -23,10 +23,9 @@ import com.tangem.domain.tokens.models.analytics.TokenReceiveAnalyticsEvent
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
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.GetSelectedWalletUseCase
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.feature.tokendetails.presentation.router.InnerTokenDetailsRouter
import com.tangem.feature.tokendetails.presentation.tokendetails.analytics.TokenScreenEvent
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
@ -48,7 +47,7 @@ import kotlin.properties.Delegates
@HiltViewModel
internal class TokenDetailsViewModel @Inject constructor(
private val dispatchers: CoroutineDispatcherProvider,
private val getSelectedWalletUseCase: GetSelectedWalletUseCase,
private val getUserWalletUseCase: GetUserWalletUseCase,
private val getCurrencyStatusUpdatesUseCase: GetCurrencyStatusUpdatesUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase,
@ -67,15 +66,18 @@ internal class TokenDetailsViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
) : ViewModel(), DefaultLifecycleObserver, TokenDetailsClickIntents {
private val userWalletId: UserWalletId = savedStateHandle.get<String>(TokenDetailsRouter.USER_WALLET_ID_KEY)
?.let { stringValue -> UserWalletId(stringValue) }
?: error("This screen can't open without `UserWalletId`")
private val cryptoCurrency: CryptoCurrency = savedStateHandle[TokenDetailsRouter.CRYPTO_CURRENCY_KEY]
?: error("This screen can't open without CryptoCurrency")
?: error("This screen can't open without `CryptoCurrency`")
var router by Delegates.notNull<InnerTokenDetailsRouter>()
private val marketPriceJobHolder = JobHolder()
private val refreshStateJobHolder = JobHolder()
private var cryptoCurrencyStatus: CryptoCurrencyStatus? = null
private var wallet by Delegates.notNull<UserWallet>()
private val selectedAppCurrencyFlow: StateFlow<AppCurrency> = createSelectedAppCurrencyFlow()
@ -91,23 +93,14 @@ internal class TokenDetailsViewModel @Inject constructor(
private set
override fun onCreate(owner: LifecycleOwner) {
getWallet()
updateContent(selectedWallet = wallet)
updateContent()
handleBalanceHiding(owner)
}
private fun getWallet() {
getSelectedWalletUseCase()
.fold(
ifLeft = { error("Can not get selected wallet $it") },
ifRight = { wallet = it },
)
}
private fun updateContent(selectedWallet: UserWallet) {
updateMarketPrice(selectedWallet = selectedWallet)
private fun updateContent() {
updateMarketPrice()
updateTxHistory(refresh = false, showItemsLoading = true)
updateWarnings(selectedWallet = selectedWallet)
updateWarnings()
}
private fun handleBalanceHiding(owner: LifecycleOwner) {
@ -135,10 +128,10 @@ internal class TokenDetailsViewModel @Inject constructor(
.launchIn(viewModelScope)
}
private fun updateWarnings(selectedWallet: UserWallet) {
private fun updateWarnings() {
viewModelScope.launch(dispatchers.io) {
getCurrencyWarningsUseCase.invoke(
userWalletId = selectedWallet.walletId,
userWalletId = userWalletId,
currency = cryptoCurrency,
derivationPath = cryptoCurrency.network.derivationPath,
)
@ -148,9 +141,9 @@ internal class TokenDetailsViewModel @Inject constructor(
}
}
private fun updateMarketPrice(selectedWallet: UserWallet) {
private fun updateMarketPrice() {
getCurrencyStatusUpdatesUseCase(
userWalletId = selectedWallet.walletId,
userWalletId = userWalletId,
currencyId = cryptoCurrency.id,
derivationPath = cryptoCurrency.network.derivationPath,
)
@ -159,7 +152,7 @@ internal class TokenDetailsViewModel @Inject constructor(
uiState = stateFactory.getCurrencyLoadedBalanceState(either)
either.onRight { status ->
cryptoCurrencyStatus = status
updateButtons(userWalletId = selectedWallet.walletId, currencyStatus = status)
updateButtons(userWalletId = userWalletId, currencyStatus = status)
}
}
.flowOn(dispatchers.io)
@ -175,6 +168,7 @@ internal class TokenDetailsViewModel @Inject constructor(
private fun updateTxHistory(refresh: Boolean, showItemsLoading: Boolean) {
viewModelScope.launch(dispatchers.io) {
val txHistoryItemsCountEither = txHistoryItemsCountUseCase(
userWalletId = userWalletId,
network = cryptoCurrency.network,
)
@ -184,9 +178,13 @@ internal class TokenDetailsViewModel @Inject constructor(
}
txHistoryItemsCountEither.onRight {
val either = txHistoryItemsUseCase(currency = cryptoCurrency)
.map { it.cachedIn(viewModelScope) }
uiState = stateFactory.getLoadedTxHistoryState(txHistoryEither = either)
val maybeTxHistory = txHistoryItemsUseCase(
userWalletId = userWalletId,
currency = cryptoCurrency,
refresh = refresh,
).map { it.cachedIn(viewModelScope) }
uiState = stateFactory.getLoadedTxHistoryState(maybeTxHistory)
}
}
}
@ -211,13 +209,15 @@ internal class TokenDetailsViewModel @Inject constructor(
analyticsEventsHandler.send(TokenScreenEvent.ButtonBuy(cryptoCurrency.symbol))
val status = cryptoCurrencyStatus ?: return
reduxStateHolder.dispatch(
TradeCryptoAction.New.Buy(
userWallet = wallet,
cryptoCurrencyStatus = status,
appCurrencyCode = selectedAppCurrencyFlow.value.code,
),
)
viewModelScope.launch(dispatchers.io) {
reduxStateHolder.dispatch(
TradeCryptoAction.New.Buy(
userWallet = getUserWalletUseCase(userWalletId).getOrElse { return@launch },
cryptoCurrencyStatus = status,
appCurrencyCode = selectedAppCurrencyFlow.value.code,
),
)
}
}
override fun onReloadClick() {
@ -231,38 +231,38 @@ internal class TokenDetailsViewModel @Inject constructor(
val cryptoCurrencyStatus = cryptoCurrencyStatus ?: return
when (cryptoCurrencyStatus.currency) {
is CryptoCurrency.Coin -> {
reduxStateHolder.dispatch(
action = TradeCryptoAction.New.SendCoin(
userWallet = wallet,
coinStatus = cryptoCurrencyStatus,
),
)
viewModelScope.launch(dispatchers.io) {
when (cryptoCurrencyStatus.currency) {
is CryptoCurrency.Coin -> {
reduxStateHolder.dispatch(
action = TradeCryptoAction.New.SendCoin(
userWallet = getUserWalletUseCase(userWalletId).getOrElse { return@launch },
coinStatus = cryptoCurrencyStatus,
),
)
}
is CryptoCurrency.Token -> sendToken(status = cryptoCurrencyStatus)
}
is CryptoCurrency.Token -> sendToken(status = cryptoCurrencyStatus)
}
}
private fun sendToken(status: CryptoCurrencyStatus) {
viewModelScope.launch(dispatchers.io) {
getNetworkCoinStatusUseCase(
userWalletId = wallet.walletId,
val maybeCoinStatus = getNetworkCoinStatusUseCase(
userWalletId = userWalletId,
networkId = status.currency.network.id,
derivationPath = status.currency.network.derivationPath,
)
.take(count = 1)
.collectLatest {
it.onRight { coinStatus ->
reduxStateHolder.dispatch(
action = TradeCryptoAction.New.SendToken(
userWallet = wallet,
tokenStatus = status,
coinFiatRate = coinStatus.value.fiatRate,
),
)
}
}
).firstOrNull()
maybeCoinStatus?.onRight { coinStatus ->
reduxStateHolder.dispatch(
action = TradeCryptoAction.New.SendToken(
userWallet = getUserWalletUseCase(userWalletId).getOrElse { return@launch },
tokenStatus = status,
coinFiatRate = coinStatus.value.fiatRate,
),
)
}
}
}
@ -271,7 +271,7 @@ internal class TokenDetailsViewModel @Inject constructor(
viewModelScope.launch(dispatchers.io) {
val addresses = walletManagersFacade.getAddress(
userWalletId = wallet.walletId,
userWalletId = userWalletId,
network = cryptoCurrency.network,
)
@ -314,7 +314,7 @@ internal class TokenDetailsViewModel @Inject constructor(
analyticsEventsHandler.send(TokenScreenEvent.ButtonRemoveToken(cryptoCurrency.symbol))
viewModelScope.launch {
val hasLinkedTokens = removeCurrencyUseCase.hasLinkedTokens(wallet.walletId, cryptoCurrency)
val hasLinkedTokens = removeCurrencyUseCase.hasLinkedTokens(userWalletId, cryptoCurrency)
uiState = if (hasLinkedTokens) {
stateFactory.getStateWithLinkedTokensDialog(cryptoCurrency)
} else {
@ -325,7 +325,7 @@ internal class TokenDetailsViewModel @Inject constructor(
override fun onHideConfirmed() {
viewModelScope.launch {
removeCurrencyUseCase.invoke(wallet.walletId, cryptoCurrency)
removeCurrencyUseCase.invoke(userWalletId, cryptoCurrency)
.onLeft { Timber.e(it) }
.onRight { router.popBackStack() }
}
@ -335,7 +335,7 @@ internal class TokenDetailsViewModel @Inject constructor(
analyticsEventsHandler.send(TokenScreenEvent.ButtonExplore(cryptoCurrency.symbol))
viewModelScope.launch(dispatchers.io) {
val addresses = walletManagersFacade.getAddress(
userWalletId = wallet.walletId,
userWalletId = userWalletId,
network = cryptoCurrency.network,
)
@ -357,7 +357,7 @@ internal class TokenDetailsViewModel @Inject constructor(
viewModelScope.launch {
router.openUrl(
url = getExploreUrlUseCase(
userWalletId = wallet.walletId,
userWalletId = userWalletId,
network = cryptoCurrency.network,
addressType = addressType,
),
@ -373,8 +373,8 @@ internal class TokenDetailsViewModel @Inject constructor(
viewModelScope.launch(dispatchers.io) {
listOf(
async {
fetchCurrencyStatusUseCase.invoke(
userWalletId = wallet.walletId,
fetchCurrencyStatusUseCase(
userWalletId = userWalletId,
id = cryptoCurrency.id,
derivationPath = cryptoCurrency.network.derivationPath,
refresh = true,
@ -386,7 +386,7 @@ internal class TokenDetailsViewModel @Inject constructor(
showItemsLoading = uiState.txHistoryState !is TxHistoryState.Content,
)
},
async { updateWarnings(wallet) },
async { updateWarnings() },
).awaitAll()
uiState = stateFactory.getRefreshedState()
}.saveIn(refreshStateJobHolder)

View file

@ -107,11 +107,14 @@ internal class DefaultWalletRouter(private val reduxNavController: ReduxNavContr
reduxNavController.navigate(action = NavigationAction.OpenUrl(url))
}
override fun openTokenDetails(currency: CryptoCurrency) {
override fun openTokenDetails(userWalletId: UserWalletId, currency: CryptoCurrency) {
reduxNavController.navigate(
action = NavigationAction.NavigateTo(
screen = AppScreen.WalletDetails,
bundle = bundleOf(TokenDetailsRouter.CRYPTO_CURRENCY_KEY to currency),
bundle = bundleOf(
TokenDetailsRouter.USER_WALLET_ID_KEY to userWalletId.stringValue,
TokenDetailsRouter.CRYPTO_CURRENCY_KEY to currency,
),
),
)
}

View file

@ -44,7 +44,7 @@ internal interface InnerWalletRouter : WalletRouter {
fun openTxHistoryWebsite(url: String)
/** Open token details screen */
fun openTokenDetails(currency: CryptoCurrency)
fun openTokenDetails(userWalletId: UserWalletId, currency: CryptoCurrency)
/** Open stories screen */
fun openStoriesScreen()

View file

@ -726,7 +726,7 @@ internal class WalletViewModel @Inject constructor(
override fun onTokenItemClick(currency: CryptoCurrency) {
analyticsEventsHandler.send(PortfolioEvent.TokenTapped)
router.openTokenDetails(currency = currency)
router.openTokenDetails(getSelectedWallet().walletId, currency)
}
override fun onTokenItemLongClick(cryptoCurrencyStatus: CryptoCurrencyStatus) {
@ -963,7 +963,7 @@ internal class WalletViewModel @Inject constructor(
private fun getSingleCurrencyContent(index: Int) {
val wallet = getWallet(index)
getPrimaryCurrencyStatusUpdatesUseCase(userWalletId = wallet.walletId)
getPrimaryCurrencyStatusUpdatesUseCase(wallet.walletId)
.distinctUntilChanged()
.onEach { maybeCryptoCurrencyStatus ->
uiState = stateFactory.getSingleCurrencyLoadedBalanceState(maybeCryptoCurrencyStatus)
@ -976,8 +976,8 @@ internal class WalletViewModel @Inject constructor(
}
updateNotifications(index)
updateButtons(userWalletId = wallet.walletId, currencyStatus = status)
updateTxHistory(status.currency)
updateButtons(wallet.walletId, status)
updateTxHistory(wallet.walletId, status.currency, refresh = false)
}
}
.flowOn(dispatchers.io)
@ -985,9 +985,12 @@ internal class WalletViewModel @Inject constructor(
.saveIn(marketPriceJobHolder)
}
private fun updateTxHistory(currency: CryptoCurrency) {
private fun updateTxHistory(userWalletId: UserWalletId, currency: CryptoCurrency, refresh: Boolean) {
viewModelScope.launch(dispatchers.io) {
val txHistoryItemsCountEither = txHistoryItemsCountUseCase(currency.network)
val txHistoryItemsCountEither = txHistoryItemsCountUseCase(
userWalletId = userWalletId,
network = currency.network,
)
uiState = stateFactory.getLoadingTxHistoryState(
itemsCountEither = txHistoryItemsCountEither,
@ -995,7 +998,11 @@ internal class WalletViewModel @Inject constructor(
txHistoryItemsCountEither.onRight {
uiState = stateFactory.getLoadedTxHistoryState(
txHistoryEither = txHistoryItemsUseCase(currency = currency).map {
txHistoryEither = txHistoryItemsUseCase(
userWalletId = userWalletId,
currency = currency,
refresh = refresh,
).map {
it.cachedIn(viewModelScope)
},
)
@ -1057,6 +1064,10 @@ internal class WalletViewModel @Inject constructor(
uiState = stateFactory.getRefreshedState()
uiState = result.fold(stateFactory::getStateByCurrencyStatusError) { uiState }
singleWalletCryptoCurrencyStatus?.let {
updateTxHistory(wallet.walletId, it.currency, refresh = true)
}
}.saveIn(refreshContentJobHolder)
}
@ -1091,5 +1102,12 @@ internal class WalletViewModel @Inject constructor(
)
}
private fun getSelectedWallet(): UserWallet {
val state = uiState as? WalletState.ContentState
?: error("Unable to get selected user wallet")
return getWallet(state.walletsListConfig.selectedWalletIndex)
}
private fun getCardTypeResolver(index: Int): CardTypesResolver = getWallet(index).scanResponse.cardTypesResolver
}