Updated on 2026-08-14
This commit is contained in:
commit
cef36091da
379 changed files with 8948 additions and 8976 deletions
|
|
@ -3,7 +3,6 @@ package com.tangem.tap.features.customtoken.impl.di
|
|||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.domain.card.DerivePublicKeysUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.features.tester.api.TesterFeatureToggles
|
||||
import com.tangem.tap.features.customtoken.impl.data.DefaultCustomTokenRepository
|
||||
import com.tangem.tap.features.customtoken.impl.domain.CustomTokenInteractor
|
||||
import com.tangem.tap.features.customtoken.impl.domain.DefaultCustomTokenInteractor
|
||||
|
|
@ -30,7 +29,6 @@ internal object CustomTokenInteractorModule {
|
|||
reduxStateHolder: AppStateHolder,
|
||||
getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||
derivePublicKeysUseCase: DerivePublicKeysUseCase,
|
||||
testerFeatureToggles: TesterFeatureToggles,
|
||||
): CustomTokenInteractor {
|
||||
return DefaultCustomTokenInteractor(
|
||||
featureRepository = DefaultCustomTokenRepository(
|
||||
|
|
@ -40,7 +38,6 @@ internal object CustomTokenInteractorModule {
|
|||
),
|
||||
getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase,
|
||||
derivePublicKeysUseCase = derivePublicKeysUseCase,
|
||||
testerFeatureToggles = testerFeatureToggles,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,40 +1,19 @@
|
|||
package com.tangem.tap.features.customtoken.impl.domain
|
||||
|
||||
import com.tangem.blockchain.blockchains.cardano.CardanoUtils
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.common.extensions.toMapKey
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.data.tokens.utils.CryptoCurrencyFactory
|
||||
import com.tangem.domain.card.DerivePublicKeysUseCase
|
||||
import com.tangem.domain.common.configs.CardConfig
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.common.util.hasDerivation
|
||||
import com.tangem.domain.features.addCustomToken.CustomCurrency
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.features.tester.api.TesterFeatureToggles
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.model.Currency
|
||||
import com.tangem.tap.features.customtoken.impl.domain.models.FoundToken
|
||||
import com.tangem.tap.features.tokens.legacy.redux.TokensMiddleware
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import com.tangem.tap.userWalletsListManager
|
||||
import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE
|
||||
import kotlinx.coroutines.delay
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
|
|
@ -48,7 +27,6 @@ class DefaultCustomTokenInteractor(
|
|||
private val featureRepository: CustomTokenRepository,
|
||||
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||
private val derivePublicKeysUseCase: DerivePublicKeysUseCase,
|
||||
private val testerFeatureToggles: TesterFeatureToggles,
|
||||
) : CustomTokenInteractor {
|
||||
|
||||
// TODO: Move to DI
|
||||
|
|
@ -70,136 +48,12 @@ class DefaultCustomTokenInteractor(
|
|||
val userWallet = getSelectedWalletSyncUseCase().fold(ifLeft = { return }, ifRight = { it })
|
||||
val currency = Currency.fromCustomCurrency(customCurrency)
|
||||
|
||||
if (testerFeatureToggles.isDerivePublicKeysRefactoringEnabled) {
|
||||
val currencies = listOfNotNull(element = currency.toCryptoCurrency(userWallet.scanResponse))
|
||||
derivePublicKeysUseCase(userWalletId = userWallet.walletId, currencies = currencies)
|
||||
.onRight {
|
||||
addCryptoCurrenciesUseCase(userWalletId = userWallet.walletId, currencies = currencies)
|
||||
}
|
||||
.onLeft { Timber.e("Failed to derive public keys: $it") }
|
||||
} else {
|
||||
// TODO: delete [REDACTED_JIRA]
|
||||
val isNeedToDerive = isNeedToDerive(userWallet, currency)
|
||||
if (isNeedToDerive) {
|
||||
deriveMissingBlockchains(
|
||||
userWallet = userWallet,
|
||||
currencyList = listOf(currency),
|
||||
onSuccess = { submitAdd(userWallet = userWallet.copy(scanResponse = it), currency = currency) },
|
||||
) {
|
||||
throw it
|
||||
}
|
||||
} else {
|
||||
submitAdd(userWallet, currency)
|
||||
val currencies = listOfNotNull(element = currency.toCryptoCurrency(userWallet.scanResponse))
|
||||
derivePublicKeysUseCase(userWalletId = userWallet.walletId, currencies = currencies)
|
||||
.onRight {
|
||||
addCryptoCurrenciesUseCase(userWalletId = userWallet.walletId, currencies = currencies)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isNeedToDerive(userWallet: UserWallet, currency: Currency): Boolean {
|
||||
val scanResponse = userWallet.scanResponse
|
||||
return currency.derivationPath?.let { !scanResponse.hasDerivation(currency.blockchain, it) } ?: false
|
||||
}
|
||||
|
||||
private suspend fun deriveMissingBlockchains(
|
||||
userWallet: UserWallet,
|
||||
currencyList: List<Currency>,
|
||||
onSuccess: suspend (ScanResponse) -> Unit,
|
||||
onFailure: suspend (TangemError) -> Unit,
|
||||
) {
|
||||
val scanResponse = userWallet.scanResponse
|
||||
val config = CardConfig.createConfig(scanResponse.card)
|
||||
val derivationDataList = currencyList.mapNotNull { currency ->
|
||||
val curve = config.primaryCurve(currency.blockchain)
|
||||
curve?.let { getDerivations(curve, scanResponse, currency) }
|
||||
}
|
||||
|
||||
val derivations = buildMap<ByteArrayKey, MutableList<DerivationPath>> {
|
||||
derivationDataList.forEach {
|
||||
val current = this[it.derivations.first]
|
||||
if (current != null) {
|
||||
current.addAll(it.derivations.second)
|
||||
current.distinct()
|
||||
} else {
|
||||
this[it.derivations.first] = it.derivations.second.toMutableList()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (derivations.isEmpty()) {
|
||||
onSuccess(scanResponse)
|
||||
return
|
||||
}
|
||||
|
||||
when (val result = tangemSdkManager.derivePublicKeys(cardId = null, derivations = derivations)) {
|
||||
is CompletionResult.Success -> {
|
||||
val newDerivedKeys = result.data.entries
|
||||
val oldDerivedKeys = scanResponse.derivedKeys
|
||||
|
||||
val walletKeys = (newDerivedKeys.keys + oldDerivedKeys.keys).toSet()
|
||||
|
||||
val updatedDerivedKeys = walletKeys.associateWith { walletKey ->
|
||||
val oldDerivations = ExtendedPublicKeysMap(oldDerivedKeys[walletKey] ?: emptyMap())
|
||||
val newDerivations = newDerivedKeys[walletKey] ?: ExtendedPublicKeysMap(emptyMap())
|
||||
ExtendedPublicKeysMap(oldDerivations + newDerivations)
|
||||
}
|
||||
|
||||
val updatedScanResponse = scanResponse.copy(derivedKeys = updatedDerivedKeys)
|
||||
store.dispatchOnMain(GlobalAction.SaveScanResponse(updatedScanResponse))
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
|
||||
onSuccess(updatedScanResponse)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
onFailure.invoke(result.error)
|
||||
store.dispatchDebugErrorNotification(TapError.CustomError("Error adding tokens"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDerivations(
|
||||
curve: EllipticCurve,
|
||||
scanResponse: ScanResponse,
|
||||
currency: Currency,
|
||||
): TokensMiddleware.DerivationData? {
|
||||
val wallet = scanResponse.card.wallets.firstOrNull { it.curve == curve } ?: return null
|
||||
|
||||
val supportedCurves = currency.blockchain.getSupportedCurves()
|
||||
val path = currency.blockchain.derivationPath(scanResponse.derivationStyleProvider.getDerivationStyle())
|
||||
.takeIf { supportedCurves.contains(curve) }
|
||||
|
||||
val customPath = currency.derivationPath?.let {
|
||||
DerivationPath(it)
|
||||
}.takeIf { supportedCurves.contains(curve) }
|
||||
|
||||
val bothCandidates = listOfNotNull(path, customPath).distinct().toMutableList()
|
||||
if (bothCandidates.isEmpty()) return null
|
||||
|
||||
if (currency is Currency.Blockchain && currency.blockchain == Blockchain.Cardano) {
|
||||
currency.derivationPath?.let {
|
||||
bothCandidates.add(CardanoUtils.extendedDerivationPath(DerivationPath(it)))
|
||||
}
|
||||
}
|
||||
|
||||
val mapKeyOfWalletPublicKey = wallet.publicKey.toMapKey()
|
||||
val alreadyDerivedKeys: ExtendedPublicKeysMap =
|
||||
scanResponse.derivedKeys[mapKeyOfWalletPublicKey] ?: ExtendedPublicKeysMap(emptyMap())
|
||||
val alreadyDerivedPaths = alreadyDerivedKeys.keys.toList()
|
||||
|
||||
val toDerive = bothCandidates.filterNot { alreadyDerivedPaths.contains(it) }
|
||||
if (toDerive.isEmpty()) return null
|
||||
|
||||
return TokensMiddleware.DerivationData(derivations = mapKeyOfWalletPublicKey to toDerive)
|
||||
}
|
||||
|
||||
private suspend fun submitAdd(userWallet: UserWallet, currency: Currency) {
|
||||
val scanResponse = userWallet.scanResponse
|
||||
val userWalletId = userWallet.walletId
|
||||
|
||||
val currencyList = listOfNotNull(element = currency.toCryptoCurrency(scanResponse))
|
||||
|
||||
userWalletsListManager.update(userWalletId) {
|
||||
it.copy(scanResponse = scanResponse)
|
||||
}
|
||||
|
||||
addCryptoCurrenciesUseCase(userWalletId, currencyList)
|
||||
.onLeft { Timber.e("Failed to derive public keys: $it") }
|
||||
}
|
||||
|
||||
private fun Currency.toCryptoCurrency(scanResponse: ScanResponse): CryptoCurrency? {
|
||||
|
|
|
|||
|
|
@ -3,21 +3,21 @@ package com.tangem.tap.features.customtoken.impl.presentation.validators
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.address.AddressService
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.domain.AddCustomTokenError
|
||||
import com.tangem.domain.tokens.error.AddCustomTokenError
|
||||
|
||||
/**
|
||||
* Validator of contract address
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
object ContactAddressValidator {
|
||||
object ContractAddressValidator {
|
||||
|
||||
/** Validate a [address] using [blockchain] */
|
||||
fun validate(address: String, blockchain: Blockchain): ContractAddressValidatorResult {
|
||||
return when {
|
||||
address.isEmpty() -> ContractAddressValidatorResult.Error(type = AddCustomTokenError.FieldIsEmpty)
|
||||
address.isEmpty() -> ContractAddressValidatorResult.Error(type = AddCustomTokenError.FIELD_IS_EMPTY)
|
||||
validateAddress(blockchain, address) -> ContractAddressValidatorResult.Success
|
||||
else -> ContractAddressValidatorResult.Error(type = AddCustomTokenError.InvalidContractAddress)
|
||||
else -> ContractAddressValidatorResult.Error(type = AddCustomTokenError.INVALID_CONTRACT_ADDRESS)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
package com.tangem.tap.features.customtoken.impl.presentation.validators
|
||||
|
||||
import com.tangem.domain.AddCustomTokenError
|
||||
import com.tangem.domain.tokens.error.AddCustomTokenError
|
||||
|
||||
/**
|
||||
* Result of validation contract address
|
||||
|
|
|
|||
|
|
@ -16,13 +16,13 @@ import com.tangem.blockchain.common.derivation.DerivationStyle
|
|||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.crypto.hdWallet.HDWalletError
|
||||
import com.tangem.domain.AddCustomTokenError
|
||||
import com.tangem.domain.common.DerivationStyleProvider
|
||||
import com.tangem.domain.common.extensions.*
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.features.addCustomToken.CustomCurrency
|
||||
import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase
|
||||
import com.tangem.domain.tokens.error.AddCustomTokenError
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.tap.features.customtoken.impl.domain.CustomTokenInteractor
|
||||
|
|
@ -33,7 +33,7 @@ import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTok
|
|||
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenSelectorField.SelectorItem
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.routers.CustomTokenRouter
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.states.AddCustomTokenStateHolder
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.validators.ContactAddressValidator
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.validators.ContractAddressValidator
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.validators.ContractAddressValidatorResult
|
||||
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
||||
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
||||
|
|
@ -461,11 +461,11 @@ internal class AddCustomTokenViewModel @Inject constructor(
|
|||
private fun getTokenWarningSet(): Set<AddCustomTokenWarning> {
|
||||
val networkSelectorValue = uiState.form.networkSelectorField.selectedItem.blockchain
|
||||
|
||||
val isContractAddressFieldEmpty = ContactAddressValidator.validate(
|
||||
val isContractAddressFieldEmpty = ContractAddressValidator.validate(
|
||||
address = uiState.form.contractAddressInputField.value,
|
||||
blockchain = networkSelectorValue,
|
||||
).let {
|
||||
it is ContractAddressValidatorResult.Error && it.type == AddCustomTokenError.FieldIsEmpty
|
||||
it is ContractAddressValidatorResult.Error && it.type == AddCustomTokenError.FIELD_IS_EMPTY
|
||||
}
|
||||
|
||||
val isSupportedToken = if (!isNetworkSelected()) {
|
||||
|
|
@ -513,7 +513,7 @@ internal class AddCustomTokenViewModel @Inject constructor(
|
|||
val state = when {
|
||||
isAllTokenFieldsFilled() && isNetworkSelected() -> {
|
||||
val networkSelectorValue = uiState.form.networkSelectorField.selectedItem.blockchain
|
||||
val error = ContactAddressValidator.validate(
|
||||
val error = ContractAddressValidator.validate(
|
||||
address = uiState.form.contractAddressInputField.value,
|
||||
blockchain = networkSelectorValue,
|
||||
)
|
||||
|
|
@ -621,7 +621,7 @@ internal class AddCustomTokenViewModel @Inject constructor(
|
|||
|
||||
private fun handleContractAddressErrorValidation(type: AddCustomTokenError) {
|
||||
when {
|
||||
isNetworkSelected() && type == AddCustomTokenError.InvalidContractAddress -> {
|
||||
isNetworkSelected() && type == AddCustomTokenError.INVALID_CONTRACT_ADDRESS -> {
|
||||
val isAnotherTokenFieldsFilled = isAnyTokenFieldsFilled()
|
||||
uiState = uiState.copySealed(
|
||||
form = uiState.form.copy(
|
||||
|
|
@ -644,7 +644,7 @@ internal class AddCustomTokenViewModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
!isNetworkSelected() || type == AddCustomTokenError.FieldIsEmpty -> {
|
||||
!isNetworkSelected() || type == AddCustomTokenError.FIELD_IS_EMPTY -> {
|
||||
uiState = uiState.copySealed(
|
||||
form = uiState.form.copy(
|
||||
contractAddressInputField = uiState.form.contractAddressInputField.copy(isError = false),
|
||||
|
|
@ -727,7 +727,7 @@ internal class AddCustomTokenViewModel @Inject constructor(
|
|||
)
|
||||
|
||||
val selectedNetwork = uiState.form.networkSelectorField.selectedItem.blockchain
|
||||
val validatorResult = ContactAddressValidator.validate(
|
||||
val validatorResult = ContractAddressValidator.validate(
|
||||
address = enteredValue,
|
||||
blockchain = selectedNetwork,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ private fun StoriesScreenContent(config: StoriesScreenContentConfig, modifier: M
|
|||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.align(Alignment.BottomCenter)
|
||||
.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16),
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = config.currentStory == Stories.Currencies,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.Arrangement
|
|||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.ButtonColors
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
|
@ -13,15 +12,11 @@ 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.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.components.SpacerW8
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButton
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonColors
|
||||
import com.tangem.core.ui.components.SpacerW12
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@Composable
|
||||
internal fun HomeButtons(
|
||||
btnScanStateInProgress: Boolean,
|
||||
|
|
@ -38,7 +33,7 @@ internal fun HomeButtons(
|
|||
showProgress = btnScanStateInProgress,
|
||||
onClick = onScanButtonClick,
|
||||
)
|
||||
SpacerW8()
|
||||
SpacerW12()
|
||||
OrderCardButton(
|
||||
modifier = Modifier.weight(weight = 1f),
|
||||
onClick = onShopButtonClick,
|
||||
|
|
@ -48,44 +43,26 @@ internal fun HomeButtons(
|
|||
|
||||
@Composable
|
||||
private fun ScanCardButton(showProgress: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
TangemButton(
|
||||
StoriesButton(
|
||||
modifier = modifier,
|
||||
text = stringResource(id = R.string.home_button_scan),
|
||||
useDarkerColors = false,
|
||||
icon = TangemButtonIconPosition.End(iconResId = R.drawable.ic_tangem_24),
|
||||
colors = LightBgScanCardButtonColors,
|
||||
showProgress = showProgress,
|
||||
enabled = true,
|
||||
onClick = onClick,
|
||||
showProgress = showProgress,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OrderCardButton(onClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
TangemButton(
|
||||
StoriesButton(
|
||||
modifier = modifier,
|
||||
text = stringResource(id = R.string.home_button_order),
|
||||
icon = TangemButtonIconPosition.None,
|
||||
colors = LightBgOrderCardButtonColors,
|
||||
showProgress = false,
|
||||
enabled = true,
|
||||
useDarkerColors = true,
|
||||
onClick = onClick,
|
||||
)
|
||||
}
|
||||
|
||||
private val LightBgScanCardButtonColors: ButtonColors = TangemButtonColors(
|
||||
backgroundColor = TangemColorPalette.Light4,
|
||||
contentColor = TangemColorPalette.Dark6,
|
||||
disabledBackgroundColor = TangemColorPalette.Dark5,
|
||||
disabledContentColor = TangemColorPalette.Dark6,
|
||||
)
|
||||
|
||||
private val LightBgOrderCardButtonColors: ButtonColors = TangemButtonColors(
|
||||
backgroundColor = TangemColorPalette.Dark6,
|
||||
contentColor = TangemColorPalette.White,
|
||||
disabledBackgroundColor = TangemColorPalette.Dark6,
|
||||
disabledContentColor = TangemColorPalette.White,
|
||||
)
|
||||
|
||||
// region Preview
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
|
|
@ -95,10 +72,10 @@ private fun HomeButtonsPreview(@PreviewParameter(HomeButtonsParameterProvider::c
|
|||
modifier = Modifier.background(Color.Black),
|
||||
) {
|
||||
HomeButtons(
|
||||
modifier = Modifier.padding(all = TangemTheme.dimens.spacing16),
|
||||
btnScanStateInProgress = state.btnScanStateInProgress,
|
||||
onScanButtonClick = {},
|
||||
onShopButtonClick = {},
|
||||
modifier = Modifier.padding(all = TangemTheme.dimens.spacing16),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +1,42 @@
|
|||
package com.tangem.tap.features.home.compose.views
|
||||
|
||||
import androidx.compose.material.ButtonColors
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButton
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonColors
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@Composable
|
||||
internal fun SearchCurrenciesButton(onClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
TangemButton(
|
||||
StoriesButton(
|
||||
modifier = modifier,
|
||||
text = stringResource(id = R.string.common_search_tokens),
|
||||
icon = TangemButtonIconPosition.Start(R.drawable.ic_search_24),
|
||||
onClick = onClick,
|
||||
colors = SearchCurrenciesButtonColors,
|
||||
showProgress = false,
|
||||
enabled = true,
|
||||
useDarkerColors = true,
|
||||
onClick = onClick,
|
||||
)
|
||||
}
|
||||
|
||||
private val SearchCurrenciesButtonColors: ButtonColors = TangemButtonColors(
|
||||
backgroundColor = TangemColorPalette.Dark5,
|
||||
contentColor = TangemColorPalette.White,
|
||||
disabledBackgroundColor = TangemColorPalette.Dark5,
|
||||
disabledContentColor = TangemColorPalette.White,
|
||||
)
|
||||
// region Preview
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun SearchCurrenciesButtonPreview() {
|
||||
TangemTheme {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(color = Color.Black)
|
||||
.padding(all = TangemTheme.dimens.spacing16),
|
||||
) {
|
||||
SearchCurrenciesButton(modifier = Modifier.fillMaxWidth(), onClick = {})
|
||||
}
|
||||
}
|
||||
}
|
||||
// endregion Preview
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package com.tangem.tap.features.home.compose.views
|
||||
|
||||
import androidx.compose.material.ButtonColors
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButton
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonColors
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
@Composable
|
||||
internal fun StoriesButton(
|
||||
text: String,
|
||||
useDarkerColors: Boolean,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
icon: TangemButtonIconPosition = TangemButtonIconPosition.None,
|
||||
showProgress: Boolean = false,
|
||||
) {
|
||||
TangemButton(
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
icon = icon,
|
||||
colors = if (useDarkerColors) DarkerButtonColors else LighterButtonColors,
|
||||
showProgress = showProgress,
|
||||
enabled = true,
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
iconPadding = when (icon) {
|
||||
is TangemButtonIconPosition.Start -> TangemTheme.dimens.spacing4
|
||||
is TangemButtonIconPosition.End,
|
||||
is TangemButtonIconPosition.None,
|
||||
-> TangemTheme.dimens.spacing8
|
||||
},
|
||||
onClick = onClick,
|
||||
)
|
||||
}
|
||||
|
||||
private val LighterButtonColors: ButtonColors = TangemButtonColors(
|
||||
backgroundColor = TangemColorPalette.Light4,
|
||||
contentColor = TangemColorPalette.Dark6,
|
||||
disabledBackgroundColor = TangemColorPalette.Dark5,
|
||||
disabledContentColor = TangemColorPalette.Dark6,
|
||||
)
|
||||
|
||||
private val DarkerButtonColors: ButtonColors = TangemButtonColors(
|
||||
backgroundColor = TangemColorPalette.Dark4,
|
||||
contentColor = TangemColorPalette.White,
|
||||
disabledBackgroundColor = TangemColorPalette.Dark4,
|
||||
disabledContentColor = TangemColorPalette.White,
|
||||
)
|
||||
|
|
@ -13,10 +13,10 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.SpacerW4
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
|
|
@ -30,7 +30,7 @@ fun StoriesProgressBar(
|
|||
stepDuration: Int = 8_000,
|
||||
onStepFinish: () -> Unit = {},
|
||||
) {
|
||||
val progress = remember(currentStep) { Animatable(0f) }
|
||||
val progress = remember(currentStep) { Animatable(initialValue = 0f) }
|
||||
|
||||
val context = LocalContext.current
|
||||
val animatorSpeed = Settings.Global.getFloat(
|
||||
|
|
@ -75,20 +75,21 @@ fun StoriesProgressBar(
|
|||
.height(TangemTheme.dimens.size2)
|
||||
.weight(1f)
|
||||
.clip(RoundedCornerShape(TangemTheme.dimens.radius2))
|
||||
.background(Color.White.copy(alpha = 0.4f)),
|
||||
.background(TangemColorPalette.White.copy(alpha = .2f)),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(TangemTheme.dimens.radius2))
|
||||
.background(Color.White)
|
||||
.fillMaxHeight().let {
|
||||
.background(TangemColorPalette.White)
|
||||
.fillMaxHeight()
|
||||
.let {
|
||||
when (index) {
|
||||
currentStep -> it.fillMaxWidth(progress.value)
|
||||
in 0..currentStep -> it.fillMaxWidth(1f)
|
||||
in 0..currentStep -> it.fillMaxWidth(fraction = 1f)
|
||||
else -> it
|
||||
}
|
||||
},
|
||||
) {}
|
||||
)
|
||||
}
|
||||
if (index != steps) {
|
||||
SpacerW4()
|
||||
|
|
@ -100,5 +101,12 @@ fun StoriesProgressBar(
|
|||
@Preview
|
||||
@Composable
|
||||
private fun StoriesProgressBarPreview() {
|
||||
StoriesProgressBar(steps = 3, currentStep = 2, paused = false) { }
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.background(TangemColorPalette.Black)
|
||||
.padding(vertical = TangemTheme.dimens.spacing16),
|
||||
) {
|
||||
StoriesProgressBar(steps = 5, currentStep = 3, paused = false)
|
||||
}
|
||||
}
|
||||
|
|
@ -83,7 +83,9 @@ internal fun BriefNetworkItem(model: NetworkItemState, modifier: Modifier = Modi
|
|||
Icon(
|
||||
painter = painterResource(id = model.iconResId.value),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(size = TangemTheme.dimens.size20),
|
||||
modifier = Modifier
|
||||
.size(size = TangemTheme.dimens.size20)
|
||||
.clip(CircleShape),
|
||||
tint = if (isAdded) Color.Unspecified else TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -80,6 +80,13 @@ object TokenListPreviewData {
|
|||
iconResId = mutableStateOf(R.drawable.ic_bsc_16),
|
||||
isMainNetwork = false,
|
||||
),
|
||||
// check icon clipping
|
||||
NetworkItemState.ReadContent(
|
||||
name = "SHIBARIUM",
|
||||
protocolName = "BEP20",
|
||||
iconResId = mutableStateOf(R.drawable.ic_shibarium_22),
|
||||
isMainNetwork = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -3,14 +3,20 @@ package com.tangem.tap.features.tokens.impl.presentation.viewmodels
|
|||
import arrow.core.Either
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.data.tokens.utils.CryptoCurrencyFactory
|
||||
import com.tangem.domain.card.DerivePublicKeysUseCase
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
|
||||
import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase
|
||||
import com.tangem.domain.tokens.TokenWithBlockchain
|
||||
import com.tangem.domain.tokens.TokensAction
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.store
|
||||
import timber.log.Timber
|
||||
import kotlin.properties.Delegates
|
||||
|
|
@ -24,6 +30,8 @@ import kotlin.properties.Delegates
|
|||
internal class TokensListMigration(
|
||||
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||
private val getCurrenciesUseCase: GetCryptoCurrenciesUseCase,
|
||||
private val derivePublicKeysUseCase: DerivePublicKeysUseCase,
|
||||
private val addCryptoCurrenciesUseCase: AddCryptoCurrenciesUseCase,
|
||||
) {
|
||||
|
||||
private var currentNewCoins: List<CryptoCurrency.Coin> by Delegates.notNull()
|
||||
|
|
@ -76,31 +84,72 @@ internal class TokensListMigration(
|
|||
}
|
||||
}
|
||||
|
||||
fun onSaveButtonClick(
|
||||
suspend fun onSaveButtonClick(
|
||||
changedTokensList: MutableList<TokenWithBlockchain>,
|
||||
changedBlockchainList: List<Blockchain>,
|
||||
) {
|
||||
store.dispatch(
|
||||
action = TokensAction.SaveChanges(
|
||||
currentTokens = currentNewTokens,
|
||||
currentCoins = currentNewCoins,
|
||||
changedTokens = changedTokensList.mapNotNull {
|
||||
cryptoCurrencyFactory.createToken(
|
||||
sdkToken = it.token,
|
||||
blockchain = it.blockchain,
|
||||
extraDerivationPath = null,
|
||||
derivationStyleProvider = currentUserWallet.scanResponse.derivationStyleProvider,
|
||||
)
|
||||
},
|
||||
changedCoins = changedBlockchainList.mapNotNull {
|
||||
cryptoCurrencyFactory.createCoin(
|
||||
blockchain = it,
|
||||
extraDerivationPath = null,
|
||||
derivationStyleProvider = currentUserWallet.scanResponse.derivationStyleProvider,
|
||||
)
|
||||
},
|
||||
userWallet = currentUserWallet,
|
||||
),
|
||||
val changedTokens = changedTokensList.mapNotNull {
|
||||
cryptoCurrencyFactory.createToken(
|
||||
sdkToken = it.token,
|
||||
blockchain = it.blockchain,
|
||||
extraDerivationPath = null,
|
||||
derivationStyleProvider = currentUserWallet.scanResponse.derivationStyleProvider,
|
||||
)
|
||||
}
|
||||
|
||||
val changedCoins = changedBlockchainList.mapNotNull {
|
||||
cryptoCurrencyFactory.createCoin(
|
||||
blockchain = it,
|
||||
extraDerivationPath = null,
|
||||
derivationStyleProvider = currentUserWallet.scanResponse.derivationStyleProvider,
|
||||
)
|
||||
}
|
||||
|
||||
val blockchainsToAdd = changedCoins.filterNot(currentNewCoins::contains)
|
||||
val blockchainsToRemove = currentNewCoins.filterNot(changedCoins::contains)
|
||||
|
||||
val tokensToAdd = changedTokens.filterNot(currentNewTokens::contains)
|
||||
val tokensToRemove = currentNewTokens.filterNot { token -> changedTokens.any { it == token } }
|
||||
|
||||
removeCurrenciesIfNeeded(
|
||||
userWalletId = currentUserWallet.walletId,
|
||||
currencies = blockchainsToRemove + tokensToRemove,
|
||||
)
|
||||
|
||||
val isNothingToDoWithTokens = tokensToAdd.isEmpty() && tokensToRemove.isEmpty()
|
||||
val isNothingToDoWithBlockchain = blockchainsToAdd.isEmpty() && blockchainsToRemove.isEmpty()
|
||||
if (isNothingToDoWithTokens && isNothingToDoWithBlockchain) {
|
||||
store.dispatchDebugErrorNotification(message = "Nothing to save")
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo())
|
||||
return
|
||||
}
|
||||
|
||||
val currencyList = blockchainsToAdd + tokensToAdd
|
||||
|
||||
derivePublicKeysUseCase(userWalletId = currentUserWallet.walletId, currencies = currencyList)
|
||||
.onRight {
|
||||
addCryptoCurrenciesUseCase(userWalletId = currentUserWallet.walletId, currencies = currencyList)
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo())
|
||||
}
|
||||
.onLeft { Timber.e("Failed to derive public keys: $it") }
|
||||
}
|
||||
|
||||
private suspend fun removeCurrenciesIfNeeded(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
|
||||
if (currencies.isEmpty()) return
|
||||
val currenciesRepository = store.state.daggerGraphState.get(DaggerGraphState::currenciesRepository)
|
||||
val walletManagersFacade = store.state.daggerGraphState.get(DaggerGraphState::walletManagersFacade)
|
||||
|
||||
currenciesRepository.removeCurrencies(userWalletId = userWalletId, currencies = currencies)
|
||||
|
||||
walletManagersFacade.remove(
|
||||
userWalletId = userWalletId,
|
||||
networks = currencies
|
||||
.filterIsInstance<CryptoCurrency.Coin>()
|
||||
.mapTo(hashSetOf(), CryptoCurrency::network),
|
||||
)
|
||||
walletManagersFacade.removeTokens(
|
||||
userWalletId = userWalletId,
|
||||
tokens = currencies.filterIsInstance<CryptoCurrency.Token>().toSet(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -13,12 +13,14 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.ui.extensions.getActiveIconRes
|
||||
import com.tangem.core.ui.extensions.getGreyedOutIconRes
|
||||
import com.tangem.domain.card.DerivePublicKeysUseCase
|
||||
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
|
||||
import com.tangem.domain.common.extensions.canHandleBlockchain
|
||||
import com.tangem.domain.common.extensions.canHandleToken
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.domain.common.extensions.supportedTokens
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
|
||||
import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase
|
||||
import com.tangem.domain.tokens.TokenWithBlockchain
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
|
|
@ -67,6 +69,8 @@ internal class TokensListViewModel @Inject constructor(
|
|||
private val router: TokensListRouter,
|
||||
private val dispatchers: AppCoroutineDispatcherProvider,
|
||||
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||
derivePublicKeysUseCase: DerivePublicKeysUseCase,
|
||||
addCryptoCurrenciesUseCase: AddCryptoCurrenciesUseCase,
|
||||
analyticsEventHandler: AnalyticsEventHandler,
|
||||
getCurrenciesUseCase: GetCryptoCurrenciesUseCase,
|
||||
) : ViewModel(), DefaultLifecycleObserver {
|
||||
|
|
@ -88,6 +92,8 @@ internal class TokensListViewModel @Inject constructor(
|
|||
private val tokensListMigration = TokensListMigration(
|
||||
getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase,
|
||||
getCurrenciesUseCase = getCurrenciesUseCase,
|
||||
derivePublicKeysUseCase = derivePublicKeysUseCase,
|
||||
addCryptoCurrenciesUseCase = addCryptoCurrenciesUseCase,
|
||||
)
|
||||
|
||||
init {
|
||||
|
|
@ -303,10 +309,13 @@ internal class TokensListViewModel @Inject constructor(
|
|||
|
||||
fun onSaveButtonClick() {
|
||||
analyticsSender.sendWhenSaveButtonClicked()
|
||||
tokensListMigration.onSaveButtonClick(
|
||||
changedTokensList = changedTokensList,
|
||||
changedBlockchainList = changedBlockchainList,
|
||||
)
|
||||
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
tokensListMigration.onSaveButtonClick(
|
||||
changedTokensList = changedTokensList,
|
||||
changedBlockchainList = changedBlockchainList,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSearchValueChange(newValue: String) {
|
||||
|
|
|
|||
|
|
@ -1,250 +0,0 @@
|
|||
package com.tangem.tap.features.tokens.legacy.redux
|
||||
|
||||
import com.tangem.blockchain.blockchains.cardano.CardanoUtils
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.common.extensions.toMapKey
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.domain.common.configs.CardConfig
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.common.util.supportsHdWallet
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
|
||||
import com.tangem.domain.tokens.TokensAction
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.card.DefaultDerivePublicKeysUseCase
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import com.tangem.tap.userWalletsListManager
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Middleware
|
||||
import timber.log.Timber
|
||||
|
||||
@Suppress("LargeClass")
|
||||
object TokensMiddleware {
|
||||
|
||||
// TODO: Move to DI
|
||||
private val addCryptoCurrenciesUseCase by lazy(LazyThreadSafetyMode.NONE) {
|
||||
val currenciesRepository = store.state.daggerGraphState.get(DaggerGraphState::currenciesRepository)
|
||||
val networksRepository = store.state.daggerGraphState.get(DaggerGraphState::networksRepository)
|
||||
|
||||
AddCryptoCurrenciesUseCase(currenciesRepository, networksRepository)
|
||||
}
|
||||
|
||||
val tokensMiddleware: Middleware<AppState> = { _, _ ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
when (action) {
|
||||
is TokensAction.SaveChanges -> handleSaveChanges(action)
|
||||
}
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSaveChanges(action: TokensAction.SaveChanges) {
|
||||
scope.launch {
|
||||
val scanResponse = action.userWallet.scanResponse
|
||||
|
||||
val currentTokens = action.currentTokens
|
||||
val currentBlockchains = action.currentCoins
|
||||
|
||||
val blockchainsToAdd = action.changedCoins.filterNot(currentBlockchains::contains)
|
||||
val blockchainsToRemove = currentBlockchains.filterNot(action.changedCoins::contains)
|
||||
|
||||
val tokensToAdd = action.changedTokens.filterNot(currentTokens::contains)
|
||||
val tokensToRemove = currentTokens.filterNot { token -> action.changedTokens.any { it == token } }
|
||||
|
||||
removeCurrenciesIfNeeded(
|
||||
userWalletId = action.userWallet.walletId,
|
||||
currencies = blockchainsToRemove + tokensToRemove,
|
||||
)
|
||||
|
||||
val isNothingToDoWithTokens = tokensToAdd.isEmpty() && tokensToRemove.isEmpty()
|
||||
val isNothingToDoWithBlockchain = blockchainsToAdd.isEmpty() && blockchainsToRemove.isEmpty()
|
||||
if (isNothingToDoWithTokens && isNothingToDoWithBlockchain) {
|
||||
store.dispatchDebugErrorNotification(message = "Nothing to save")
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo())
|
||||
return@launch
|
||||
}
|
||||
|
||||
val currencyList = blockchainsToAdd + tokensToAdd
|
||||
|
||||
val featureToggles = store.state.daggerGraphState.get(DaggerGraphState::testerFeatureToggles)
|
||||
if (featureToggles.isDerivePublicKeysRefactoringEnabled) {
|
||||
val derivePublicKeys = DefaultDerivePublicKeysUseCase(
|
||||
tangemSdkManager = tangemSdkManager,
|
||||
derivationsRepository = store.state.daggerGraphState.get(DaggerGraphState::derivationsRepository),
|
||||
)
|
||||
|
||||
derivePublicKeys(userWalletId = action.userWallet.walletId, currencies = currencyList)
|
||||
.onRight {
|
||||
addCryptoCurrenciesUseCase(
|
||||
userWalletId = action.userWallet.walletId,
|
||||
currencies = currencyList,
|
||||
)
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo())
|
||||
}
|
||||
.onLeft { Timber.e("Failed to derive public keys: $it") }
|
||||
} else {
|
||||
// TODO: delete [REDACTED_JIRA]
|
||||
if (scanResponse.supportsHdWallet()) {
|
||||
deriveMissingCoins(scanResponse = scanResponse, currencyList = currencyList) {
|
||||
submitAdd(
|
||||
userWallet = action.userWallet,
|
||||
updatedScanResponse = it,
|
||||
currencyList = currencyList,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
submitAdd(action.userWallet, scanResponse, currencyList)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated(message = "Use DerivePublicKeysUseCase instead")
|
||||
private fun deriveMissingCoins(
|
||||
scanResponse: ScanResponse,
|
||||
currencyList: List<CryptoCurrency>,
|
||||
onSuccess: (ScanResponse) -> Unit,
|
||||
) {
|
||||
val config = CardConfig.createConfig(scanResponse.card)
|
||||
val derivationDataList = currencyList.mapNotNull { currency ->
|
||||
val curve = config.primaryCurve(blockchain = Blockchain.fromId(currency.network.id.value))
|
||||
curve?.let { getDerivations(curve, scanResponse, currency) }
|
||||
}
|
||||
val derivations = buildMap<ByteArrayKey, MutableList<DerivationPath>> {
|
||||
derivationDataList.forEach {
|
||||
val current = this[it.derivations.first]
|
||||
if (current != null) {
|
||||
current.addAll(it.derivations.second)
|
||||
current.distinct()
|
||||
} else {
|
||||
this[it.derivations.first] = it.derivations.second.toMutableList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (derivations.isEmpty()) {
|
||||
onSuccess(scanResponse)
|
||||
return
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
val result = tangemSdkManager.derivePublicKeys(
|
||||
cardId = null,
|
||||
derivations = derivations,
|
||||
)
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val newDerivedKeys = result.data.entries
|
||||
val oldDerivedKeys = scanResponse.derivedKeys
|
||||
|
||||
val walletKeys = (newDerivedKeys.keys + oldDerivedKeys.keys).toSet()
|
||||
|
||||
val updatedDerivedKeys = walletKeys.associateWith { walletKey ->
|
||||
val oldDerivations = ExtendedPublicKeysMap(oldDerivedKeys[walletKey] ?: emptyMap())
|
||||
val newDerivations = newDerivedKeys[walletKey] ?: ExtendedPublicKeysMap(emptyMap())
|
||||
ExtendedPublicKeysMap(oldDerivations + newDerivations)
|
||||
}
|
||||
val updatedScanResponse = scanResponse.copy(derivedKeys = updatedDerivedKeys)
|
||||
|
||||
store.dispatchOnMain(GlobalAction.SaveScanResponse(updatedScanResponse))
|
||||
|
||||
onSuccess(updatedScanResponse)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
store.dispatchDebugErrorNotification(TapError.CustomError("Error adding tokens"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDerivations(
|
||||
curve: EllipticCurve,
|
||||
scanResponse: ScanResponse,
|
||||
currency: CryptoCurrency,
|
||||
): DerivationData? {
|
||||
val wallet = scanResponse.card.wallets.firstOrNull { it.curve == curve } ?: return null
|
||||
|
||||
val blockchain = Blockchain.fromId(currency.network.id.value)
|
||||
val supportedCurves = blockchain.getSupportedCurves()
|
||||
val path = blockchain.derivationPath(scanResponse.derivationStyleProvider.getDerivationStyle())
|
||||
.takeIf { supportedCurves.contains(curve) }
|
||||
|
||||
val customPath = currency.network.derivationPath.value?.let {
|
||||
DerivationPath(it)
|
||||
}.takeIf { supportedCurves.contains(curve) }
|
||||
|
||||
val bothCandidates = listOfNotNull(path, customPath).distinct().toMutableList()
|
||||
if (bothCandidates.isEmpty()) return null
|
||||
|
||||
if (currency is CryptoCurrency.Coin && blockchain == Blockchain.Cardano) {
|
||||
currency.network.derivationPath.value?.let {
|
||||
bothCandidates.add(CardanoUtils.extendedDerivationPath(DerivationPath(it)))
|
||||
}
|
||||
}
|
||||
|
||||
val mapKeyOfWalletPublicKey = wallet.publicKey.toMapKey()
|
||||
val alreadyDerivedKeys: ExtendedPublicKeysMap =
|
||||
scanResponse.derivedKeys[mapKeyOfWalletPublicKey] ?: ExtendedPublicKeysMap(emptyMap())
|
||||
val alreadyDerivedPaths = alreadyDerivedKeys.keys.toList()
|
||||
|
||||
val toDerive = bothCandidates.filterNot { alreadyDerivedPaths.contains(it) }
|
||||
if (toDerive.isEmpty()) return null
|
||||
|
||||
return DerivationData(derivations = mapKeyOfWalletPublicKey to toDerive)
|
||||
}
|
||||
|
||||
class DerivationData(val derivations: Pair<ByteArrayKey, List<DerivationPath>>)
|
||||
|
||||
private fun submitAdd(
|
||||
userWallet: UserWallet,
|
||||
updatedScanResponse: ScanResponse,
|
||||
currencyList: List<CryptoCurrency>,
|
||||
) {
|
||||
scope.launch {
|
||||
userWalletsListManager.update(
|
||||
userWalletId = userWallet.walletId,
|
||||
update = { it.copy(scanResponse = updatedScanResponse) },
|
||||
).doOnSuccess {
|
||||
addCryptoCurrenciesUseCase(userWallet.walletId, currencyList)
|
||||
}
|
||||
}
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo())
|
||||
}
|
||||
|
||||
private suspend fun removeCurrenciesIfNeeded(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
|
||||
if (currencies.isEmpty()) return
|
||||
val currenciesRepository = store.state.daggerGraphState.get(DaggerGraphState::currenciesRepository)
|
||||
val walletManagersFacade = store.state.daggerGraphState.get(DaggerGraphState::walletManagersFacade)
|
||||
|
||||
currenciesRepository.removeCurrencies(userWalletId = userWalletId, currencies = currencies)
|
||||
|
||||
walletManagersFacade.remove(
|
||||
userWalletId = userWalletId,
|
||||
networks = currencies
|
||||
.filterIsInstance<CryptoCurrency.Coin>()
|
||||
.mapTo(hashSetOf(), CryptoCurrency::network),
|
||||
)
|
||||
walletManagersFacade.removeTokens(
|
||||
userWalletId = userWalletId,
|
||||
tokens = currencies.filterIsInstance<CryptoCurrency.Token>().toSet(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -46,6 +46,11 @@ object TradeCryptoMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private val isSendRedesignedEnabled: Boolean
|
||||
get() = store.state.daggerGraphState.get(
|
||||
getDependency = DaggerGraphState::sendFeatureToggles,
|
||||
).isRedesignedSendEnabled
|
||||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
private fun handle(state: () -> AppState?, action: TradeCryptoAction) {
|
||||
if (DemoHelper.tryHandle(state, action)) return
|
||||
|
|
@ -57,8 +62,20 @@ object TradeCryptoMiddleware {
|
|||
is TradeCryptoAction.Swap -> openSwap(
|
||||
currency = action.cryptoCurrency,
|
||||
)
|
||||
is TradeCryptoAction.SendToken -> handleSendToken(action = action)
|
||||
is TradeCryptoAction.SendCoin -> handleSendCoin(action = action)
|
||||
is TradeCryptoAction.SendToken -> {
|
||||
if (isSendRedesignedEnabled) {
|
||||
handleNewSendToken(action = action)
|
||||
} else {
|
||||
handleSendToken(action = action)
|
||||
}
|
||||
}
|
||||
is TradeCryptoAction.SendCoin -> {
|
||||
if (isSendRedesignedEnabled) {
|
||||
handleNewSendCoin(action = action)
|
||||
} else {
|
||||
handleSendCoin(action = action)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -280,4 +297,35 @@ object TradeCryptoMiddleware {
|
|||
store.dispatchOnMain(NavigationAction.NavigateTo(screen = AppScreen.Send, bundle = bundle))
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleNewSendToken(action: TradeCryptoAction.SendToken) {
|
||||
handleNewSend(
|
||||
userWalletId = action.userWallet.walletId.stringValue,
|
||||
txInfo = action.transactionInfo,
|
||||
currency = action.tokenCurrency,
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleNewSendCoin(action: TradeCryptoAction.SendCoin) {
|
||||
handleNewSend(
|
||||
userWalletId = action.userWallet.walletId.stringValue,
|
||||
txInfo = action.transactionInfo,
|
||||
currency = action.coinStatus.currency,
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleNewSend(
|
||||
userWalletId: String,
|
||||
txInfo: TradeCryptoAction.TransactionInfo?,
|
||||
currency: CryptoCurrency,
|
||||
) {
|
||||
val bundle = bundleOf(
|
||||
SendRouter.CRYPTO_CURRENCY_KEY to currency,
|
||||
SendRouter.USER_WALLET_ID_KEY to userWalletId,
|
||||
SendRouter.TRANSACTION_ID_KEY to txInfo?.transactionId,
|
||||
SendRouter.DESTINATION_ADDRESS_KEY to txInfo?.destinationAddress,
|
||||
SendRouter.AMOUNT_KEY to txInfo?.amount,
|
||||
)
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(screen = AppScreen.Send, bundle = bundle))
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue