Updated on 2026-08-14
This commit is contained in:
commit
0becaafc0a
74 changed files with 641 additions and 249 deletions
|
|
@ -3,6 +3,7 @@ package com.tangem.features.markets.details.impl.model
|
|||
import androidx.compose.runtime.Stable
|
||||
import arrow.core.Either
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.common.ui.charts.state.MarketChartData
|
||||
import com.tangem.common.ui.charts.state.MarketChartDataProducer
|
||||
import com.tangem.common.ui.charts.state.sorted
|
||||
|
|
@ -64,6 +65,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
|||
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
|
||||
private val urlOpener: UrlOpener,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val excludedBlockchains: ExcludedBlockchains,
|
||||
) : Model() {
|
||||
|
||||
private var quotesJob = JobHolder()
|
||||
|
|
@ -400,7 +402,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
|||
}
|
||||
|
||||
val networks = newInfo.networks?.filter {
|
||||
BlockchainUtils.isSupportedNetworkId(it.networkId)
|
||||
BlockchainUtils.isSupportedNetworkId(it.networkId, excludedBlockchains)
|
||||
}
|
||||
|
||||
networksState.value = if (networks.isNullOrEmpty()) {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import com.tangem.features.onramp.swap.availablepairs.AvailableSwapPairsComponen
|
|||
import com.tangem.features.onramp.swap.availablepairs.entity.transformers.SetLoadingTokenItemsTransformer
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUM
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUMController
|
||||
import com.tangem.features.onramp.tokenlist.entity.transformer.SetNothingToFoundStateTransformer
|
||||
import com.tangem.features.onramp.tokenlist.entity.transformer.UpdateTokenItemsTransformer
|
||||
import com.tangem.features.onramp.utils.InputManager
|
||||
import com.tangem.features.onramp.utils.UpdateSearchBarActiveStateTransformer
|
||||
|
|
@ -80,24 +81,35 @@ internal class AvailableSwapPairsModel @Inject constructor(
|
|||
} else {
|
||||
val (appCurrency, isBalanceHidden) = appCurrencyAndBalanceHiding
|
||||
|
||||
val filterTokenList = currencies
|
||||
val filterByQueryTokenList = currencies
|
||||
.filter { it.currency != selectedStatus?.currency }
|
||||
.filterByQuery(query = query)
|
||||
.filterByAvailability(availablePairs = availablePairs)
|
||||
|
||||
UpdateTokenItemsTransformer(
|
||||
appCurrency = appCurrency,
|
||||
onItemClick = params.onTokenClick,
|
||||
statuses = filterTokenList,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
hasSearchBar = currencies.isNotEmpty(),
|
||||
unavailableTokensHeaderReference = resourceReference(
|
||||
id = R.string.tokens_list_unavailable_to_swap_header,
|
||||
wrappedList(selectedStatus?.currency?.name?.capitalize() ?: ""),
|
||||
),
|
||||
onQueryChange = ::onSearchQueryChange,
|
||||
onActiveChange = ::onSearchBarActiveChange,
|
||||
)
|
||||
if (query.isNotEmpty() && filterByQueryTokenList.isEmpty()) {
|
||||
SetNothingToFoundStateTransformer(
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
hasSearchBar = currencies.isNotEmpty(),
|
||||
emptySearchMessageReference = resourceReference(
|
||||
id = R.string.action_buttons_swap_empty_search_message,
|
||||
),
|
||||
onQueryChange = ::onSearchQueryChange,
|
||||
onActiveChange = ::onSearchBarActiveChange,
|
||||
)
|
||||
} else {
|
||||
UpdateTokenItemsTransformer(
|
||||
appCurrency = appCurrency,
|
||||
onItemClick = params.onTokenClick,
|
||||
statuses = filterByQueryTokenList.filterByAvailability(availablePairs = availablePairs),
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
hasSearchBar = currencies.isNotEmpty(),
|
||||
unavailableTokensHeaderReference = resourceReference(
|
||||
id = R.string.tokens_list_unavailable_to_swap_header,
|
||||
wrappedList(selectedStatus?.currency?.name?.capitalize() ?: ""),
|
||||
),
|
||||
onQueryChange = ::onSearchQueryChange,
|
||||
onActiveChange = ::onSearchBarActiveChange,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onEach(tokenListUMController::update)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
package com.tangem.features.onramp.tokenlist.entity.transformer
|
||||
|
||||
import com.tangem.core.ui.components.fields.entity.SearchBarUM
|
||||
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.features.onramp.impl.R
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUM
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUMTransformer
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
internal class SetNothingToFoundStateTransformer(
|
||||
private val isBalanceHidden: Boolean,
|
||||
private val hasSearchBar: Boolean,
|
||||
private val emptySearchMessageReference: TextReference,
|
||||
private val onQueryChange: (String) -> Unit,
|
||||
private val onActiveChange: (Boolean) -> Unit,
|
||||
) : TokenListUMTransformer {
|
||||
|
||||
override fun transform(prevState: TokenListUM): TokenListUM {
|
||||
val searchBarItem = if (hasSearchBar) {
|
||||
prevState.getSearchBar() ?: createSearchBarItem()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
return prevState.copy(
|
||||
availableItems = buildList {
|
||||
if (searchBarItem != null) {
|
||||
add(searchBarItem)
|
||||
}
|
||||
|
||||
createGroupTitle(
|
||||
textReference = resourceReference(id = R.string.exchange_tokens_available_tokens_header),
|
||||
)
|
||||
.let(::add)
|
||||
|
||||
TokensListItemUM.Text(
|
||||
id = emptySearchMessageReference.hashCode(),
|
||||
text = emptySearchMessageReference,
|
||||
).let(::add)
|
||||
}
|
||||
.toImmutableList(),
|
||||
unavailableItems = persistentListOf(),
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createSearchBarItem(): TokensListItemUM.SearchBar {
|
||||
return TokensListItemUM.SearchBar(
|
||||
searchBarUM = SearchBarUM(
|
||||
placeholderText = resourceReference(id = R.string.common_search),
|
||||
query = "",
|
||||
onQueryChange = onQueryChange,
|
||||
isActive = false,
|
||||
onActiveChange = onActiveChange,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createGroupTitle(textReference: TextReference): TokensListItemUM.GroupTitle {
|
||||
return TokensListItemUM.GroupTitle(id = textReference.hashCode(), text = textReference)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@ import arrow.core.getOrElse
|
|||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
|
|
@ -19,6 +18,7 @@ import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent
|
|||
import com.tangem.features.onramp.tokenlist.entity.OnrampOperation
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUM
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUMController
|
||||
import com.tangem.features.onramp.tokenlist.entity.transformer.SetNothingToFoundStateTransformer
|
||||
import com.tangem.features.onramp.tokenlist.entity.transformer.UpdateTokenItemsTransformer
|
||||
import com.tangem.features.onramp.utils.InputManager
|
||||
import com.tangem.features.onramp.utils.UpdateSearchBarActiveStateTransformer
|
||||
|
|
@ -65,30 +65,38 @@ internal class OnrampTokenListModel @Inject constructor(
|
|||
)
|
||||
.flattenCurrencies()
|
||||
|
||||
val filterTokenList = currencies
|
||||
val filterByQueryTokenList = currencies
|
||||
.filterByQuery(query = query)
|
||||
.filterByAvailability()
|
||||
|
||||
UpdateTokenItemsTransformer(
|
||||
appCurrency = appCurrency,
|
||||
onItemClick = params.onTokenClick,
|
||||
statuses = filterTokenList,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
hasSearchBar = params.hasSearchBar && currencies.isNotEmpty(),
|
||||
unavailableTokensHeaderReference = when (params.filterOperation) {
|
||||
OnrampOperation.BUY -> resourceReference(id = R.string.tokens_list_unavailable_to_purchase_header)
|
||||
OnrampOperation.SELL -> resourceReference(id = R.string.tokens_list_unavailable_to_sell_header)
|
||||
OnrampOperation.SWAP -> {
|
||||
// TODO: [REDACTED_JIRA]
|
||||
resourceReference(
|
||||
id = R.string.tokens_list_unavailable_to_swap_header,
|
||||
wrappedList(""),
|
||||
)
|
||||
if (query.isNotEmpty() && filterByQueryTokenList.isEmpty()) {
|
||||
SetNothingToFoundStateTransformer(
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
hasSearchBar = params.hasSearchBar && currencies.isNotEmpty(),
|
||||
emptySearchMessageReference = when (params.filterOperation) {
|
||||
OnrampOperation.BUY -> R.string.action_buttons_buy_empty_search_message
|
||||
OnrampOperation.SELL -> R.string.action_buttons_sell_empty_search_message
|
||||
OnrampOperation.SWAP -> R.string.action_buttons_swap_empty_search_message
|
||||
}
|
||||
},
|
||||
onQueryChange = ::onSearchQueryChange,
|
||||
onActiveChange = ::onSearchBarActiveChange,
|
||||
)
|
||||
.let(::resourceReference),
|
||||
onQueryChange = ::onSearchQueryChange,
|
||||
onActiveChange = ::onSearchBarActiveChange,
|
||||
)
|
||||
} else {
|
||||
UpdateTokenItemsTransformer(
|
||||
appCurrency = appCurrency,
|
||||
onItemClick = params.onTokenClick,
|
||||
statuses = filterByQueryTokenList.filterByAvailability(),
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
hasSearchBar = params.hasSearchBar && currencies.isNotEmpty(),
|
||||
unavailableTokensHeaderReference = when (params.filterOperation) {
|
||||
OnrampOperation.BUY -> R.string.tokens_list_unavailable_to_purchase_header
|
||||
OnrampOperation.SELL -> R.string.tokens_list_unavailable_to_sell_header
|
||||
OnrampOperation.SWAP -> R.string.tokens_list_unavailable_to_swap_source_header
|
||||
}.let(::resourceReference),
|
||||
onQueryChange = ::onSearchQueryChange,
|
||||
onActiveChange = ::onSearchBarActiveChange,
|
||||
)
|
||||
}
|
||||
}
|
||||
.onEach(tokenListUMController::update)
|
||||
.flowOn(dispatchers.main)
|
||||
|
|
|
|||
|
|
@ -47,6 +47,9 @@ dependencies {
|
|||
implementation(projects.domain.qrScanning)
|
||||
implementation(projects.domain.qrScanning.models)
|
||||
|
||||
/** Data */
|
||||
implementation(projects.data.card)
|
||||
|
||||
/** Compose */
|
||||
implementation(deps.compose.foundation)
|
||||
implementation(deps.compose.ui)
|
||||
|
|
@ -57,4 +60,5 @@ dependencies {
|
|||
/** Other dependencies */
|
||||
implementation(deps.arrow.core)
|
||||
implementation(deps.timber)
|
||||
implementation(deps.tangem.card.core)
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import androidx.lifecycle.SavedStateHandle
|
|||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.data.card.sdk.CardSdkProvider
|
||||
import com.tangem.domain.qrscanning.models.SourceType
|
||||
import com.tangem.feature.qrscanning.navigation.QrScanningInnerRouter
|
||||
import com.tangem.feature.qrscanning.presentation.QrScanningState
|
||||
|
|
@ -20,6 +21,7 @@ import javax.inject.Inject
|
|||
internal class QrScanningViewModel @Inject constructor(
|
||||
private val stateHolder: QrScanningStateController,
|
||||
private val clickIntents: QrScanningClickIntentsImplementor,
|
||||
private val cardSdkProvider: CardSdkProvider,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel() {
|
||||
|
||||
|
|
@ -31,6 +33,12 @@ internal class QrScanningViewModel @Inject constructor(
|
|||
val uiState: StateFlow<QrScanningState> = stateHolder.uiState
|
||||
val launchGalleryEvent: SharedFlow<GalleryRequest> = clickIntents.launchGallery
|
||||
|
||||
init {
|
||||
// samsung for some reason disables reader mode, and then it works unstable
|
||||
// to prevent this disable ir manually before scan QR
|
||||
cardSdkProvider.sdk.forceDisableReaderMode()
|
||||
}
|
||||
|
||||
fun setRouter(router: QrScanningInnerRouter) {
|
||||
clickIntents.initialize(
|
||||
router = router,
|
||||
|
|
@ -49,4 +57,10 @@ internal class QrScanningViewModel @Inject constructor(
|
|||
fun onDismissBottomSheetState() {
|
||||
stateHolder.update(DismissBottomSheetTransformer())
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
// don't forget enable reader mode after scan complete
|
||||
cardSdkProvider.sdk.forceEnableReaderMode()
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.feature.referral.data
|
|||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.data.common.currency.CryptoCurrencyFactory
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
|
|
@ -18,14 +19,18 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
|||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class ReferralRepositoryImpl @Inject constructor(
|
||||
private val referralApi: TangemTechApi,
|
||||
private val referralConverter: ReferralConverter,
|
||||
private val coroutineDispatcher: CoroutineDispatcherProvider,
|
||||
private val demoModeDatasource: DemoModeDatasource,
|
||||
private val userWalletsStore: UserWalletsStore,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
) : ReferralRepository {
|
||||
|
||||
private val cryptoCurrencyFactory = CryptoCurrencyFactory(excludedBlockchains)
|
||||
|
||||
override val isDemoMode: Boolean
|
||||
get() = demoModeDatasource.isDemoModeActive
|
||||
|
||||
|
|
@ -65,8 +70,6 @@ internal class ReferralRepositoryImpl @Inject constructor(
|
|||
val blockchain = Blockchain.fromNetworkId(tokenData.networkId)
|
||||
?: error("Blockchain ${tokenData.networkId} not found")
|
||||
|
||||
val cryptoCurrencyFactory = CryptoCurrencyFactory()
|
||||
|
||||
val contractAddress = tokenData.contractAddress
|
||||
val decimalCount = tokenData.decimalCount
|
||||
return if (contractAddress != null && decimalCount != null) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.feature.referral.di
|
||||
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.demo.DemoModeDatasource
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
|
|
@ -25,6 +26,7 @@ class ReferralRepositoryModule {
|
|||
coroutineDispatcherProvider: CoroutineDispatcherProvider,
|
||||
demoModeDatasource: DemoModeDatasource,
|
||||
userWalletsStore: UserWalletsStore,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
): ReferralRepository {
|
||||
return ReferralRepositoryImpl(
|
||||
referralApi = tangemTechApi,
|
||||
|
|
@ -32,6 +34,7 @@ class ReferralRepositoryModule {
|
|||
coroutineDispatcher = coroutineDispatcherProvider,
|
||||
demoModeDatasource = demoModeDatasource,
|
||||
userWalletsStore = userWalletsStore,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -75,6 +75,7 @@ internal class StakingStateController @Inject constructor(
|
|||
walletName = "",
|
||||
cryptoCurrencyName = "",
|
||||
cryptoCurrencySymbol = "",
|
||||
cryptoCurrencyBlockchainId = "",
|
||||
currentStep = StakingStep.InitialInfo,
|
||||
initialInfoState = StakingStates.InitialInfoState.Empty(),
|
||||
amountState = AmountState.Empty(),
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
|
|||
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
||||
import com.tangem.domain.staking.analytics.StakingAnalyticsEvent
|
||||
import com.tangem.features.staking.impl.analytics.utils.StakingAnalyticSender
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isSolana
|
||||
|
||||
internal class StakingStateRouter(
|
||||
private val appRouter: AppRouter,
|
||||
|
|
@ -23,7 +24,13 @@ internal class StakingStateRouter(
|
|||
fun onNextClick() {
|
||||
when (stateController.value.currentStep) {
|
||||
StakingStep.InitialInfo -> when (stateController.value.actionType) {
|
||||
StakingActionCommonType.Enter, StakingActionCommonType.Exit -> showAmount()
|
||||
StakingActionCommonType.Enter -> showAmount()
|
||||
// TODO staking [REDACTED_TASK_KEY] support solana multisize hashes signing
|
||||
StakingActionCommonType.Exit -> if (isSolana(stateController.value.cryptoCurrencyBlockchainId)) {
|
||||
showConfirmation()
|
||||
} else {
|
||||
showAmount()
|
||||
}
|
||||
StakingActionCommonType.Pending.Other,
|
||||
StakingActionCommonType.Pending.Rewards,
|
||||
-> showConfirmation()
|
||||
|
|
@ -50,7 +57,9 @@ internal class StakingStateRouter(
|
|||
val isEnter = uiState.actionType == StakingActionCommonType.Enter
|
||||
val isExit = uiState.actionType == StakingActionCommonType.Exit
|
||||
|
||||
if (isEnter || isExit) {
|
||||
// TODO staking [REDACTED_TASK_KEY] support solana multisize hashes signing
|
||||
val isSolana = isSolana(uiState.cryptoCurrencyBlockchainId)
|
||||
if (isEnter || isExit && !isSolana) {
|
||||
showAmount()
|
||||
} else {
|
||||
showInitial()
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ internal data class StakingUiState(
|
|||
val walletName: String,
|
||||
val cryptoCurrencyName: String,
|
||||
val cryptoCurrencySymbol: String,
|
||||
val cryptoCurrencyBlockchainId: String,
|
||||
val currentStep: StakingStep,
|
||||
val initialInfoState: StakingStates.InitialInfoState,
|
||||
val amountState: AmountState,
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ internal class SetInitialDataStateTransformer(
|
|||
title = TextReference.EMPTY,
|
||||
cryptoCurrencyName = cryptoCurrency.name,
|
||||
cryptoCurrencySymbol = cryptoCurrency.symbol,
|
||||
cryptoCurrencyBlockchainId = cryptoCurrency.network.id.value,
|
||||
clickIntents = clickIntents,
|
||||
currentStep = StakingStep.InitialInfo,
|
||||
initialInfoState = createInitialInfoState(),
|
||||
|
|
|
|||
|
|
@ -40,9 +40,10 @@ internal fun StakingConfirmationContent(
|
|||
validatorState: StakingStates.ValidatorState,
|
||||
clickIntents: StakingClickIntents,
|
||||
type: StakingActionCommonType,
|
||||
isSolana: Boolean, // TODO staking [REDACTED_TASK_KEY] support solana multisize hashes signing
|
||||
) {
|
||||
if (state !is StakingStates.ConfirmationState.Data) return
|
||||
val isAmountEditable = type == StakingActionCommonType.Enter || type == StakingActionCommonType.Exit
|
||||
val isAmountEditable = type == StakingActionCommonType.Enter || type == StakingActionCommonType.Exit && !isSolana
|
||||
val isTransactionSent = state.innerState == InnerConfirmationStakingState.COMPLETED
|
||||
val isTransactionInProgress = state.notifications.any { it is StakingNotification.Warning.TransactionInProgress }
|
||||
Column(
|
||||
|
|
@ -90,6 +91,7 @@ private fun Preview_StakingConfirmationContent() {
|
|||
validatorState = ValidatorStatePreviewData.validatorState,
|
||||
clickIntents = StakingClickIntentsStub,
|
||||
type = StakingActionCommonType.Enter,
|
||||
isSolana = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import com.tangem.features.staking.impl.presentation.state.bottomsheet.StakingAc
|
|||
import com.tangem.features.staking.impl.presentation.state.bottomsheet.StakingInfoBottomSheetConfig
|
||||
import com.tangem.features.staking.impl.presentation.ui.bottomsheet.StakingActionSelectorBottomSheet
|
||||
import com.tangem.features.staking.impl.presentation.ui.bottomsheet.StakingInfoBottomSheet
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isSolana
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
|
@ -170,6 +171,7 @@ private fun StakingScreenContent(uiState: StakingUiState, modifier: Modifier = M
|
|||
validatorState = uiState.validatorState,
|
||||
clickIntents = uiState.clickIntents,
|
||||
type = uiState.actionType,
|
||||
isSolana = isSolana(uiState.cryptoCurrencyBlockchainId),
|
||||
)
|
||||
StakingStep.RestakeValidator,
|
||||
StakingStep.Validators,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import arrow.core.raise.either
|
|||
import arrow.core.right
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.data.common.currency.CryptoCurrencyFactory
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
|
|
@ -48,12 +49,13 @@ internal class DefaultSwapRepository(
|
|||
private val errorsDataConverter: ErrorsDataConverter,
|
||||
private val dataSignatureVerifier: DataSignatureVerifier,
|
||||
moshi: Moshi,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
) : SwapRepository {
|
||||
|
||||
private val expressDataConverter = ExpressDataConverter()
|
||||
private val leastTokenInfoConverter = LeastTokenInfoConverter()
|
||||
private val swapPairInfoConverter = SwapPairInfoConverter()
|
||||
private val cryptoCurrencyFactory = CryptoCurrencyFactory()
|
||||
private val cryptoCurrencyFactory = CryptoCurrencyFactory(excludedBlockchains)
|
||||
private val exchangeStatusConverter = ExchangeStatusConverter()
|
||||
private val txDetailsMoshiAdapter = moshi.adapter(TxDetails::class.java)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.feature.swap
|
||||
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectList
|
||||
|
|
@ -18,12 +19,13 @@ import kotlinx.coroutines.flow.flowOn
|
|||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class DefaultSwapTransactionRepository(
|
||||
internal class DefaultSwapTransactionRepository(
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
) : SwapTransactionRepository {
|
||||
|
||||
private val converter = SavedSwapTransactionListConverter()
|
||||
private val converter = SavedSwapTransactionListConverter(excludedBlockchains)
|
||||
|
||||
override suspend fun storeTransaction(
|
||||
userWalletId: UserWalletId,
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ internal class ErrorsDataConverter(
|
|||
2270 -> ExpressDataError.ExchangeNotEnoughBalanceError(code = error.code)
|
||||
2280 -> ExpressDataError.ExchangeInvalidAddressError(code = error.code)
|
||||
2290 -> tryParseExchangeInvalidFromDecimalsError(error = error)
|
||||
2320 -> tryParseProviderDifferentAmountError(error = error)
|
||||
else -> ExpressDataError.UnknownErrorWithCode(error.code)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
|
|
@ -79,4 +80,20 @@ internal class ErrorsDataConverter(
|
|||
expressFromDecimals = expressFromDecimals,
|
||||
)
|
||||
}
|
||||
|
||||
private fun tryParseProviderDifferentAmountError(error: ExpressError): ExpressDataError {
|
||||
val decimals = error.value?.decimals ?: return ExpressDataError.UnknownErrorWithCode(error.code)
|
||||
|
||||
val fromAmount = error.value?.fromAmount?.toBigDecimalOrNull()
|
||||
?: return ExpressDataError.UnknownErrorWithCode(error.code)
|
||||
val fromAmountProvider = error.value?.fromAmountProvider?.toBigDecimalOrNull()
|
||||
?: return ExpressDataError.UnknownErrorWithCode(error.code)
|
||||
|
||||
return ExpressDataError.ProviderDifferentAmountError(
|
||||
code = error.code,
|
||||
decimals = decimals,
|
||||
fromAmount = fromAmount.movePointLeft(decimals),
|
||||
fromProviderAmount = fromAmountProvider.movePointLeft(decimals),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.feature.swap.converters
|
||||
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory
|
||||
import com.tangem.data.common.currency.UserTokensResponseFactory
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
|
|
@ -11,10 +12,11 @@ import com.tangem.feature.swap.domain.models.domain.SavedSwapTransactionListMode
|
|||
import com.tangem.feature.swap.domain.models.domain.SavedSwapTransactionModel
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
class SavedSwapTransactionListConverter :
|
||||
Converter<SavedSwapTransactionListModel, SavedSwapTransactionListModelInner> {
|
||||
internal class SavedSwapTransactionListConverter(
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
) : Converter<SavedSwapTransactionListModel, SavedSwapTransactionListModelInner> {
|
||||
|
||||
private val responseCryptoCurrenciesFactory = ResponseCryptoCurrenciesFactory()
|
||||
private val responseCryptoCurrenciesFactory = ResponseCryptoCurrenciesFactory(excludedBlockchains)
|
||||
private val userTokensResponseFactory = UserTokensResponseFactory()
|
||||
|
||||
override fun convert(value: SavedSwapTransactionListModel) = SavedSwapTransactionListModelInner(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.feature.swap.di
|
||||
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.datasource.api.express.TangemExpressApi
|
||||
import com.tangem.datasource.api.express.models.response.ExpressErrorResponse
|
||||
import com.tangem.datasource.crypto.DataSignatureVerifier
|
||||
|
|
@ -34,6 +35,7 @@ internal class SwapDataModule {
|
|||
userWalletsListManager: UserWalletsListManager,
|
||||
errorsDataConverter: ErrorsDataConverter,
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
): SwapRepository {
|
||||
return DefaultSwapRepository(
|
||||
tangemExpressApi = tangemExpressApi,
|
||||
|
|
@ -43,6 +45,7 @@ internal class SwapDataModule {
|
|||
errorsDataConverter = errorsDataConverter,
|
||||
dataSignatureVerifier = dataSignature,
|
||||
moshi = moshi,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -51,10 +54,12 @@ internal class SwapDataModule {
|
|||
fun provideSwapTransactionRepository(
|
||||
appPreferencesStore: AppPreferencesStore,
|
||||
dispatcherProvider: CoroutineDispatcherProvider,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
): SwapTransactionRepository {
|
||||
return DefaultSwapTransactionRepository(
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
dispatchers = dispatcherProvider,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,13 @@ sealed class ExpressDataError {
|
|||
val expressFromDecimals: Int,
|
||||
) : ExpressDataError()
|
||||
|
||||
data class ProviderDifferentAmountError(
|
||||
override val code: Int,
|
||||
val fromAmount: BigDecimal,
|
||||
val fromProviderAmount: BigDecimal,
|
||||
val decimals: Int,
|
||||
) : ExpressDataError()
|
||||
|
||||
data class UnknownErrorWithCode(override val code: Int) : ExpressDataError()
|
||||
|
||||
data class InvalidSignatureError(override val code: Int = 990) : ExpressDataError()
|
||||
|
|
|
|||
|
|
@ -10,10 +10,7 @@ import com.tangem.core.ui.components.notifications.NotificationConfig
|
|||
import com.tangem.core.ui.event.consumedEvent
|
||||
import com.tangem.core.ui.event.triggeredEvent
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.format.bigdecimal.anyDecimals
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.format.bigdecimal.uncapped
|
||||
import com.tangem.core.ui.format.bigdecimal.*
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.core.ui.utils.parseBigDecimal
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
|
|
@ -781,6 +778,19 @@ internal class StateBuilder(
|
|||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
),
|
||||
)
|
||||
is ExpressDataError.ProviderDifferentAmountError -> SwapWarning.GeneralError(
|
||||
notificationConfig = NotificationConfig(
|
||||
title = resourceReference(id = R.string.common_error),
|
||||
subtitle = resourceReference(
|
||||
R.string.express_error_provider_amount_roundup,
|
||||
formatArgs = wrappedList(
|
||||
expressDataError.code,
|
||||
expressDataError.fromProviderAmount.format { simple(decimals = expressDataError.decimals) },
|
||||
),
|
||||
),
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
),
|
||||
)
|
||||
else -> SwapWarning.GeneralWarning(
|
||||
notificationConfig = NotificationConfig(
|
||||
title = providerErrorTitle,
|
||||
|
|
|
|||
|
|
@ -28,6 +28,13 @@ internal fun getExpressErrorMessage(expressDataError: ExpressDataError): TextRef
|
|||
id = R.string.express_error_swap_pair_unavailable,
|
||||
formatArgs = wrappedList(expressDataError.code),
|
||||
)
|
||||
is ExpressDataError.ProviderDifferentAmountError -> resourceReference(
|
||||
R.string.express_error_provider_amount_roundup,
|
||||
formatArgs = wrappedList(
|
||||
expressDataError.code,
|
||||
expressDataError.fromProviderAmount.format { simple(decimals = expressDataError.decimals) },
|
||||
),
|
||||
)
|
||||
else -> resourceReference(R.string.express_error_code, wrappedList(expressDataError.code.toString()))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase
|
|||
import com.tangem.domain.tokens.error.CurrencyStatusError
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents
|
||||
|
|
@ -25,6 +26,7 @@ internal class GetSingleWalletWarningsFactory @Inject constructor(
|
|||
private val isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase,
|
||||
private val isNeedToBackupUseCase: IsNeedToBackupUseCase,
|
||||
private val hasSingleWalletSignedHashesUseCase: HasSingleWalletSignedHashesUseCase,
|
||||
private val getWalletsUseCase: GetWalletsUseCase,
|
||||
) {
|
||||
|
||||
private var readyForRateAppNotification = false
|
||||
|
|
@ -36,22 +38,32 @@ internal class GetSingleWalletWarningsFactory @Inject constructor(
|
|||
flow = getPrimaryCurrencyStatusUpdatesUseCase(userWallet.walletId),
|
||||
flow2 = isReadyToShowRateAppUseCase().conflate(),
|
||||
flow3 = isNeedToBackupUseCase(userWallet.walletId).conflate(),
|
||||
) { primaryCurrencyStatus, isReadyToShowRating, isNeedToBackup ->
|
||||
flow4 = getWalletsUseCase().conflate(),
|
||||
) { maybePrimaryCurrencyStatus, isReadyToShowRating, isNeedToBackup, userWallets ->
|
||||
readyForRateAppNotification = true
|
||||
buildList {
|
||||
addCriticalNotifications(cardTypesResolver)
|
||||
|
||||
addInformationalNotifications(cardTypesResolver, clickIntents)
|
||||
|
||||
addWarningNotifications(
|
||||
userWallet,
|
||||
cardTypesResolver,
|
||||
primaryCurrencyStatus,
|
||||
isNeedToBackup,
|
||||
clickIntents,
|
||||
addCriticalNotifications(
|
||||
cardTypesResolver = cardTypesResolver,
|
||||
)
|
||||
|
||||
addRateTheAppNotification(isReadyToShowRating, clickIntents)
|
||||
addInformationalNotifications(
|
||||
userWallets = userWallets,
|
||||
cardTypesResolver = cardTypesResolver,
|
||||
clickIntents = clickIntents,
|
||||
)
|
||||
|
||||
addWarningNotifications(
|
||||
userWallet = userWallet,
|
||||
cardTypesResolver = cardTypesResolver,
|
||||
maybePrimaryCurrencyStatus = maybePrimaryCurrencyStatus,
|
||||
isNeedToBackup = isNeedToBackup,
|
||||
clickIntents = clickIntents,
|
||||
)
|
||||
|
||||
addRateTheAppNotification(
|
||||
isReadyToShowRating = isReadyToShowRating,
|
||||
clickIntents = clickIntents,
|
||||
)
|
||||
}.toImmutableList()
|
||||
}
|
||||
}
|
||||
|
|
@ -76,14 +88,19 @@ internal class GetSingleWalletWarningsFactory @Inject constructor(
|
|||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addInformationalNotifications(
|
||||
userWallets: List<UserWallet>,
|
||||
cardTypesResolver: CardTypesResolver,
|
||||
clickIntents: WalletClickIntents,
|
||||
) {
|
||||
val userHasWalletOrWallet2 = userWallets.any {
|
||||
val typesResolver = it.scanResponse.cardTypesResolver
|
||||
typesResolver.isTangemWallet() || typesResolver.isWallet2()
|
||||
}
|
||||
addIf(
|
||||
element = WalletNotification.NoteMigration(
|
||||
onClick = { clickIntents.onNoteMigrationButtonClick(NOTE_MIGRATION_URL) },
|
||||
),
|
||||
condition = cardTypesResolver.isTangemNote(),
|
||||
condition = cardTypesResolver.isTangemNote() && !userHasWalletOrWallet2,
|
||||
)
|
||||
|
||||
addIf(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue