Updated on 2026-08-14
This commit is contained in:
commit
5bca4d6107
19 changed files with 278 additions and 121 deletions
|
|
@ -3,8 +3,10 @@ package com.tangem.tap.di.domain
|
|||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.legacy.WalletsStateHolder
|
||||
import com.tangem.domain.wallets.repository.WalletAddressServiceRepository
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.domain.wallets.usecase.*
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -91,4 +93,16 @@ internal object WalletsDomainModule {
|
|||
fun providesShouldSaveUserWalletsUseCase(walletsRepository: WalletsRepository): ShouldSaveUserWalletsUseCase {
|
||||
return ShouldSaveUserWalletsUseCase(walletsRepository = walletsRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
fun providesValidateWalletAddressUseCase(
|
||||
walletAddressServiceRepository: WalletAddressServiceRepository,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): ValidateWalletAddressUseCase {
|
||||
return ValidateWalletAddressUseCase(
|
||||
walletAddressServiceRepository = walletAddressServiceRepository,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
package com.tangem.core.ui.components.inputrow
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment.Companion.CenterVertically
|
||||
|
|
@ -48,6 +50,7 @@ fun InputRowRecipient(
|
|||
error: TextReference? = null,
|
||||
isError: Boolean = false,
|
||||
showDivider: Boolean = false,
|
||||
isLoading: Boolean = false,
|
||||
) {
|
||||
val (titleText, color) = if (isError && error != null) {
|
||||
error to TangemTheme.colors.text.warning
|
||||
|
|
@ -63,23 +66,36 @@ fun InputRowRecipient(
|
|||
.fillMaxWidth()
|
||||
.padding(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
Text(
|
||||
text = titleText.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = color,
|
||||
)
|
||||
AnimatedContent(targetState = titleText, label = "Title Change") {
|
||||
Text(
|
||||
text = it.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = color,
|
||||
)
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(top = TangemTheme.dimens.spacing8),
|
||||
) {
|
||||
IdentIcon(
|
||||
address = value,
|
||||
AnimatedContent(
|
||||
targetState = isLoading,
|
||||
label = "Indicator Show Change",
|
||||
modifier = Modifier
|
||||
.align(CenterVertically)
|
||||
.clip(RoundedCornerShape(TangemTheme.dimens.radius18))
|
||||
.size(TangemTheme.dimens.size36)
|
||||
.background(TangemTheme.colors.background.tertiary),
|
||||
)
|
||||
) { showIndicator ->
|
||||
if (showIndicator) {
|
||||
CircularProgressIndicator(
|
||||
color = TangemTheme.colors.icon.informative,
|
||||
modifier = Modifier
|
||||
.padding(TangemTheme.dimens.spacing8),
|
||||
)
|
||||
} else {
|
||||
IdentIcon(address = value)
|
||||
}
|
||||
}
|
||||
SimpleTextField(
|
||||
value = value,
|
||||
placeholder = placeholder,
|
||||
|
|
@ -115,6 +131,7 @@ private fun InputRowRecipientPreview_Light(
|
|||
placeholder = TextReference.Res(R.string.send_optional_field),
|
||||
error = TextReference.Str("Error"),
|
||||
isError = value.isError,
|
||||
isLoading = value.isLoading,
|
||||
showDivider = true,
|
||||
onValueChange = {},
|
||||
onPasteClick = {},
|
||||
|
|
@ -134,6 +151,7 @@ private fun InputRowRecipientPreview_Dark(
|
|||
title = TextReference.Res(R.string.send_recipient),
|
||||
placeholder = TextReference.Res(R.string.send_optional_field),
|
||||
error = TextReference.Str("Error"),
|
||||
isLoading = value.isLoading,
|
||||
isError = value.isError,
|
||||
showDivider = true,
|
||||
onValueChange = {},
|
||||
|
|
@ -146,6 +164,7 @@ private fun InputRowRecipientPreview_Dark(
|
|||
private data class InputRowRecipientPreviewData(
|
||||
val value: String,
|
||||
val isError: Boolean,
|
||||
val isLoading: Boolean = false,
|
||||
)
|
||||
|
||||
private class InputRowRecipientPreviewDataProvider : PreviewParameterProvider<InputRowRecipientPreviewData> {
|
||||
|
|
@ -161,6 +180,7 @@ private class InputRowRecipientPreviewDataProvider : PreviewParameterProvider<In
|
|||
),
|
||||
InputRowRecipientPreviewData(
|
||||
value = "0x391316d97a07027a0702c8A002c8A0C25d8470",
|
||||
isLoading = true,
|
||||
isError = true,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -11,14 +11,27 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
/** Tangem libraries */
|
||||
implementation(deps.tangem.blockchain) // android-library
|
||||
|
||||
/** Core */
|
||||
implementation(projects.core.datasource)
|
||||
implementation(projects.core.utils)
|
||||
|
||||
/** Domain */
|
||||
implementation(projects.domain.wallets)
|
||||
|
||||
/** Domain models */
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.tokens.models)
|
||||
|
||||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
implementation(project(":domain:legacy"))
|
||||
kapt(deps.hilt.kapt)
|
||||
|
||||
/** Local storages */
|
||||
/** Other deps */
|
||||
implementation(deps.androidx.datastore)
|
||||
implementation(deps.arrow.core)
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.data.wallets
|
||||
|
||||
import com.tangem.blockchain.blockchains.near.NearWalletManager
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.repository.WalletAddressServiceRepository
|
||||
|
||||
class DefaultWalletAddressServiceRepository(
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
) : WalletAddressServiceRepository {
|
||||
override suspend fun validate(userWalletId: UserWalletId, network: Network, address: String): Boolean {
|
||||
val blockchain = Blockchain.fromId(network.id.value)
|
||||
val walletManager = walletManagersFacade.getOrCreateWalletManager(
|
||||
userWalletId = userWalletId,
|
||||
blockchain = blockchain,
|
||||
derivationPath = network.derivationPath.value,
|
||||
) ?: return false
|
||||
|
||||
return if (blockchain.isNear()) {
|
||||
(walletManager as? NearWalletManager)?.validateAddress(address) ?: false
|
||||
} else {
|
||||
blockchain.validateAddress(address)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Blockchain.isNear(): Boolean {
|
||||
return this == Blockchain.Near || this == Blockchain.NearTestnet
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,10 @@
|
|||
package com.tangem.data.wallets.di
|
||||
|
||||
import com.tangem.data.wallets.DefaultWalletAddressServiceRepository
|
||||
import com.tangem.data.wallets.DefaultWalletsRepository
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.repository.WalletAddressServiceRepository
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -18,4 +21,12 @@ internal object WalletsDataModule {
|
|||
fun providesWalletsRepository(appPreferencesStore: AppPreferencesStore): WalletsRepository {
|
||||
return DefaultWalletsRepository(appPreferencesStore = appPreferencesStore)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesWalletAddressServiceRepository(
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
): WalletAddressServiceRepository {
|
||||
return DefaultWalletAddressServiceRepository(walletManagersFacade)
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ dependencies {
|
|||
|
||||
// region Core modules
|
||||
implementation(projects.core.res)
|
||||
implementation(projects.core.utils)
|
||||
// endregion
|
||||
|
||||
// region Domain modules
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.domain.wallets.repository
|
||||
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
/**
|
||||
* Wallet address service repository.
|
||||
*/
|
||||
interface WalletAddressServiceRepository {
|
||||
|
||||
suspend fun validate(userWalletId: UserWalletId, network: Network, address: String): Boolean
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.domain.wallets.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.repository.WalletAddressServiceRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* Use case for validating wallet address.
|
||||
*/
|
||||
class ValidateWalletAddressUseCase(
|
||||
private val walletAddressServiceRepository: WalletAddressServiceRepository,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
address: String,
|
||||
): Either<Throwable, Boolean> = withContext(dispatchers.io) {
|
||||
Either.catch {
|
||||
walletAddressServiceRepository.validate(userWalletId, network, address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ import androidx.paging.PagingData
|
|||
import com.tangem.blockchain.common.address.Address
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.core.ui.components.currency.tokenicon.converter.CryptoCurrencyToIconStateConverter
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.txhistory.models.TxHistoryItem
|
||||
|
|
@ -23,7 +23,6 @@ import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientS
|
|||
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
|
||||
import com.tangem.features.send.impl.presentation.viewmodel.isNotAddressInWallet
|
||||
import com.tangem.features.send.impl.presentation.viewmodel.validateMemo
|
||||
import com.tangem.features.send.impl.presentation.viewmodel.verifyAddress
|
||||
import com.tangem.utils.Provider
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
|
@ -118,41 +117,64 @@ internal class SendStateFactory(
|
|||
)
|
||||
}
|
||||
|
||||
fun getOnRecipientAddressValueChangeState(value: String): SendUiState {
|
||||
fun onRecipientAddressValueChange(value: String): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
val recipientState = state.recipientState ?: return state
|
||||
return state.copy(
|
||||
recipientState = recipientState.copy(
|
||||
addressTextField = recipientState.addressTextField.copy(value = value),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun getOnRecipientAddressValidState(value: String, isValidAddress: Boolean): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
val recipientState = state.recipientState ?: return state
|
||||
|
||||
val isValidMemo = validateMemo(
|
||||
memo = recipientState.addressTextField.value.value,
|
||||
memo = recipientState.addressTextField.value,
|
||||
cryptoCurrency = cryptoCurrencyStatusProvider().currency,
|
||||
)
|
||||
val isAddressInWallet = isNotAddressInWallet(
|
||||
address = value,
|
||||
walletAddresses = walletAddressesProvider(),
|
||||
)
|
||||
val isValidAddress = verifyAddress(
|
||||
address = value,
|
||||
cryptoCurrency = cryptoCurrencyStatusProvider().currency,
|
||||
)
|
||||
|
||||
recipientState.addressTextField.update {
|
||||
it.copy(
|
||||
value = value,
|
||||
error = when {
|
||||
!isValidAddress || !isAddressInWallet -> TextReference.Res(R.string.send_recipient_address_error)
|
||||
else -> null
|
||||
},
|
||||
isError = !isValidAddress || !isAddressInWallet,
|
||||
)
|
||||
}
|
||||
return state.copy(
|
||||
recipientState = recipientState.copy(
|
||||
isPrimaryButtonEnabled = isValidMemo && isValidAddress && isAddressInWallet,
|
||||
isValidating = false,
|
||||
addressTextField = recipientState.addressTextField.copy(
|
||||
error = when {
|
||||
!isValidAddress || !isAddressInWallet -> resourceReference(
|
||||
R.string.send_recipient_address_error,
|
||||
)
|
||||
else -> null
|
||||
},
|
||||
isError = value.isNotEmpty() && !isValidAddress || !isAddressInWallet,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun getOnRecipientMemoValueChangeState(value: String): SendUiState {
|
||||
fun getOnRecipientAddressValidationStarted(): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
val recipientState = state.recipientState ?: return state
|
||||
return state.copy(
|
||||
recipientState = recipientState.copy(isValidating = true),
|
||||
)
|
||||
}
|
||||
|
||||
fun getOnRecipientMemoValueChange(value: String): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
val recipientState = state.recipientState ?: return state
|
||||
return state.copy(
|
||||
recipientState = recipientState.copy(
|
||||
memoTextField = recipientState.memoTextField?.copy(value = value),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun getOnRecipientMemoValidState(value: String, isValidAddress: Boolean): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
val recipientState = state.recipientState ?: return state
|
||||
|
||||
|
|
@ -162,22 +184,15 @@ internal class SendStateFactory(
|
|||
)
|
||||
val isAddressInWallet = isNotAddressInWallet(
|
||||
walletAddresses = walletAddressesProvider(),
|
||||
address = recipientState.addressTextField.value.value,
|
||||
address = recipientState.addressTextField.value,
|
||||
)
|
||||
val isValidAddress = verifyAddress(
|
||||
address = recipientState.addressTextField.value.value,
|
||||
cryptoCurrency = cryptoCurrencyStatusProvider().currency,
|
||||
)
|
||||
|
||||
recipientState.memoTextField?.update {
|
||||
it.copy(
|
||||
value = value,
|
||||
isError = !isValidMemo,
|
||||
)
|
||||
}
|
||||
return state.copy(
|
||||
recipientState = recipientState.copy(
|
||||
isPrimaryButtonEnabled = isValidMemo && isValidAddress && isAddressInWallet,
|
||||
isValidating = false,
|
||||
memoTextField = recipientState.memoTextField?.copy(
|
||||
isError = value.isNotEmpty() || isValidMemo && isValidAddress && isAddressInWallet,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,18 +43,19 @@ internal sealed class SendStates {
|
|||
val tokenIconState: TokenIconState,
|
||||
val isFiatValue: Boolean,
|
||||
val segmentedButtonConfig: PersistentList<SendAmountSegmentedButtonsConfig>,
|
||||
val amountTextField: MutableStateFlow<SendTextField.Amount>,
|
||||
val amountTextField: SendTextField.Amount,
|
||||
val isPrimaryButtonEnabled: Boolean,
|
||||
) : SendStates()
|
||||
|
||||
/** Recipient state */
|
||||
data class RecipientState(
|
||||
override val type: SendUiStateType = SendUiStateType.Recipient,
|
||||
val addressTextField: MutableStateFlow<SendTextField.RecipientAddress>,
|
||||
val memoTextField: MutableStateFlow<SendTextField.RecipientMemo>?,
|
||||
val addressTextField: SendTextField.RecipientAddress,
|
||||
val memoTextField: SendTextField.RecipientMemo?,
|
||||
val recipients: MutableStateFlow<PagingData<SendRecipientListContent>> = MutableStateFlow(PagingData.empty()),
|
||||
val network: String,
|
||||
val isPrimaryButtonEnabled: Boolean,
|
||||
val isValidating: Boolean = false,
|
||||
) : SendStates()
|
||||
|
||||
/** Fee and speed state */
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldCo
|
|||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
internal class SendAmountStateConverter(
|
||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||
|
|
@ -35,7 +34,7 @@ internal class SendAmountStateConverter(
|
|||
walletName = userWallet.name,
|
||||
walletBalance = "$crypto ($fiat)",
|
||||
tokenIconState = iconStateConverter.convert(status),
|
||||
amountTextField = MutableStateFlow(sendAmountFieldConverter.convert(Unit)),
|
||||
amountTextField = sendAmountFieldConverter.convert(Unit),
|
||||
isFiatValue = false,
|
||||
isPrimaryButtonEnabled = false,
|
||||
segmentedButtonConfig = persistentListOf(
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import com.tangem.features.send.impl.presentation.state.SendStates
|
|||
import com.tangem.features.send.impl.presentation.state.SendUiState
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.coroutines.flow.update
|
||||
import java.text.DecimalFormatSymbols
|
||||
import java.text.NumberFormat
|
||||
|
||||
|
|
@ -42,31 +41,27 @@ internal class SendAmountFieldChangeConverter(
|
|||
}
|
||||
|
||||
val isExceedBalance = value.checkExceedBalance(amountState.cryptoCurrencyStatus, amountState)
|
||||
amountState.amountTextField.update {
|
||||
it.copy(
|
||||
value = cryptoValue,
|
||||
fiatValue = fiatValue,
|
||||
isError = isExceedBalance,
|
||||
)
|
||||
}
|
||||
return state.copy(
|
||||
amountState = amountState.copy(
|
||||
isPrimaryButtonEnabled = !isExceedBalance,
|
||||
amountTextField = amountState.amountTextField.copy(
|
||||
value = cryptoValue,
|
||||
fiatValue = fiatValue,
|
||||
isError = isExceedBalance,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun SendUiState.emptyState(): SendUiState {
|
||||
amountState?.amountTextField?.update {
|
||||
it.copy(
|
||||
value = if (!amountState.isFiatValue) "" else DEFAULT_VALUE,
|
||||
fiatValue = if (amountState.isFiatValue) "" else DEFAULT_VALUE,
|
||||
isError = false,
|
||||
)
|
||||
}
|
||||
return copy(
|
||||
amountState = amountState?.copy(
|
||||
isPrimaryButtonEnabled = false,
|
||||
amountTextField = amountState.amountTextField.copy(
|
||||
value = if (!amountState.isFiatValue) "" else DEFAULT_VALUE,
|
||||
fiatValue = if (amountState.isFiatValue) "" else DEFAULT_VALUE,
|
||||
isError = false,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,29 +3,27 @@ package com.tangem.features.send.impl.presentation.state.recipient
|
|||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.features.send.impl.R
|
||||
import com.tangem.features.send.impl.presentation.state.fields.SendTextField
|
||||
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
internal class SendRecipientAddressFieldConverter(
|
||||
private val clickIntents: SendClickIntents,
|
||||
) : Converter<Unit, MutableStateFlow<SendTextField.RecipientAddress>> {
|
||||
) : Converter<Unit, SendTextField.RecipientAddress> {
|
||||
|
||||
override fun convert(value: Unit): MutableStateFlow<SendTextField.RecipientAddress> {
|
||||
return MutableStateFlow(
|
||||
SendTextField.RecipientAddress(
|
||||
value = "",
|
||||
onValueChange = clickIntents::onRecipientAddressValueChange,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
imeAction = ImeAction.Next,
|
||||
keyboardType = KeyboardType.Text,
|
||||
),
|
||||
placeholder = TextReference.Res(R.string.send_enter_address_field),
|
||||
label = TextReference.Res(R.string.send_recipient),
|
||||
override fun convert(value: Unit): SendTextField.RecipientAddress {
|
||||
return SendTextField.RecipientAddress(
|
||||
value = "",
|
||||
onValueChange = clickIntents::onRecipientAddressValueChange,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
imeAction = ImeAction.Next,
|
||||
keyboardType = KeyboardType.Text,
|
||||
),
|
||||
error = resourceReference(R.string.send_recipient_address_error),
|
||||
placeholder = resourceReference(R.string.send_enter_address_field),
|
||||
label = resourceReference(R.string.send_recipient),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,21 +4,20 @@ import androidx.compose.foundation.text.KeyboardOptions
|
|||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.send.impl.R
|
||||
import com.tangem.features.send.impl.presentation.state.fields.SendTextField
|
||||
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
internal class SendRecipientMemoFieldConverter(
|
||||
private val clickIntents: SendClickIntents,
|
||||
private val cryptoCurrencyStatus: Provider<CryptoCurrencyStatus>,
|
||||
) : Converter<Int, MutableStateFlow<SendTextField.RecipientMemo>> {
|
||||
) : Converter<Int, SendTextField.RecipientMemo> {
|
||||
|
||||
fun convertOrNull(): MutableStateFlow<SendTextField.RecipientMemo>? {
|
||||
fun convertOrNull(): SendTextField.RecipientMemo? {
|
||||
val cryptoCurrency = cryptoCurrencyStatus().currency
|
||||
|
||||
return when (cryptoCurrency.network.id.value) {
|
||||
|
|
@ -34,19 +33,17 @@ internal class SendRecipientMemoFieldConverter(
|
|||
}
|
||||
}
|
||||
|
||||
override fun convert(value: Int): MutableStateFlow<SendTextField.RecipientMemo> {
|
||||
return MutableStateFlow(
|
||||
SendTextField.RecipientMemo(
|
||||
value = "",
|
||||
onValueChange = clickIntents::onRecipientMemoValueChange,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
imeAction = ImeAction.Done,
|
||||
keyboardType = KeyboardType.Text,
|
||||
),
|
||||
placeholder = TextReference.Res(R.string.send_optional_field),
|
||||
label = TextReference.Res(value),
|
||||
error = TextReference.Res(R.string.send_memo_destination_tag_error),
|
||||
override fun convert(value: Int): SendTextField.RecipientMemo {
|
||||
return SendTextField.RecipientMemo(
|
||||
value = "",
|
||||
onValueChange = clickIntents::onRecipientMemoValueChange,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
imeAction = ImeAction.Done,
|
||||
keyboardType = KeyboardType.Text,
|
||||
),
|
||||
placeholder = resourceReference(R.string.send_optional_field),
|
||||
label = resourceReference(value),
|
||||
error = resourceReference(R.string.send_memo_destination_tag_error),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -11,14 +11,12 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.ui.components.currency.tokenicon.TokenIcon
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.send.impl.presentation.state.SendStates
|
||||
|
||||
@Composable
|
||||
internal fun AmountFieldContainer(amountState: SendStates.AmountState, modifier: Modifier = Modifier) {
|
||||
val amountTextField = amountState.amountTextField.collectAsStateWithLifecycle()
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
|
|
@ -54,7 +52,7 @@ internal fun AmountFieldContainer(amountState: SendStates.AmountState, modifier:
|
|||
.align(Alignment.CenterHorizontally),
|
||||
)
|
||||
AmountField(
|
||||
sendField = amountTextField.value,
|
||||
sendField = amountState.amountTextField,
|
||||
isFiat = amountState.isFiatValue,
|
||||
cryptoSymbol = amountState.cryptoCurrencyStatus.currency.symbol,
|
||||
fiatSymbol = amountState.appCurrency.symbol,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@ import androidx.compose.foundation.lazy.LazyListScope
|
|||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
|
@ -38,8 +40,9 @@ internal fun SendRecipientContent(
|
|||
recipientList: LazyPagingItems<SendRecipientListContent>,
|
||||
) {
|
||||
if (uiState == null) return
|
||||
val address = uiState.addressTextField.collectAsState().value
|
||||
val memo = uiState.memoTextField?.collectAsState()?.value
|
||||
val address = uiState.addressTextField
|
||||
val isValidating by remember(uiState.isValidating) { derivedStateOf { uiState.isValidating } }
|
||||
val isError by remember(address.isError) { derivedStateOf { address.isError } }
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
|
|
@ -57,7 +60,8 @@ internal fun SendRecipientContent(
|
|||
onValueChange = address.onValueChange,
|
||||
onPasteClick = clickIntents::onRecipientAddressValueChange,
|
||||
singleLine = true,
|
||||
isError = address.isError,
|
||||
isError = isError,
|
||||
isLoading = isValidating,
|
||||
error = address.error,
|
||||
modifier = Modifier
|
||||
.padding(top = TangemTheme.dimens.spacing4)
|
||||
|
|
@ -68,7 +72,7 @@ internal fun SendRecipientContent(
|
|||
)
|
||||
}
|
||||
}
|
||||
memo?.let { memoField ->
|
||||
uiState.memoTextField?.let { memoField ->
|
||||
item(key = MEMO_FIELD_KEY) {
|
||||
TextFieldWithPaste(
|
||||
value = memoField.value,
|
||||
|
|
|
|||
|
|
@ -114,14 +114,14 @@ private fun FromWallet(walletName: String, walletBalance: String) {
|
|||
|
||||
@Composable
|
||||
private fun AmountBlock(amountState: SendStates.AmountState, isSuccess: State<Boolean>, onClick: () -> Unit) {
|
||||
val amount = amountState.amountTextField.collectAsStateWithLifecycle()
|
||||
val amount = amountState.amountTextField
|
||||
|
||||
val cryptoAmount = formatCryptoAmount(
|
||||
cryptoCurrency = amountState.cryptoCurrencyStatus.currency,
|
||||
cryptoAmount = amount.value.value.toBigDecimalOrDefault(),
|
||||
cryptoAmount = amount.value.toBigDecimalOrDefault(),
|
||||
)
|
||||
val fiatAmount = BigDecimalFormatter.formatFiatAmount(
|
||||
fiatAmount = amount.value.fiatValue.toBigDecimalOrDefault(),
|
||||
fiatAmount = amount.fiatValue.toBigDecimalOrDefault(),
|
||||
fiatCurrencyCode = amountState.appCurrency.code,
|
||||
fiatCurrencySymbol = amountState.appCurrency.symbol,
|
||||
)
|
||||
|
|
@ -140,8 +140,8 @@ private fun AmountBlock(amountState: SendStates.AmountState, isSuccess: State<Bo
|
|||
|
||||
@Composable
|
||||
private fun RecipientBlock(recipientState: SendStates.RecipientState, isSuccess: State<Boolean>, onClick: () -> Unit) {
|
||||
val address = recipientState.addressTextField.collectAsStateWithLifecycle()
|
||||
val memo = recipientState.memoTextField?.collectAsStateWithLifecycle()
|
||||
val address = recipientState.addressTextField
|
||||
val memo = recipientState.memoTextField
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
|
|
@ -149,16 +149,16 @@ private fun RecipientBlock(recipientState: SendStates.RecipientState, isSuccess:
|
|||
.background(TangemTheme.colors.background.action)
|
||||
.clickable(enabled = !isSuccess.value) { onClick() },
|
||||
) {
|
||||
val showMemo = memo != null && memo.value.value.isNotBlank()
|
||||
val showMemo = memo != null && memo.value.isNotBlank()
|
||||
InputRowRecipientDefault(
|
||||
title = TextReference.Res(R.string.send_recipient),
|
||||
value = address.value.value,
|
||||
value = address.value,
|
||||
showDivider = showMemo,
|
||||
)
|
||||
if (showMemo) {
|
||||
InputRowDefault(
|
||||
title = TextReference.Res(R.string.send_extras_hint_memo),
|
||||
text = TextReference.Str(memo?.value?.value.orEmpty()),
|
||||
text = TextReference.Str(memo?.value.orEmpty()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import com.tangem.domain.wallets.models.UserWallet
|
|||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.domain.wallets.usecase.ValidateWalletAddressUseCase
|
||||
import com.tangem.features.send.api.navigation.SendRouter
|
||||
import com.tangem.features.send.impl.navigation.InnerSendRouter
|
||||
import com.tangem.features.send.impl.presentation.domain.AvailableWallet
|
||||
|
|
@ -74,6 +75,7 @@ internal class SendViewModel @Inject constructor(
|
|||
private val getFeeUseCase: GetFeeUseCase,
|
||||
private val sendTransactionUseCase: SendTransactionUseCase,
|
||||
private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase,
|
||||
private val validateWalletAddressUseCase: ValidateWalletAddressUseCase,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel(), DefaultLifecycleObserver, SendClickIntents {
|
||||
|
|
@ -110,6 +112,7 @@ internal class SendViewModel @Inject constructor(
|
|||
private var recipientsJobHolder = JobHolder()
|
||||
private var walletAddressesJobHolder = JobHolder()
|
||||
private var feeJobHolder = JobHolder()
|
||||
private var addressValidationJobHolder = JobHolder()
|
||||
|
||||
override fun onCreate(owner: LifecycleOwner) {
|
||||
getWalletAddresses()
|
||||
|
|
@ -255,8 +258,8 @@ internal class SendViewModel @Inject constructor(
|
|||
|
||||
stateFactory.onFeeOnLoadingState()
|
||||
getFeeUseCase.invoke(
|
||||
amount = amountState.amountTextField.value.value.toBigDecimal(),
|
||||
destination = recipientState.addressTextField.value.value,
|
||||
amount = amountState.amountTextField.value.toBigDecimal(),
|
||||
destination = recipientState.addressTextField.value,
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
)
|
||||
|
|
@ -319,15 +322,33 @@ internal class SendViewModel @Inject constructor(
|
|||
|
||||
// region recipient state clicks
|
||||
override fun onRecipientAddressValueChange(value: String) {
|
||||
if (!checkIfXrpAddressValue(value)) {
|
||||
uiState = stateFactory.getOnRecipientAddressValueChangeState(value)
|
||||
}
|
||||
uiState = stateFactory.onRecipientAddressValueChange(value)
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
uiState = stateFactory.getOnRecipientAddressValidationStarted()
|
||||
if (!checkIfXrpAddressValue(value)) {
|
||||
val isValidAddress = validateAddress(value)
|
||||
uiState = stateFactory.getOnRecipientAddressValidState(value, isValidAddress)
|
||||
}
|
||||
}.saveIn(addressValidationJobHolder)
|
||||
}
|
||||
|
||||
override fun onRecipientMemoValueChange(value: String) {
|
||||
if (!checkIfXrpAddressValue(value)) {
|
||||
uiState = stateFactory.getOnRecipientMemoValueChangeState(value)
|
||||
}
|
||||
uiState = stateFactory.getOnRecipientMemoValueChange(value)
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
uiState = stateFactory.getOnRecipientAddressValidationStarted()
|
||||
if (!checkIfXrpAddressValue(value)) {
|
||||
val isValidAddress = validateAddress(value)
|
||||
uiState = stateFactory.getOnRecipientMemoValidState(value, isValidAddress)
|
||||
}
|
||||
}.saveIn(addressValidationJobHolder)
|
||||
}
|
||||
|
||||
private suspend fun validateAddress(value: String): Boolean {
|
||||
return validateWalletAddressUseCase(
|
||||
userWalletId = userWalletId,
|
||||
network = cryptoCurrency.network,
|
||||
address = value,
|
||||
).getOrElse { false }
|
||||
}
|
||||
|
||||
private fun checkIfXrpAddressValue(value: String): Boolean {
|
||||
|
|
@ -395,7 +416,7 @@ internal class SendViewModel @Inject constructor(
|
|||
is TransactionFee.Single -> selectedFee.normal.amount.value
|
||||
} ?: BigDecimal.ZERO
|
||||
|
||||
return BigDecimal(amount.value).minus(fee)
|
||||
return BigDecimal(amount).minus(fee)
|
||||
}
|
||||
//endregion
|
||||
|
||||
|
|
@ -418,7 +439,7 @@ internal class SendViewModel @Inject constructor(
|
|||
val memo = uiState.recipientState?.memoTextField?.value
|
||||
val fee = getFee(feeState) ?: return
|
||||
|
||||
val amountToSend = amount.value.toBigDecimal().convertToAmount(cryptoCurrency)
|
||||
val amountToSend = amount.toBigDecimal().convertToAmount(cryptoCurrency)
|
||||
|
||||
// todo add notifications [[REDACTED_JIRA]]
|
||||
// val transactionErrors = walletManagersFacade.validateTransaction(
|
||||
|
|
@ -431,11 +452,11 @@ internal class SendViewModel @Inject constructor(
|
|||
val txData = walletManagersFacade.createTransaction(
|
||||
amount = amountToSend,
|
||||
fee = fee,
|
||||
memo = memo?.value,
|
||||
destination = recipient.value,
|
||||
memo = memo,
|
||||
destination = recipient,
|
||||
userWalletId = userWalletId,
|
||||
network = cryptoCurrency.network,
|
||||
)?.copy(extras = getMemoExtras(cryptoCurrency.network.id.value, memo?.value)) ?: return
|
||||
)?.copy(extras = getMemoExtras(cryptoCurrency.network.id.value, memo)) ?: return
|
||||
|
||||
sendTransactionUseCase(
|
||||
txData = txData,
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ spr-client = "3.6.2"
|
|||
# endregion Other libraries
|
||||
|
||||
# region Tangem
|
||||
tangemBlockchainSdk = "develop-411"
|
||||
tangemBlockchainSdk = "develop-418"
|
||||
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
|
||||
tangemCardSdk = "develop-312"
|
||||
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue