Updated on 2026-08-14

This commit is contained in:
Tangem 2024-05-22 12:40:28 +01:00
commit a8ef18ca56
14 changed files with 145 additions and 33 deletions

View file

@ -1,8 +1,10 @@
@file:Suppress("Filename")
package com.tangem.tap.di
import javax.inject.Qualifier
@Deprecated("Use one in Core Utils")
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class DelayedWork

View file

@ -160,10 +160,10 @@ private fun String.preserveDecimalSymbol(newValue: String, decimalSymbol: Char)
private fun String.preserveTrailingZeros(newValue: String, parsedDecimal: BigDecimal?, decimalSymbol: Char): String {
val trailingZeros = newValue.split(decimalSymbol).getOrNull(1)?.takeLastWhile { it == '0' }.orEmpty()
return if (parsedDecimal?.scale() == 0 && trailingZeros.isNotEmpty()) {
"$this$decimalSymbol$trailingZeros"
} else {
this.plus(trailingZeros)
return when {
this.endsWith('0') -> this
parsedDecimal?.scale() == 0 && trailingZeros.isNotEmpty() -> "$this$decimalSymbol$trailingZeros"
else -> this.plus(trailingZeros)
}
}

View file

@ -0,0 +1,7 @@
package com.tangem.utils.coroutines
import javax.inject.Qualifier
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class DelayedWork

View file

@ -0,0 +1,23 @@
package com.tangem.utils.di
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.DelayedWork
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object DelayedWorkCoroutineModule {
@Provides
@Singleton
@DelayedWork
fun provideDelayedWorkCoroutineScope(coroutineDispatcherProvider: CoroutineDispatcherProvider): CoroutineScope {
return CoroutineScope(SupervisorJob() + coroutineDispatcherProvider.io)
}
}

View file

@ -13,6 +13,7 @@ import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyAddress
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.NetworkStatus
import com.tangem.domain.tokens.repository.NetworksRepository
@ -78,7 +79,10 @@ internal class DefaultNetworksRepository(
return blockchain == Blockchain.Aptos
}
override suspend fun getNetworkAddresses(userWalletId: UserWalletId, network: Network): List<String> {
override suspend fun getNetworkAddresses(
userWalletId: UserWalletId,
network: Network,
): List<CryptoCurrencyAddress> {
// Get list of currencies matching [network]
val currencies = getCurrencies(userWalletId)
.filter { currency -> network.id == currency.network.id }
@ -87,9 +91,12 @@ internal class DefaultNetworksRepository(
if (currencies.toList().isEmpty()) return emptyList()
return currencies.toList().map { currency ->
walletManagersFacade.getAddresses(userWalletId, currency.network)
.firstOrNull { it.type == AddressType.Default }
?.value.orEmpty()
CryptoCurrencyAddress(
cryptoCurrency = currency,
address = walletManagersFacade.getAddresses(userWalletId, currency.network)
.firstOrNull { it.type == AddressType.Default }
?.value.orEmpty(),
)
}
}

View file

@ -0,0 +1,6 @@
package com.tangem.domain.tokens.model
data class CryptoCurrencyAddress(
val cryptoCurrency: CryptoCurrency,
val address: String,
)

View file

@ -1,5 +1,6 @@
package com.tangem.domain.tokens
import com.tangem.domain.tokens.model.CryptoCurrencyAddress
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.wallets.models.UserWalletId
@ -8,7 +9,7 @@ class GetNetworkAddressesUseCase(
internal val networksRepository: NetworksRepository,
) {
suspend fun invokeSync(userWalletId: UserWalletId, network: Network): List<String> {
suspend fun invokeSync(userWalletId: UserWalletId, network: Network): List<CryptoCurrencyAddress> {
return networksRepository.getNetworkAddresses(userWalletId, network)
}
}

View file

@ -1,5 +1,6 @@
package com.tangem.domain.tokens.repository
import com.tangem.domain.tokens.model.CryptoCurrencyAddress
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.NetworkStatus
import com.tangem.domain.wallets.models.UserWalletId
@ -46,5 +47,5 @@ interface NetworksRepository {
fun isNeedToCreateAccountWithoutReserve(network: Network): Boolean
suspend fun getNetworkAddresses(userWalletId: UserWalletId, network: Network): List<String>
suspend fun getNetworkAddresses(userWalletId: UserWalletId, network: Network): List<CryptoCurrencyAddress>
}

View file

@ -3,6 +3,7 @@ package com.tangem.domain.tokens.repository
import arrow.core.Either
import arrow.core.getOrElse
import com.tangem.domain.core.error.DataError
import com.tangem.domain.tokens.model.CryptoCurrencyAddress
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.NetworkStatus
import com.tangem.domain.wallets.models.UserWalletId
@ -34,4 +35,10 @@ internal class MockNetworksRepository(
}
override fun isNeedToCreateAccountWithoutReserve(network: Network) = false
override suspend fun getNetworkAddresses(
userWalletId: UserWalletId,
network: Network,
): List<CryptoCurrencyAddress> {
return emptyList()
}
}

View file

@ -1,6 +1,7 @@
package com.tangem.features.send.impl.presentation.domain
import androidx.compose.runtime.Immutable
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
/**
@ -15,4 +16,5 @@ data class AvailableWallet(
val name: String,
val userWalletId: UserWalletId,
val address: String,
val cryptoCurrency: CryptoCurrency,
)

View file

@ -2,12 +2,25 @@ package com.tangem.features.send.impl.presentation.state.previewdata
import androidx.compose.foundation.text.KeyboardOptions
import com.tangem.core.ui.extensions.stringReference
import com.tangem.features.send.impl.R
import com.tangem.features.send.impl.presentation.domain.SendRecipientListContent
import com.tangem.features.send.impl.presentation.state.SendStates
import com.tangem.features.send.impl.presentation.state.fields.SendTextField
import kotlinx.collections.immutable.persistentListOf
internal object RecipientStatePreviewData {
private val defaultRecentItem = SendRecipientListContent(
id = "sanctus",
title = stringReference("address"),
subtitle = stringReference("0.001 BTC"),
timestamp = stringReference("1.01.1970, 00:00"),
subtitleEndOffset = 0,
subtitleIconRes = R.drawable.ic_arrow_down_24,
isVisible = true,
isLoading = false,
)
val recipientState = SendStates.RecipientState(
addressTextField = SendTextField.RecipientAddress(
value = "0x23948239805671983476598176",
@ -19,8 +32,14 @@ internal object RecipientStatePreviewData {
error = null,
),
memoTextField = null,
recent = persistentListOf(),
wallets = persistentListOf(),
recent = persistentListOf(
defaultRecentItem.copy(id = "1"),
defaultRecentItem.copy(id = "2"),
defaultRecentItem.copy(id = "3"),
),
wallets = persistentListOf(
defaultRecentItem.copy(id = "4", subtitle = stringReference("Wallet")),
),
network = "Ethereum",
isValidating = false,
isPrimaryButtonEnabled = true,

View file

@ -95,11 +95,13 @@ private fun ListItemWithIcon(
address = title,
modifier = Modifier
.padding(vertical = TangemTheme.dimens.spacing8)
.size(TangemTheme.dimens.size36)
.clip(RoundedCornerShape(TangemTheme.dimens.radius18)),
.size(TangemTheme.dimens.size40)
.clip(RoundedCornerShape(TangemTheme.dimens.radius20)),
)
Column(
modifier = Modifier.padding(start = TangemTheme.dimens.spacing12),
modifier = Modifier
.height(TangemTheme.dimens.size36)
.padding(start = TangemTheme.dimens.spacing12),
verticalArrangement = Arrangement.SpaceBetween,
) {
EllipsisText(
@ -118,7 +120,8 @@ private fun ListItemWithIcon(
tint = TangemTheme.colors.icon.informative,
modifier = Modifier
.size(TangemTheme.dimens.size16)
.background(TangemTheme.colors.background.tertiary, CircleShape),
.background(TangemTheme.colors.background.tertiary, CircleShape)
.padding(TangemTheme.dimens.spacing2),
)
}
val (text, offset) = remember(subtitle, info) {
@ -152,11 +155,11 @@ private fun ListItemLoading(modifier: Modifier = Modifier) {
CircleShimmer(
modifier = Modifier
.padding(vertical = TangemTheme.dimens.spacing8)
.size(TangemTheme.dimens.size36),
.size(TangemTheme.dimens.size40),
)
Column(
modifier = Modifier
.height(TangemTheme.dimens.size32)
.height(TangemTheme.dimens.size36)
.padding(start = TangemTheme.dimens.spacing12),
verticalArrangement = Arrangement.SpaceBetween,
) {

View file

@ -17,6 +17,9 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import com.tangem.common.Strings.STARS
import com.tangem.core.ui.components.inputrow.InputRowRecipient
import com.tangem.core.ui.extensions.resolveReference
@ -26,6 +29,8 @@ import com.tangem.features.send.impl.presentation.analytics.EnterAddressSource
import com.tangem.features.send.impl.presentation.domain.SendRecipientListContent
import com.tangem.features.send.impl.presentation.state.SendStates
import com.tangem.features.send.impl.presentation.state.fields.SendTextField
import com.tangem.features.send.impl.presentation.state.previewdata.RecipientStatePreviewData
import com.tangem.features.send.impl.presentation.state.previewdata.SendClickIntentsStub
import com.tangem.features.send.impl.presentation.ui.common.FooterContainer
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
import kotlinx.collections.immutable.ImmutableList
@ -150,7 +155,7 @@ private fun LazyListScope.listHeaderItem(@StringRes titleRes: Int, isVisible: Bo
TangemTheme.dimens.spacing0 to TangemTheme.dimens.spacing8
}
val topRadius = if (isFirst) {
TangemTheme.dimens.radius12
TangemTheme.dimens.radius16
} else {
TangemTheme.dimens.radius0
}
@ -170,7 +175,7 @@ private fun LazyListScope.listHeaderItem(@StringRes titleRes: Int, isVisible: Bo
.background(TangemTheme.colors.background.action)
.padding(
top = paddingFromTop,
bottom = TangemTheme.dimens.spacing8,
bottom = TangemTheme.dimens.spacing12,
start = TangemTheme.dimens.spacing12,
end = TangemTheme.dimens.spacing12,
),
@ -242,4 +247,25 @@ private fun AnimateRecentAppearance(isVisible: Boolean, content: @Composable ()
Box(modifier = Modifier.fillMaxWidth())
}
}
}
@Preview(widthDp = 360, heightDp = 800)
@Composable
private fun SendRecipientContent_Preview(
@PreviewParameter(SendRecipientContentPreviewProvider::class) recipientState: SendStates.RecipientState,
) {
TangemTheme(isDark = false) {
SendRecipientContent(
uiState = recipientState,
clickIntents = SendClickIntentsStub,
isBalanceHidden = false,
)
}
}
private class SendRecipientContentPreviewProvider : PreviewParameterProvider<SendStates.RecipientState> {
override val values: Sequence<SendStates.RecipientState>
get() = sequenceOf(
RecipientStatePreviewData.recipientState,
)
}

View file

@ -56,9 +56,11 @@ import com.tangem.features.send.impl.presentation.state.fee.*
import com.tangem.lib.crypto.BlockchainUtils
import com.tangem.utils.Provider
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.DelayedWork
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import timber.log.Timber
@ -97,6 +99,7 @@ internal class SendViewModel @Inject constructor(
private val addCryptoCurrenciesUseCase: AddCryptoCurrenciesUseCase,
private val updateDelayedCurrencyStatusUseCase: UpdateDelayedNetworkStatusUseCase,
private val fetchPendingTransactionsUseCase: FetchPendingTransactionsUseCase,
@DelayedWork private val coroutineScope: CoroutineScope,
currencyChecksRepository: CurrencyChecksRepository,
isFeeApproximateUseCase: IsFeeApproximateUseCase,
validateWalletMemoUseCase: ValidateWalletMemoUseCase,
@ -415,8 +418,11 @@ internal class SendViewModel @Inject constructor(
}
}
private suspend fun List<UserWallet>.toAvailableWallets(): List<AvailableWallet> =
filterNot { it.walletId == userWalletId || it.isLocked }
private suspend fun List<UserWallet>.toAvailableWallets(): List<AvailableWallet> {
val currentAddress: String = kotlin.runCatching {
cryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value
}.getOrNull().orEmpty()
return filterNot { it.isLocked }
.mapNotNull { wallet ->
val addresses = if (!wallet.isMultiCurrency) {
getCryptoCurrencyUseCase(wallet.walletId).getOrNull()?.let {
@ -429,14 +435,18 @@ internal class SendViewModel @Inject constructor(
} else {
getNetworkAddressesUseCase.invokeSync(wallet.walletId, cryptoCurrency.network)
}
addresses?.map { address ->
AvailableWallet(
name = wallet.name,
address = address,
userWalletId = wallet.walletId,
)
}?.fastDistinctBy { it.address }
addresses
?.filter { it.address != currentAddress }
?.map { (cryptoCurrency, address) ->
AvailableWallet(
name = wallet.name,
address = address,
cryptoCurrency = cryptoCurrency,
userWalletId = wallet.walletId,
)
}?.fastDistinctBy { it.address }
}.flatten()
}
private suspend fun getTxHistory() {
val txHistoryList = getFixedTxHistoryItemsUseCase.getSync(
@ -890,11 +900,9 @@ internal class SendViewModel @Inject constructor(
}
private fun scheduleUpdates() {
viewModelScope.launch(dispatchers.main) {
coroutineScope.launch {
// we should update network to find pending tx after 1 sec
fetchPendingTransactionsUseCase(userWallet.walletId, setOf(cryptoCurrency.network))
}
viewModelScope.launch(dispatchers.main) {
// we should update network for new balance
updateDelayedCurrencyStatusUseCase(
userWalletId = userWallet.walletId,
@ -954,7 +962,7 @@ internal class SendViewModel @Inject constructor(
private companion object {
const val CHECK_FEE_UPDATE_DELAY = 60_000L
const val BALANCE_UPDATE_DELAY = 10_000L
const val BALANCE_UPDATE_DELAY = 11_000L
const val RU_LOCALE = "ru"
const val EN_LOCALE = "en"