Updated on 2026-08-14
This commit is contained in:
commit
a8ef18ca56
14 changed files with 145 additions and 33 deletions
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
package com.tangem.utils.coroutines
|
||||
|
||||
import javax.inject.Qualifier
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class DelayedWork
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.domain.tokens.model
|
||||
|
||||
data class CryptoCurrencyAddress(
|
||||
val cryptoCurrency: CryptoCurrency,
|
||||
val address: String,
|
||||
)
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue