Updated on 2026-08-14
This commit is contained in:
commit
d6f9f59866
1729 changed files with 67614 additions and 9361 deletions
202
features/swap-v2/CLAUDE.md
Normal file
202
features/swap-v2/CLAUDE.md
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
# Swap V2 / Send-with-Swap Feature
|
||||
|
||||
This module implements **Send-with-Swap (SvS)**: a send transaction where the sent token is swapped
|
||||
(CEX) to a different *receive* token at a *destination address* in one flow. The user picks a receive
|
||||
token, enters amounts (with Fixed/Float rate), enters a destination address (+ memo for memo-networks),
|
||||
reviews on Confirm, and sends.
|
||||
|
||||
> There is **no standalone token↔token swap UI** in this module — that lives in `features/swap/`
|
||||
> (see `features/swap/CLAUDE.md`). swap-v2 is the redesigned **send-with-swap** flow plus its shared
|
||||
> amount/provider/notifications subscreens, built on the **send-v2** subcomponents.
|
||||
|
||||
## Module Structure
|
||||
|
||||
```
|
||||
features/swap-v2/
|
||||
api/ — com.tangem.features.swap.v2.api
|
||||
SendWithSwapComponent (+ Params/Factory), SwapFeatureToggles,
|
||||
SwapAmountUpdateTrigger, subcomponents/, choosetoken/
|
||||
impl/ — com.tangem.features.swap.v2.impl (android-library + Hilt/kapt)
|
||||
sendviaswap/ — SvS flow root, model, routes, confirm/, success/, analytics/
|
||||
amount/ — swap amount screen (model, transformers, converters, entity, ui)
|
||||
chooseprovider/— provider selector bottom sheet
|
||||
choosetoken/ — receive-token / network selection
|
||||
notifications/ — swap-specific notifications (price impact, express errors)
|
||||
common/ — ConfirmData, SwapAlertFactory, SwapUtils, entities (ConfirmUM, SwapQuoteUM)
|
||||
di/ — Hilt modules
|
||||
```
|
||||
|
||||
**Package naming:** API = `com.tangem.features.swap.v2.api`, Impl = `com.tangem.features.swap.v2.impl`.
|
||||
Consistent `.v2` segment (unlike the legacy `features/swap` which uses `feature.swap` for impl).
|
||||
|
||||
**Build commands:**
|
||||
```bash
|
||||
./gradlew :features:swap-v2:impl:compileDebugKotlin
|
||||
./gradlew :features:swap-v2:api:compileDebugKotlin
|
||||
./gradlew :features:swap-v2:impl:testDebugUnitTest
|
||||
./gradlew :features:swap-v2:impl:detekt
|
||||
```
|
||||
|
||||
## The SvS Flow (sendviaswap/)
|
||||
|
||||
### Entry: SendWithSwapComponent (api) / DefaultSendWithSwapComponent (impl)
|
||||
- `SendWithSwapComponent.Params`: `userWalletId`, `currency` (the **FROM** token), `callback`.
|
||||
- `DefaultSendWithSwapComponent` (`impl/.../sendviaswap/DefaultSendWithSwapComponent.kt`) owns an inner
|
||||
`StackNavigation<SendWithSwapRoute>` + `InnerRouter`, creates `SendWithSwapModel` via
|
||||
`getOrCreateModel`, and a `childStack` rendering Amount/Destination/Confirm/Success.
|
||||
|
||||
### Routes: SendWithSwapRoute
|
||||
`impl/.../sendviaswap/SendWithSwapRoute.kt` — sealed `Route`, every entry has `isEditMode: Boolean`:
|
||||
- `Amount(isEditMode)` — implements `SwapAmountRoute`
|
||||
- `Destination(isEditMode)` — implements send-v2 `DestinationRoute`
|
||||
- `Confirm` (object, `isEditMode = false`)
|
||||
- `Success` (object, `isEditMode = false`)
|
||||
|
||||
`isEditMode` distinguishes the **linear** forward flow (`Amount → Destination → Confirm`) from
|
||||
**re-editing** a step *from Confirm* (`showEditAmount`/`showEditDestination` push the step with
|
||||
`isEditMode = true`; `onNextClick` then **pops** back to Confirm instead of advancing).
|
||||
|
||||
### Parent model: SendWithSwapModel
|
||||
`impl/.../sendviaswap/model/SendWithSwapModel.kt`. `@ModelScoped`. Implements three child callbacks
|
||||
(`SwapAmountComponent.ModelCallback`, `SendDestinationComponent.ModelCallback`,
|
||||
`SendWithSwapConfirmComponent.ModelCallback`). Holds the **aggregate** state:
|
||||
- `uiState: StateFlow<SendWithSwapUM>` — `{ amountUM, destinationUM, feeSelectorUM, confirmUM, navigationUM }`
|
||||
- `currentRoute: MutableStateFlow<SendWithSwapRoute>`
|
||||
- `primaryCryptoCurrencyStatusFlow`, `primaryFeePaidCurrencyStatusFlow`, `accountFlow`,
|
||||
`isAccountModeFlow`, `isBalanceHiddenFlow` — read-only sources passed down to children as params.
|
||||
|
||||
Child→parent merge callbacks:
|
||||
- `onAmountResult(amountUM)` → `uiState.copy(amountUM = …)`
|
||||
- `onDestinationResult(destinationUM)` → `uiState.copy(destinationUM = …)`
|
||||
- `onResult(route, sendWithSwapUM)` → **`if (currentRoute.value == route) uiState.value = …`** (full replace,
|
||||
route-guarded; used by Confirm to publish its full state back up)
|
||||
- `onNavigationResult(navigationUM)` → drives the shared footer button/app-bar.
|
||||
|
||||
### childStack subscription = the state-sync mechanism (READ THIS)
|
||||
`DefaultSendWithSwapComponent.init { childStack.subscribe(CREATE_DESTROY) { stack → componentScope.launch { … } } }`:
|
||||
on every active-child change it **pushes the parent's current snapshot into the newly-active child** and
|
||||
then emits the new route:
|
||||
```kotlin
|
||||
when (active) {
|
||||
is SwapAmountComponent -> active.updateState(uiState.value.amountUM)
|
||||
is SendDestinationComponent -> active.updateState(uiState.value.destinationUM) // screen
|
||||
is SendWithSwapConfirmComponent ->
|
||||
if (model.currentRoute.value.isEditMode) active.updateState(uiState.value) // ← gated!
|
||||
}
|
||||
model.currentRoute.emit(stack.active.configuration) // emitted AFTER the isEditMode read
|
||||
```
|
||||
The `isEditMode` check intentionally reads the **previous** route (the emit happens afterwards) so it is
|
||||
true exactly when returning to a *reused* Confirm from an edit step. In the linear flow Confirm is
|
||||
re-created fresh from `params.sendWithSwapUM`, so no re-push is needed.
|
||||
|
||||
### Confirm: SendWithSwapConfirmComponent / SendWithSwapConfirmModel
|
||||
`impl/.../sendviaswap/confirm/`. The Confirm screen embeds **read-only blocks** reused from send-v2:
|
||||
- `SwapAmountBlockComponent` (swap-v2)
|
||||
- `SendDestinationBlockComponent` (send-v2) — shows address + memo, click → `showEditDestination`
|
||||
- `FeeSelectorBlockComponent` (send-v2)
|
||||
- `SendNotificationsComponent` (send-v2) + `SwapNotificationsComponent` (swap-v2)
|
||||
|
||||
`SendWithSwapConfirmModel`:
|
||||
- `uiState: StateFlow<SendWithSwapUM>` seeded from `params.sendWithSwapUM`.
|
||||
- `confirmData: ConfirmData` (computed) — extracts `enteredFromAmount/enteredToAmount`,
|
||||
`enteredDestination`, `enteredMemo`, `fee`, statuses, quote, rateType, amountType, priceImpact from
|
||||
`uiState`; this is what the transaction + notifications are built from.
|
||||
- `onFeeResult/onAmountResult/onDestinationResult` — block callbacks copy into `uiState`.
|
||||
- `updateState(sendWithSwapUM)` — full replace (used by the edit-mode re-push).
|
||||
- `configConfirmNavigation` — `combine(uiState, currentRoute).filter { route is Confirm }` →
|
||||
`callback.onResult(Confirm, state.copy(navigationUM = …))` (publishes confirm state up to the parent).
|
||||
- Sending: `SwapTransactionSender` (CEX only; DEX/DEX_BRIDGE/ONRAMP rejected). Success →
|
||||
`SendWithSwapConfirmSentStateTransformer` + `router.replaceAll(Success)`.
|
||||
|
||||
### Success: SendWithSwapSuccessComponent
|
||||
`impl/.../sendviaswap/success/` — renders `ConfirmUM.Success` (tx date, explorer url, provider, swap data).
|
||||
|
||||
## Amount screen (amount/)
|
||||
|
||||
- `SwapAmountComponent` / `SwapAmountModel` (`amount/model/SwapAmountModel.kt`, ~big orchestrator).
|
||||
- State `SwapAmountUM` (`amount/entity/SwapAmountUM.kt`): `Empty(swapDirection)` | `Content` with
|
||||
`primaryAmount`/`secondaryAmount` fields, `primary/secondaryCryptoCurrencyStatus`,
|
||||
`swapRateType: ExpressRateType` (Fixed|Float), `swapQuotes`, `selectedQuote: SwapQuoteUM`, `priceImpact`.
|
||||
- Quotes are loaded periodically via a task scheduler and through `GetSwapQuoteUseCase`.
|
||||
- Transformers (`amount/model/transformers/`): `SwapAmountValueChangeTransformer`,
|
||||
`SwapAmountSelectQuoteTransformer`, `SwapAmountSetQuotesTransformer`,
|
||||
`SwapAmountChangeAmountTypeTransformer`, `SwapAmount{Reduce*,Max,Paste,…}Transformer`, applied via
|
||||
`uiState.transformerUpdate(…)`.
|
||||
- **Fixed vs Float:** `SwapAmountType.To` must use `ExpressRateType.Fixed` (the float API can't target a
|
||||
to-amount); `SwapAmountType.From` uses `Float`. Provider filtering checks
|
||||
`provider.rateTypes.contains(rateType)` before requesting a quote.
|
||||
|
||||
## Choose provider / token, Notifications
|
||||
|
||||
- `chooseprovider/` — `SwapChooseProviderComponent`/`Model`, bottom-sheet provider list (converters
|
||||
`SwapProviderListItemConverter`, `SwapProviderStateConverter`).
|
||||
- `choosetoken/` — receive-token + network selection (`SwapChooseTokenNetworkModel`, transformers).
|
||||
- `notifications/` — `SwapNotificationsComponent`/`Model`, driven by `SwapNotificationsUpdateTrigger`/
|
||||
`…Listener`; produces price-impact / express-error / destination-tag-required notifications.
|
||||
|
||||
## Reused send-v2 subcomponents (API boundary)
|
||||
|
||||
SvS consumes these `features/send-v2/api` contracts (impl injected via DI):
|
||||
- `SendDestinationComponent.Factory` — the navigable **address/memo screen**.
|
||||
- `SendDestinationBlockComponent.Factory` — the **read-only block** on Confirm.
|
||||
- `FeeSelectorBlockComponent.Factory` + `FeeSelectorReloadTrigger`.
|
||||
- `SendNotificationsComponent.Factory` + `SendNotificationsUpdateTrigger`/`…Listener`.
|
||||
- Entities: `DestinationUM`, `FeeSelectorUM`, `NavigationUM`, `PredefinedValues`.
|
||||
|
||||
The shared destination model is **`features/send-v2/.../subcomponents/destination/model/SendDestinationModel.kt`**.
|
||||
Its `updateState(destinationUM)` does `if (Content && isInitialized) _uiState.value = destinationUM`
|
||||
(StateFlow dedups equal values). `saveResult()` (push to the parent callback) runs on Next, on
|
||||
auto-next, and **on back only when `!route.isEditMode`**.
|
||||
|
||||
## DI modules (di/ and per-subpackage di/)
|
||||
|
||||
| Module | Scope | Provides |
|
||||
|---|---|---|
|
||||
| `SwapFeatureModules` | Singleton | `SwapFeatureToggles` |
|
||||
| `SendWithSwapModule` | Singleton + Model | `SendWithSwapComponent.Factory`, `SendWithSwapModel` |
|
||||
| `SwapAmountModule` | Singleton + Model | `SwapAmountModel`, `SwapAmountUpdateTrigger/Listener`, `SwapAmountReduceTrigger/Listener` |
|
||||
| `SendWithSwapConfirmModule` | Model | `SendWithSwapConfirmModel` |
|
||||
| `SwapChooseProviderModule` | Model | `SwapChooseProviderModel` |
|
||||
| `SwapChooseTokenModule` | Singleton + Model | choose-token factories/model |
|
||||
| `SwapNotificationsModule` | Singleton + Model | `SwapNotificationsModel`, `SwapNotificationsUpdateTrigger/Listener` |
|
||||
|
||||
## Analytics
|
||||
|
||||
- `SendWithSwapAnalyticEvents` (`sendviaswap/analytics/`) — `ConfirmationScreenOpened`,
|
||||
`AmountScreenOpened`, `TransactionScreenOpened`, `OnSendClick`, `NoticeFixedRate/FloatRate`,
|
||||
`Error{InsufficientBalance,MinAmount,MaxAmount,ExpressQuote}`, `HighPriceImpact`, `TradeTooLarge`;
|
||||
category = `CommonSendAnalyticEvents.SEND_CATEGORY`. `ExpressRateType.toAnalyticsRateType()` maps rate.
|
||||
- `SwapAmountAnalyticEvents` + `SwapAmountAnalyticsSender` (`amount/analytics/`) — provider selector events.
|
||||
|
||||
## State-management patterns & gotchas
|
||||
|
||||
- **Transformer pattern:** `uiState.transformerUpdate(SomeTransformer(...))`; transformers early-return
|
||||
`prevState` if not the expected subtype (`as? Content ?: return prevState`).
|
||||
- **Three+ StateFlows hold the destination at once.** The memo/address lives in: the navigable
|
||||
Destination **screen** model (#A), the parent `SendWithSwapModel.uiState.destinationUM` (#B), the
|
||||
`SendWithSwapConfirmModel.uiState.destinationUM` (#C), and the Confirm-embedded destination **block**
|
||||
model (#D, what Confirm actually displays). They are synced by **snapshot copies** (`updateState`,
|
||||
`onResult`, `onDestinationResult`) over `StateFlow.value =` (which **dedups by `equals`**), plus the
|
||||
block's self-feeding `init { uiState.onEach { onResult(it) } }`. This is fragile — see [REDACTED_TASK_KEY]
|
||||
("floating memo": an edit on #A intermittently fails to reach #D). Prefer a single source of truth
|
||||
when touching this area; do **not** assume an `updateState` re-push actually emits (equal value = no-op).
|
||||
- **Edit-mode back does not persist.** Leaving an edit step via the back arrow / system back skips
|
||||
`saveResult()` (`SendDestinationModel.configDestinationNavigation`, `if (!route.isEditMode)`), so the
|
||||
parent keeps the pre-edit value. The footer "Continue"/"Next" button always persists. This is shared
|
||||
by regular Send + NFT Send + SvS.
|
||||
- **`onResult` is route-guarded.** `SendWithSwapModel.onResult` only applies when
|
||||
`currentRoute.value == route`, which protects against late/stale Confirm emissions overwriting the
|
||||
parent after navigating away. Keep that guard if you refactor.
|
||||
- **`currentRoute.emit` runs at the END of the subscribe coroutine**, so the `isEditMode` re-push gate
|
||||
reads the *previous* route. Relies on `componentScope` launches being serialized (main dispatcher).
|
||||
- **CEX-only.** `SwapTransactionSender` rejects DEX/DEX_BRIDGE/ONRAMP. Destination address for CEX is
|
||||
only known after exchange-data, so confirm notifications pass `destinationAddress = null` for the
|
||||
send-notifications path.
|
||||
|
||||
## Testing
|
||||
|
||||
JUnit 5 + MockK + Turbine + Truth (see project `.claude/rules/unit-testing.md`). Feature-model tests
|
||||
build the heavy graph with relaxed mocks and a single `StandardTestDispatcher`; drive with
|
||||
`advanceUntilIdle()` and `model.onDestroy()`. For SvS state-sync regressions, prefer parent-model
|
||||
(`SendWithSwapModel`) tests asserting that an edit propagated through `onDestinationResult` is the value
|
||||
that `uiState.destinationUM` ends up holding across an edit→confirm round trip.
|
||||
|
|
@ -13,7 +13,7 @@ dependencies {
|
|||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.ui)
|
||||
|
||||
api(projects.features.sendV2.api)
|
||||
api(projects.features.send.api)
|
||||
|
||||
/** Common */
|
||||
implementation(projects.common.ui)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import com.tangem.core.decompose.factory.ComponentFactory
|
|||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.features.send.v2.api.entry.SendEntryRoute
|
||||
import com.tangem.features.send.api.entry.SendEntryRoute
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
interface SendWithSwapComponent : ComposableContentComponent {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ dependencies {
|
|||
/** Feature */
|
||||
implementation(projects.features.swapV2.api)
|
||||
implementation(projects.features.manageTokens.api)
|
||||
implementation(projects.features.sendV2.api)
|
||||
implementation(projects.features.send.api)
|
||||
implementation(projects.features.commonFeatures.api)
|
||||
|
||||
/** Core */
|
||||
|
|
@ -95,7 +95,7 @@ dependencies {
|
|||
kapt(deps.hilt.kapt)
|
||||
|
||||
/** Test */
|
||||
testImplementation(deps.test.junit)
|
||||
testImplementation(deps.test.junit5)
|
||||
testImplementation(deps.test.truth)
|
||||
testImplementation(deps.test.mockk)
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@ import com.tangem.domain.models.currency.CryptoCurrency
|
|||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.swap.models.SwapDirection
|
||||
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
|
||||
import com.tangem.features.send.api.analytics.CommonSendAnalyticEvents
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
|
||||
import com.tangem.features.swap.v2.impl.sendviaswap.SendWithSwapRoute
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
|
|
|||
|
|
@ -37,8 +37,8 @@ import com.tangem.domain.tokens.GetMinimumTransactionAmountSyncUseCase
|
|||
import com.tangem.domain.transaction.models.AllowanceInfo
|
||||
import com.tangem.domain.transaction.usecase.GetAllowanceInfoUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.features.send.v2.api.subcomponents.amount.analytics.CommonSendAmountAnalyticEvents
|
||||
import com.tangem.features.send.v2.api.subcomponents.feeSelector.FeeSelectorReloadTrigger
|
||||
import com.tangem.features.send.api.subcomponents.amount.analytics.CommonSendAmountAnalyticEvents
|
||||
import com.tangem.features.send.api.subcomponents.feeSelector.FeeSelectorReloadTrigger
|
||||
import com.tangem.features.swap.v2.api.choosetoken.SwapChooseTokenNetworkListener
|
||||
import com.tangem.features.swap.v2.impl.R
|
||||
import com.tangem.features.swap.v2.impl.amount.SwapAmountBlockComponent.SwapChooseProviderConfig
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import com.tangem.domain.swap.models.SwapTxType
|
|||
import com.tangem.domain.swap.usecase.GetSwapSupportedPairsUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.features.managetokens.component.analytics.CommonManageTokensAnalyticEvents
|
||||
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
|
||||
import com.tangem.features.send.api.analytics.CommonSendAnalyticEvents
|
||||
import com.tangem.features.swap.v2.api.choosetoken.SwapChooseTokenNetworkComponent
|
||||
import com.tangem.features.swap.v2.impl.choosetoken.fromSupported.entity.SwapChooseTokenNetworkContentUM
|
||||
import com.tangem.features.swap.v2.impl.choosetoken.fromSupported.entity.SwapChooseTokenNetworkUM
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import com.tangem.common.ui.notifications.NotificationUM
|
|||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.express.models.ExpressProvider
|
||||
import com.tangem.domain.swap.models.SwapDataModel
|
||||
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationUM
|
||||
import com.tangem.features.send.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.api.subcomponents.destination.entity.DestinationUM
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
|
|
|
|||
|
|
@ -22,10 +22,10 @@ import com.tangem.core.ui.decompose.ComposableContentComponent
|
|||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.swap.models.R
|
||||
import com.tangem.domain.swap.models.SwapDirection
|
||||
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.DestinationRoute
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationComponent
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationComponentParams
|
||||
import com.tangem.features.send.api.analytics.CommonSendAnalyticEvents
|
||||
import com.tangem.features.send.api.subcomponents.destination.DestinationRoute
|
||||
import com.tangem.features.send.api.subcomponents.destination.SendDestinationComponent
|
||||
import com.tangem.features.send.api.subcomponents.destination.SendDestinationComponentParams
|
||||
import com.tangem.features.swap.v2.api.SendWithSwapComponent
|
||||
import com.tangem.features.swap.v2.impl.amount.SwapAmountComponent
|
||||
import com.tangem.features.swap.v2.impl.amount.SwapAmountComponentParams
|
||||
|
|
@ -102,6 +102,8 @@ internal class DefaultSendWithSwapComponent @AssistedInject constructor(
|
|||
if (model.currentRoute.value.isEditMode) {
|
||||
activeComponent.updateState(model.uiState.value)
|
||||
}
|
||||
// Re-sync destination from parent on Confirm entry, bypassing the edit-mode gate ([REDACTED_TASK_KEY]).
|
||||
activeComponent.updateDestinationState(model.uiState.value.destinationUM)
|
||||
val fromCurrency = params.currency
|
||||
val content = model.uiState.value.amountUM as? SwapAmountUM.Content ?: return@launch
|
||||
val toCurrency = content.secondaryCryptoCurrencyStatus?.currency ?: return@launch
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.features.swap.v2.impl.sendviaswap
|
||||
|
||||
import com.tangem.core.decompose.navigation.Route
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.DestinationRoute
|
||||
import com.tangem.features.send.api.subcomponents.destination.DestinationRoute
|
||||
import com.tangem.features.swap.v2.impl.amount.SwapAmountRoute
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import com.tangem.core.analytics.models.AnalyticsParam.Key.SEND_TOKEN
|
|||
import com.tangem.core.analytics.models.AppsFlyerIncludedEvent
|
||||
import com.tangem.domain.express.models.ExpressRateType
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
|
||||
import com.tangem.features.send.api.analytics.CommonSendAnalyticEvents
|
||||
|
||||
internal sealed class SendWithSwapAnalyticEvents(
|
||||
event: String,
|
||||
|
|
|
|||
|
|
@ -14,13 +14,14 @@ import com.tangem.domain.models.account.Account
|
|||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.swap.models.SwapDirection
|
||||
import com.tangem.features.send.v2.api.FeeSelectorBlockComponent
|
||||
import com.tangem.features.send.v2.api.SendNotificationsComponent
|
||||
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
|
||||
import com.tangem.features.send.v2.api.entity.PredefinedValues
|
||||
import com.tangem.features.send.v2.api.params.FeeSelectorParams.*
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationBlockComponent
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationComponentParams
|
||||
import com.tangem.features.send.api.FeeSelectorBlockComponent
|
||||
import com.tangem.features.send.api.SendNotificationsComponent
|
||||
import com.tangem.features.send.api.analytics.CommonSendAnalyticEvents
|
||||
import com.tangem.features.send.api.entity.PredefinedValues
|
||||
import com.tangem.features.send.api.params.FeeSelectorParams.*
|
||||
import com.tangem.features.send.api.subcomponents.destination.SendDestinationBlockComponent
|
||||
import com.tangem.features.send.api.subcomponents.destination.SendDestinationComponentParams
|
||||
import com.tangem.features.send.api.subcomponents.destination.entity.DestinationUM
|
||||
import com.tangem.features.swap.v2.impl.amount.SwapAmountBlockComponent
|
||||
import com.tangem.features.swap.v2.impl.amount.SwapAmountComponentParams
|
||||
import com.tangem.features.swap.v2.impl.common.SwapUtils.SEND_WITH_SWAP_PROVIDER_TYPES
|
||||
|
|
@ -40,7 +41,7 @@ import kotlinx.coroutines.flow.*
|
|||
internal class SendWithSwapConfirmComponent @AssistedInject constructor(
|
||||
@Assisted private val appComponentContext: AppComponentContext,
|
||||
@Assisted private val params: Params,
|
||||
sendDestinationBlockComponent: SendDestinationBlockComponent.Factory,
|
||||
sendDestinationBlockComponentFactory: SendDestinationBlockComponent.Factory,
|
||||
feeSelectorBlockComponentFactory: FeeSelectorBlockComponent.Factory,
|
||||
sendNotificationsComponentFactory: SendNotificationsComponent.Factory,
|
||||
) : ComposableContentComponent, AppComponentContext by appComponentContext {
|
||||
|
|
@ -69,7 +70,7 @@ internal class SendWithSwapConfirmComponent @AssistedInject constructor(
|
|||
onClick = model::showEditAmount,
|
||||
)
|
||||
|
||||
private val sendDestinationBlockComponent = sendDestinationBlockComponent.create(
|
||||
private val sendDestinationBlockComponent = sendDestinationBlockComponentFactory.create(
|
||||
context = child("sendWithSwapConfirmDestinationBlock"),
|
||||
params = SendDestinationComponentParams.DestinationBlockParams(
|
||||
state = model.uiState.value.destinationUM,
|
||||
|
|
@ -81,7 +82,8 @@ internal class SendWithSwapConfirmComponent @AssistedInject constructor(
|
|||
predefinedValues = PredefinedValues.Empty,
|
||||
isAllowSelfSend = true,
|
||||
),
|
||||
onResult = model::onDestinationResult,
|
||||
// No feedback: the read-only block is driven one-way by the model.uiState collector ([REDACTED_TASK_KEY]).
|
||||
onResult = {},
|
||||
onClick = model::showEditDestination,
|
||||
)
|
||||
|
||||
|
|
@ -151,15 +153,29 @@ internal class SendWithSwapConfirmComponent @AssistedInject constructor(
|
|||
val confirmUM = state.confirmUM as? ConfirmUM.Content
|
||||
blockClickEnableFlow.value = confirmUM?.isTransactionInProcess == false
|
||||
}.launchIn(componentScope)
|
||||
|
||||
// Single source of truth: the block always mirrors the model's authoritative destinationUM.
|
||||
model.uiState
|
||||
.map { it.destinationUM }
|
||||
.distinctUntilChanged()
|
||||
.onEach(sendDestinationBlockComponent::updateState)
|
||||
.launchIn(componentScope)
|
||||
}
|
||||
|
||||
fun updateState(sendWithSwapUM: SendWithSwapUM) {
|
||||
amountBlockComponent.updateState(sendWithSwapUM.amountUM)
|
||||
sendDestinationBlockComponent.updateState(sendWithSwapUM.destinationUM)
|
||||
feeSelectorBlockComponent.updateState(sendWithSwapUM.feeSelectorUM)
|
||||
model.updateState(sendWithSwapUM)
|
||||
}
|
||||
|
||||
// Re-sync destination from parent on Confirm entry, bypassing the edit-mode gate; Empty only occurs on
|
||||
// reset (which leaves Confirm), so only Content is applied ([REDACTED_TASK_KEY]).
|
||||
fun updateDestinationState(destinationUM: DestinationUM) {
|
||||
if (destinationUM is DestinationUM.Content && destinationUM != model.uiState.value.destinationUM) {
|
||||
model.onDestinationResult(destinationUM)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val sendWithSwapUM by model.uiState.collectAsStateWithLifecycle()
|
||||
|
|
|
|||
|
|
@ -37,16 +37,16 @@ import com.tangem.domain.transaction.usecase.EstimateFeeUseCase
|
|||
import com.tangem.domain.transaction.usecase.gasless.EstimateFeeForGaslessTxUseCase
|
||||
import com.tangem.domain.transaction.usecase.gasless.EstimateFeeForTokenUseCase
|
||||
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
|
||||
import com.tangem.features.send.v2.api.SendNotificationsComponent
|
||||
import com.tangem.features.send.v2.api.SendNotificationsComponent.Params.NotificationData
|
||||
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
|
||||
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents.SendScreenSource
|
||||
import com.tangem.features.send.v2.api.callbacks.FeeSelectorModelCallback
|
||||
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationUM
|
||||
import com.tangem.features.send.v2.api.subcomponents.feeSelector.FeeSelectorReloadTrigger
|
||||
import com.tangem.features.send.v2.api.subcomponents.notifications.SendNotificationsUpdateListener
|
||||
import com.tangem.features.send.v2.api.subcomponents.notifications.SendNotificationsUpdateTrigger
|
||||
import com.tangem.features.send.api.SendNotificationsComponent
|
||||
import com.tangem.features.send.api.SendNotificationsComponent.Params.NotificationData
|
||||
import com.tangem.features.send.api.analytics.CommonSendAnalyticEvents
|
||||
import com.tangem.features.send.api.analytics.CommonSendAnalyticEvents.SendScreenSource
|
||||
import com.tangem.features.send.api.callbacks.FeeSelectorModelCallback
|
||||
import com.tangem.features.send.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.api.subcomponents.destination.entity.DestinationUM
|
||||
import com.tangem.features.send.api.subcomponents.feeSelector.FeeSelectorReloadTrigger
|
||||
import com.tangem.features.send.api.subcomponents.notifications.SendNotificationsUpdateListener
|
||||
import com.tangem.features.send.api.subcomponents.notifications.SendNotificationsUpdateTrigger
|
||||
import com.tangem.features.swap.v2.api.subcomponents.SwapAmountUpdateTrigger
|
||||
import com.tangem.features.swap.v2.impl.R
|
||||
import com.tangem.features.swap.v2.impl.amount.SwapAmountReduceTrigger
|
||||
|
|
@ -76,7 +76,7 @@ import jakarta.inject.Inject
|
|||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import java.math.BigDecimal
|
||||
import com.tangem.features.send.v2.api.entity.FeeSelectorUM as FeeSelectorUMRedesigned
|
||||
import com.tangem.features.send.api.entity.FeeSelectorUM as FeeSelectorUMRedesigned
|
||||
import com.tangem.utils.transformer.update as transformerUpdate
|
||||
|
||||
@Suppress("LongParameterList", "LargeClass")
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import com.tangem.domain.transaction.usecase.CreateTransferTransactionUseCase
|
|||
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
|
||||
import com.tangem.domain.transaction.usecase.gasless.CreateAndSendGaslessTransactionUseCase
|
||||
import com.tangem.domain.utils.convertToSdkAmount
|
||||
import com.tangem.features.send.v2.api.subcomponents.feeSelector.utils.FeeCalculationUtils
|
||||
import com.tangem.features.send.api.subcomponents.feeSelector.utils.FeeCalculationUtils
|
||||
import com.tangem.features.swap.v2.impl.common.ConfirmData
|
||||
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@ import com.tangem.core.ui.extensions.wrappedList
|
|||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.domain.swap.models.SwapDirection.Companion.withSwapDirection
|
||||
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.v2.api.subcomponents.feeSelector.utils.FeeCalculationUtils.checkIfCustomFeeTooHigh
|
||||
import com.tangem.features.send.v2.api.subcomponents.feeSelector.utils.FeeCalculationUtils.checkIfCustomFeeTooLow
|
||||
import com.tangem.features.send.v2.api.utils.formatFooterFiatFee
|
||||
import com.tangem.features.send.v2.api.utils.getTronTokenFeeSendingText
|
||||
import com.tangem.features.send.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.api.subcomponents.feeSelector.utils.FeeCalculationUtils.checkIfCustomFeeTooHigh
|
||||
import com.tangem.features.send.api.subcomponents.feeSelector.utils.FeeCalculationUtils.checkIfCustomFeeTooLow
|
||||
import com.tangem.features.send.api.utils.formatFooterFiatFee
|
||||
import com.tangem.features.send.api.utils.getTronTokenFeeSendingText
|
||||
import com.tangem.features.swap.v2.impl.R
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
|
||||
import com.tangem.features.swap.v2.impl.common.entity.ConfirmUM
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ import androidx.compose.ui.unit.dp
|
|||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.common.ui.notifications.notifications
|
||||
import com.tangem.core.ui.components.SpacerH16
|
||||
import com.tangem.features.send.v2.api.FeeSelectorBlockComponent
|
||||
import com.tangem.features.send.v2.api.SendNotificationsComponent
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationBlockComponent
|
||||
import com.tangem.features.send.api.FeeSelectorBlockComponent
|
||||
import com.tangem.features.send.api.SendNotificationsComponent
|
||||
import com.tangem.features.send.api.subcomponents.destination.SendDestinationBlockComponent
|
||||
import com.tangem.features.swap.v2.impl.amount.SwapAmountBlockComponent
|
||||
import com.tangem.features.swap.v2.impl.common.entity.ConfirmUM
|
||||
import com.tangem.features.swap.v2.impl.notifications.SwapNotificationsComponent
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.features.swap.v2.impl.sendviaswap.entity
|
||||
|
||||
import com.tangem.common.ui.navigationButtons.NavigationUM
|
||||
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationUM
|
||||
import com.tangem.features.send.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.api.subcomponents.destination.entity.DestinationUM
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
|
||||
import com.tangem.features.swap.v2.impl.common.entity.ConfirmUM
|
||||
|
||||
|
|
|
|||
|
|
@ -19,10 +19,10 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
|||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.swap.models.SwapDirection
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
|
||||
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationComponent
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationUM
|
||||
import com.tangem.features.send.api.analytics.CommonSendAnalyticEvents
|
||||
import com.tangem.features.send.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.api.subcomponents.destination.SendDestinationComponent
|
||||
import com.tangem.features.send.api.subcomponents.destination.entity.DestinationUM
|
||||
import com.tangem.features.swap.v2.api.SendWithSwapComponent
|
||||
import com.tangem.features.swap.v2.impl.amount.SwapAmountComponent
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
|
||||
|
|
|
|||
|
|
@ -45,12 +45,12 @@ import com.tangem.domain.express.models.ExpressProviderType
|
|||
import com.tangem.domain.swap.models.SwapDataModel
|
||||
import com.tangem.domain.swap.models.SwapDataTransactionModel
|
||||
import com.tangem.domain.utils.convertToSdkAmount
|
||||
import com.tangem.features.send.v2.api.entity.FeeExtraInfo
|
||||
import com.tangem.features.send.v2.api.entity.FeeItem
|
||||
import com.tangem.features.send.v2.api.entity.FeeNonce
|
||||
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationTextFieldUM
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationUM
|
||||
import com.tangem.features.send.api.entity.FeeExtraInfo
|
||||
import com.tangem.features.send.api.entity.FeeItem
|
||||
import com.tangem.features.send.api.entity.FeeNonce
|
||||
import com.tangem.features.send.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.api.subcomponents.destination.entity.DestinationTextFieldUM
|
||||
import com.tangem.features.send.api.subcomponents.destination.entity.DestinationUM
|
||||
import com.tangem.features.swap.v2.impl.R
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountFieldUM
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import io.mockk.every
|
|||
import io.mockk.mockk
|
||||
import io.mockk.slot
|
||||
import io.mockk.verify
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.math.BigDecimal
|
||||
|
||||
class SwapAmountAnalyticsSenderTest {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class SwapFromSubtitleConverterTest {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
|
|||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class SwapAmountSelectQuoteTransformerTest {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import com.tangem.domain.swap.models.SwapAmountType
|
|||
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class SwapProviderListItemConverterTest {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import com.tangem.features.swap.v2.impl.chooseprovider.entity.SwapProviderState
|
|||
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import com.google.common.truth.Truth.assertThat
|
|||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.swap.models.SwapAmountType
|
||||
import io.mockk.mockk
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
internal class AmountErrorCurrencyResolverTest {
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue