Updated on 2026-08-14
This commit is contained in:
parent
1f3f592cee
commit
0d8a99dd48
6 changed files with 130 additions and 69 deletions
|
|
@ -20,6 +20,7 @@ internal data class CustomTokenFormUM(
|
|||
val name: TextInputFieldUM,
|
||||
val symbol: TextInputFieldUM,
|
||||
val decimals: TextInputFieldUM,
|
||||
val wasFilled: Boolean = false,
|
||||
)
|
||||
|
||||
data class NotificationUM(
|
||||
|
|
|
|||
|
|
@ -92,15 +92,15 @@ internal class CustomTokenFormModel @Inject constructor(
|
|||
@OptIn(FlowPreview::class)
|
||||
private fun observeTokenFormUpdates() {
|
||||
state
|
||||
.sample(periodMillis = 1_000)
|
||||
.transform {
|
||||
val values = it.tokenForm?.mapToDomainModel()
|
||||
.transform { state ->
|
||||
val form = state.tokenForm
|
||||
|
||||
if (values != null) {
|
||||
emit(values)
|
||||
if (form != null && !form.wasFilled) {
|
||||
emit(form.mapToDomainModel())
|
||||
}
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.sample(periodMillis = 1_000)
|
||||
.onEach { formValues ->
|
||||
customCurrencyValidator.validateForm(
|
||||
userWalletId = params.userWalletId,
|
||||
|
|
@ -117,14 +117,16 @@ internal class CustomTokenFormModel @Inject constructor(
|
|||
createdCurrency = null
|
||||
|
||||
when (validatorState) {
|
||||
is CustomCurrencyValidator.State.NotStarted -> Unit
|
||||
is CustomCurrencyValidator.State.SearchingToken -> updateStateWithSearching()
|
||||
is CustomCurrencyValidator.State.UnexpectedException -> showErrorDialog()
|
||||
is CustomCurrencyValidator.State.FormValidationException -> updateStateWithExceptions(
|
||||
is CustomCurrencyValidator.Status.NotStarted,
|
||||
is CustomCurrencyValidator.Status.Validating,
|
||||
-> Unit
|
||||
is CustomCurrencyValidator.Status.SearchingToken -> updateStateWithProgress()
|
||||
is CustomCurrencyValidator.Status.UnexpectedException -> showErrorDialog()
|
||||
is CustomCurrencyValidator.Status.FormValidationException -> updateStateWithExceptions(
|
||||
exceptions = validatorState.exceptions,
|
||||
)
|
||||
is CustomCurrencyValidator.State.TokenNotFound -> updateStateWithNotFoundNotification()
|
||||
is CustomCurrencyValidator.State.Validated -> {
|
||||
is CustomCurrencyValidator.Status.TokenNotFound -> updateStateWithNotFoundNotification()
|
||||
is CustomCurrencyValidator.Status.Validated -> {
|
||||
createdCurrency = validatorState.currency
|
||||
|
||||
updateStateWithCurrency(
|
||||
|
|
@ -149,6 +151,7 @@ internal class CustomTokenFormModel @Inject constructor(
|
|||
.updateWithProgress(
|
||||
showProgress = false,
|
||||
canAddToken = !isAlreadyAdded,
|
||||
isWasFilled = fillForm,
|
||||
clearNotifications = true,
|
||||
clearFieldErrors = true,
|
||||
disableSecondaryFields = !isCustom,
|
||||
|
|
@ -183,7 +186,7 @@ internal class CustomTokenFormModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun updateStateWithSearching() {
|
||||
private fun updateStateWithProgress() {
|
||||
state.update { state ->
|
||||
state.updateWithProgress(
|
||||
showProgress = true,
|
||||
|
|
@ -274,7 +277,10 @@ internal class CustomTokenFormModel @Inject constructor(
|
|||
private fun updateContractAddress(value: String) {
|
||||
state.update { state ->
|
||||
state.updateTokenForm {
|
||||
copy(contractAddress = contractAddress.updateValue(value))
|
||||
copy(
|
||||
contractAddress = contractAddress.updateValue(value),
|
||||
wasFilled = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -282,7 +288,10 @@ internal class CustomTokenFormModel @Inject constructor(
|
|||
private fun updateTokenName(value: String) {
|
||||
state.update { state ->
|
||||
state.updateTokenForm {
|
||||
copy(name = name.updateValue(value))
|
||||
copy(
|
||||
name = name.updateValue(value),
|
||||
wasFilled = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -290,7 +299,10 @@ internal class CustomTokenFormModel @Inject constructor(
|
|||
private fun updateTokenSymbol(value: String) {
|
||||
state.update { state ->
|
||||
state.updateTokenForm {
|
||||
copy(symbol = symbol.updateValue(value))
|
||||
copy(
|
||||
symbol = symbol.updateValue(value),
|
||||
wasFilled = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -298,7 +310,10 @@ internal class CustomTokenFormModel @Inject constructor(
|
|||
private fun updateDecimals(value: String) {
|
||||
state.update { state ->
|
||||
state.updateTokenForm {
|
||||
copy(decimals = decimals.updateValue(value))
|
||||
copy(
|
||||
decimals = decimals.updateValue(value),
|
||||
wasFilled = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,8 +12,11 @@ import com.tangem.domain.managetokens.model.exceptoin.FindTokenException
|
|||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveInAndJoin
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -25,12 +28,20 @@ internal class CustomCurrencyValidator @Inject constructor(
|
|||
private val checkIsCurrencyNotAddedUseCase: CheckIsCurrencyNotAddedUseCase,
|
||||
) {
|
||||
|
||||
private val state: MutableStateFlow<State> = MutableStateFlow(State.NotStarted)
|
||||
private val validateFormJobHolder = JobHolder()
|
||||
private val state: MutableStateFlow<State> = MutableStateFlow(
|
||||
value = State(
|
||||
prevValidatedForm = null,
|
||||
prevFoundOrCreatedCurrency = null,
|
||||
status = Status.NotStarted,
|
||||
),
|
||||
)
|
||||
|
||||
suspend fun consumeUpdates(block: suspend (State) -> Unit) {
|
||||
state.collectLatest { s ->
|
||||
block(s)
|
||||
}
|
||||
suspend fun consumeUpdates(block: suspend (Status) -> Unit) {
|
||||
state
|
||||
.map { it.status }
|
||||
.distinctUntilChanged()
|
||||
.collectLatest { block(it) }
|
||||
}
|
||||
|
||||
suspend fun validateForm(
|
||||
|
|
@ -38,25 +49,37 @@ internal class CustomCurrencyValidator @Inject constructor(
|
|||
networkId: Network.ID,
|
||||
derivationPath: Network.DerivationPath,
|
||||
formValues: AddCustomTokenForm.Raw,
|
||||
) {
|
||||
) = coroutineScope {
|
||||
updateStatus(Status.Validating)
|
||||
|
||||
val result = validateTokenFormUseCase(
|
||||
networkId = networkId,
|
||||
formValues = formValues,
|
||||
)
|
||||
|
||||
val validatedForm = result.getOrElse { e ->
|
||||
state.value = State.FormValidationException(e)
|
||||
return
|
||||
updateStatus(Status.FormValidationException(e))
|
||||
return@coroutineScope
|
||||
}
|
||||
|
||||
when (validatedForm) {
|
||||
is AddCustomTokenForm.Validated.All -> {
|
||||
findOrCreateCurrency(userWalletId, networkId, derivationPath, validatedForm)
|
||||
}
|
||||
is AddCustomTokenForm.Validated.ContractAddress -> {
|
||||
findToken(userWalletId, networkId, derivationPath, validatedForm)
|
||||
if (state.value.prevValidatedForm == validatedForm) {
|
||||
return@coroutineScope
|
||||
} else {
|
||||
state.update { state ->
|
||||
state.copy(prevValidatedForm = validatedForm)
|
||||
}
|
||||
}
|
||||
|
||||
launch {
|
||||
when (validatedForm) {
|
||||
is AddCustomTokenForm.Validated.All -> {
|
||||
findOrCreateCurrency(userWalletId, networkId, derivationPath, validatedForm)
|
||||
}
|
||||
is AddCustomTokenForm.Validated.ContractAddress -> {
|
||||
findToken(userWalletId, networkId, derivationPath, validatedForm)
|
||||
}
|
||||
}
|
||||
}.saveInAndJoin(validateFormJobHolder)
|
||||
}
|
||||
|
||||
suspend fun createCoin(userWalletId: UserWalletId, networkId: Network.ID, derivationPath: Network.DerivationPath) {
|
||||
|
|
@ -69,7 +92,16 @@ internal class CustomCurrencyValidator @Inject constructor(
|
|||
derivationPath: Network.DerivationPath,
|
||||
validatedForm: AddCustomTokenForm.Validated.All,
|
||||
) {
|
||||
state.value = State.SearchingToken
|
||||
val currentState = state.value
|
||||
if (currentState.prevFoundOrCreatedCurrency is CryptoCurrency.Token &&
|
||||
currentState.prevFoundOrCreatedCurrency.contractAddress == validatedForm.contractAddress
|
||||
) {
|
||||
// No need to search for token again if contract address is not changed
|
||||
createCurrency(userWalletId, networkId, derivationPath, validatedForm)
|
||||
return
|
||||
}
|
||||
|
||||
updateStatus(Status.SearchingToken)
|
||||
|
||||
val foundToken = findTokenUseCase(
|
||||
userWalletId = userWalletId,
|
||||
|
|
@ -80,7 +112,7 @@ internal class CustomCurrencyValidator @Inject constructor(
|
|||
when (e) {
|
||||
is FindTokenException.DataError -> {
|
||||
Timber.e(e.cause, "Unable to find custom currency")
|
||||
state.value = State.UnexpectedException(e.cause)
|
||||
updateStatus(Status.UnexpectedException(e.cause))
|
||||
return
|
||||
}
|
||||
is FindTokenException.NotFound -> {
|
||||
|
|
@ -102,7 +134,7 @@ internal class CustomCurrencyValidator @Inject constructor(
|
|||
derivationPath: Network.DerivationPath,
|
||||
validatedForm: AddCustomTokenForm.Validated.ContractAddress,
|
||||
) {
|
||||
state.value = State.SearchingToken
|
||||
updateStatus(Status.SearchingToken)
|
||||
|
||||
val token = findTokenUseCase(
|
||||
userWalletId = userWalletId,
|
||||
|
|
@ -110,15 +142,16 @@ internal class CustomCurrencyValidator @Inject constructor(
|
|||
networkId = networkId,
|
||||
derivationPath = derivationPath,
|
||||
).getOrElse { e ->
|
||||
state.value = when (e) {
|
||||
val newStatus = when (e) {
|
||||
is FindTokenException.DataError -> {
|
||||
Timber.e(e.cause, "Unable to find custom currency")
|
||||
State.UnexpectedException(e.cause)
|
||||
Status.UnexpectedException(e.cause)
|
||||
}
|
||||
is FindTokenException.NotFound -> {
|
||||
State.TokenNotFound
|
||||
Status.TokenNotFound
|
||||
}
|
||||
}
|
||||
updateStatus(newStatus)
|
||||
|
||||
return
|
||||
}
|
||||
|
|
@ -138,7 +171,7 @@ internal class CustomCurrencyValidator @Inject constructor(
|
|||
formValues = validatedForm,
|
||||
).getOrElse { e ->
|
||||
Timber.e(e, "Unable to create custom currency")
|
||||
state.value = State.UnexpectedException(e)
|
||||
updateStatus(Status.UnexpectedException(e))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -151,6 +184,9 @@ internal class CustomCurrencyValidator @Inject constructor(
|
|||
fillForm: Boolean,
|
||||
isCustom: Boolean,
|
||||
) {
|
||||
val currentStatus = state.value.status
|
||||
if (currentStatus is Status.Validated && currentStatus.currency == currency) return
|
||||
|
||||
val isNotAdded = checkIsCurrencyNotAddedUseCase(
|
||||
userWalletId = userWalletId,
|
||||
networkId = currency.network.id,
|
||||
|
|
@ -161,54 +197,59 @@ internal class CustomCurrencyValidator @Inject constructor(
|
|||
},
|
||||
).getOrElse { e ->
|
||||
Timber.e(e, "Unable to check if currency is already added")
|
||||
state.value = State.UnexpectedException(e)
|
||||
updateStatus(Status.UnexpectedException(e))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
state.value = State.Validated(
|
||||
currency = currency,
|
||||
fillForm = fillForm,
|
||||
isAlreadyAdded = !isNotAdded,
|
||||
isCustom = isCustom,
|
||||
)
|
||||
state.update { state ->
|
||||
state.copy(
|
||||
status = Status.Validated(
|
||||
currency = currency,
|
||||
fillForm = fillForm,
|
||||
isAlreadyAdded = !isNotAdded,
|
||||
isCustom = isCustom,
|
||||
),
|
||||
prevFoundOrCreatedCurrency = currency,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
sealed class State {
|
||||
|
||||
abstract val isFinished: Boolean
|
||||
|
||||
data object NotStarted : State() {
|
||||
override val isFinished: Boolean = false
|
||||
private fun updateStatus(status: Status) {
|
||||
state.update { state ->
|
||||
state.copy(status = status)
|
||||
}
|
||||
}
|
||||
|
||||
data object SearchingToken : State() {
|
||||
override val isFinished: Boolean = false
|
||||
}
|
||||
data class State(
|
||||
val prevValidatedForm: AddCustomTokenForm.Validated?,
|
||||
val prevFoundOrCreatedCurrency: CryptoCurrency?,
|
||||
val status: Status,
|
||||
)
|
||||
|
||||
sealed class Status {
|
||||
|
||||
data object NotStarted : Status()
|
||||
|
||||
data object SearchingToken : Status()
|
||||
|
||||
data object Validating : Status()
|
||||
|
||||
data class Validated(
|
||||
val currency: CryptoCurrency,
|
||||
val fillForm: Boolean,
|
||||
val isAlreadyAdded: Boolean,
|
||||
val isCustom: Boolean,
|
||||
) : State() {
|
||||
override val isFinished: Boolean = true
|
||||
}
|
||||
) : Status()
|
||||
|
||||
data class FormValidationException(
|
||||
val exceptions: List<CustomTokenFormValidationException>,
|
||||
) : State() {
|
||||
override val isFinished: Boolean = true
|
||||
}
|
||||
) : Status()
|
||||
|
||||
data object TokenNotFound : State() {
|
||||
override val isFinished: Boolean = true
|
||||
}
|
||||
data object TokenNotFound : Status()
|
||||
|
||||
data class UnexpectedException(
|
||||
val cause: Throwable,
|
||||
) : State() {
|
||||
override val isFinished: Boolean = true
|
||||
}
|
||||
) : Status()
|
||||
}
|
||||
}
|
||||
|
|
@ -38,6 +38,7 @@ internal fun TextInputFieldUM.updateValue(
|
|||
|
||||
internal fun CustomTokenFormUM.updateWithProgress(
|
||||
showProgress: Boolean,
|
||||
isWasFilled: Boolean = this.tokenForm?.wasFilled ?: false,
|
||||
canAddToken: Boolean = this.canAddToken,
|
||||
clearNotifications: Boolean = false,
|
||||
clearFieldErrors: Boolean = false,
|
||||
|
|
@ -64,6 +65,7 @@ internal fun CustomTokenFormUM.updateWithProgress(
|
|||
isEnabled = !showProgress && !disableSecondaryFields,
|
||||
clearError = clearFieldErrors,
|
||||
),
|
||||
wasFilled = isWasFilled,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue