Updated on 2026-08-14

This commit is contained in:
Tangem 2025-07-03 13:04:58 +05:00
commit c3da795b23
13 changed files with 135 additions and 36 deletions

View file

@ -75,6 +75,7 @@ internal class ProxyAppRouter(
try {
block()
} catch (e: Throwable) {
Timber.e(e)
onComplete(false)
}
}

View file

@ -101,7 +101,7 @@ internal fun LazyListScope.amountFieldV2(
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
modifier = modifier
.padding(top = 48.dp, bottom = 28.dp),
) {

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M7.95,5.1C8.583,4.733 9.233,4.458 9.9,4.275C10.567,4.092 11.25,4 11.95,4C14.183,4 16.083,4.775 17.65,6.325C19.217,7.875 20,9.767 20,12V12.175L21.6,10.575L23,11.975L19,15.975L15,11.975L16.4,10.575L18,12.175V12C18,10.333 17.413,8.917 16.237,7.75C15.063,6.583 13.633,6 11.95,6C11.517,6 11.092,6.05 10.675,6.15C10.258,6.25 9.85,6.4 9.45,6.6L7.95,5.1ZM1,12.025L5,8.025L9,12.025L7.6,13.425L6,11.825V12C6,13.667 6.588,15.083 7.762,16.25C8.938,17.417 10.367,18 12.05,18C12.483,18 12.908,17.95 13.325,17.85C13.742,17.75 14.15,17.6 14.55,17.4L16.05,18.9C15.417,19.267 14.767,19.542 14.1,19.725C13.433,19.908 12.75,20 12.05,20C9.817,20 7.917,19.225 6.35,17.675C4.783,16.125 4,14.233 4,12V11.825L2.4,13.425L1,12.025Z"
android:fillColor="#1F1F1F"/>
</vector>

View file

@ -0,0 +1,9 @@
package com.tangem.features.swap.v2.api.subcomponents
/**
* Trigger swap amount change from another component.
* Different from another triggers because it takes raw string instead of BigDecimal
*/
interface SwapAmountUpdateTrigger {
suspend fun triggerUpdateAmount(amountValue: String)
}

View file

@ -0,0 +1,28 @@
package com.tangem.features.swap.v2.impl.amount
import com.tangem.features.swap.v2.api.subcomponents.SwapAmountUpdateTrigger
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import javax.inject.Inject
import javax.inject.Singleton
/**
* Trigger amount change from another component.
* Different from another triggers because it takes raw string instead of BigDecimal
*/
interface SwapAmountUpdateListener {
val updateAmountTriggerFlow: Flow<String>
}
@Singleton
internal class DefaultSwapAmountUpdateTrigger @Inject constructor() :
SwapAmountUpdateTrigger,
SwapAmountUpdateListener {
override val updateAmountTriggerFlow: Flow<String>
field = MutableSharedFlow<String>()
override suspend fun triggerUpdateAmount(amountValue: String) {
updateAmountTriggerFlow.emit(amountValue)
}
}

View file

@ -38,5 +38,6 @@ internal class SwapAmountComponent @AssistedInject constructor(
interface ModelCallback : SwapNavigationModelCallback {
fun onAmountResult(amountUM: SwapAmountUM)
fun onSeparatorClick(lastAmount: String)
}
}

View file

@ -1,11 +1,11 @@
package com.tangem.features.swap.v2.impl.amount
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.swap.models.SwapDirection
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
import com.tangem.features.swap.v2.impl.swap.SwapRoute
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
@ -27,8 +27,9 @@ internal sealed class SwapAmountComponentParams {
override val swapDirection: SwapDirection,
override val primaryCryptoCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>,
override val secondaryCryptoCurrency: CryptoCurrency?,
val title: TextReference,
val callback: SwapAmountComponent.ModelCallback,
val currentRoute: Flow<SwapRoute.Amount>,
val currentRoute: Flow<SwapAmountRoute>,
) : SwapAmountComponentParams()
data class AmountBlockParams(

View file

@ -0,0 +1,8 @@
package com.tangem.features.swap.v2.impl.amount
/**
* Common interface for routing to swap amount
*/
interface SwapAmountRoute {
val isEditMode: Boolean
}

View file

@ -2,10 +2,14 @@ package com.tangem.features.swap.v2.impl.amount.di
import com.tangem.core.decompose.di.ModelComponent
import com.tangem.core.decompose.model.Model
import com.tangem.features.swap.v2.api.subcomponents.SwapAmountUpdateTrigger
import com.tangem.features.swap.v2.impl.amount.DefaultSwapAmountUpdateTrigger
import com.tangem.features.swap.v2.impl.amount.SwapAmountUpdateListener
import com.tangem.features.swap.v2.impl.amount.model.SwapAmountModel
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
@ -17,4 +21,15 @@ internal interface SwapAmountModule {
@IntoMap
@ClassKey(SwapAmountModel::class)
fun provideSwapAmountModel(impl: SwapAmountModel): Model
}
@InstallIn(SingletonComponent::class)
@Module
internal interface SwapAmountModuleBinds {
@Binds
fun provideSwapAmountUpdateTrigger(impl: DefaultSwapAmountUpdateTrigger): SwapAmountUpdateTrigger
@Binds
fun provideSwapAmountUpdateListener(impl: DefaultSwapAmountUpdateTrigger): SwapAmountUpdateListener
}

View file

@ -8,4 +8,5 @@ internal interface SwapAmountClickIntents : AmountScreenClickIntents {
fun onExpandEditField(selectedAmountType: SwapAmountType)
fun onInfoClick()
fun onSelectTokenClick()
fun onSeparatorClick()
}

View file

@ -8,6 +8,7 @@ import com.tangem.common.ui.amountScreen.converters.MaxEnterAmountConverter
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
import com.tangem.common.ui.navigationButtons.NavigationButton
import com.tangem.common.ui.navigationButtons.NavigationUM
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
@ -32,6 +33,7 @@ import com.tangem.domain.transaction.usecase.GetAllowanceUseCase
import com.tangem.features.swap.v2.impl.R
import com.tangem.features.swap.v2.impl.amount.SwapAmountBlockComponent.SwapChooseProviderConfig
import com.tangem.features.swap.v2.impl.amount.SwapAmountComponentParams
import com.tangem.features.swap.v2.impl.amount.SwapAmountUpdateListener
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountFieldUM
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountType
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
@ -39,7 +41,6 @@ import com.tangem.features.swap.v2.impl.amount.model.converter.SwapAmountReadySt
import com.tangem.features.swap.v2.impl.amount.model.transformers.*
import com.tangem.features.swap.v2.impl.chooseprovider.SwapChooseProviderComponent
import com.tangem.features.swap.v2.impl.choosetoken.fromSupported.SwapChooseTokenNetworkListener
import com.tangem.common.ui.navigationButtons.NavigationUM
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM.Content.DifferencePercent
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -67,6 +68,7 @@ internal class SwapAmountModel @Inject constructor(
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val appRouter: AppRouter,
private val swapAlertFactory: SwapAlertFactory,
private val swapAmountUpdateListener: SwapAmountUpdateListener,
) : Model(), SwapAmountClickIntents, SwapChooseProviderComponent.ModelCallback {
private val params: SwapAmountComponentParams = paramsContainer.require()
@ -99,6 +101,7 @@ internal class SwapAmountModel @Inject constructor(
}
configAmountNavigation()
subscribeOnCryptoCurrencyStatusFlow()
subscribeOnAmountUpdateTriggerUpdates()
observeChooseSelectToken()
// todo observe balance hiding flow
}
@ -189,6 +192,14 @@ internal class SwapAmountModel @Inject constructor(
)
}
override fun onSeparatorClick() {
// todo send with swap make swap types
val amountFieldData = uiState.value.primaryAmount.amountField as? AmountState.Data
val callback = (params as? SwapAmountComponentParams.AmountParams)?.callback ?: return
callback.onSeparatorClick(lastAmount = amountFieldData?.amountTextField?.value.orEmpty())
}
private fun subscribeOnCryptoCurrencyStatusFlow() {
params.primaryCryptoCurrencyStatusFlow.onEach { primaryCurrencyStatus ->
primaryCryptoCurrencyStatus = primaryCurrencyStatus
@ -210,6 +221,16 @@ internal class SwapAmountModel @Inject constructor(
}.launchIn(modelScope)
}
private fun subscribeOnAmountUpdateTriggerUpdates() {
swapAmountUpdateListener.updateAmountTriggerFlow
.onEach {
if (uiState.value is SwapAmountUM.Content) {
onAmountValueChange(it)
}
}
.launchIn(modelScope)
}
private fun observeChooseSelectToken() {
swapChooseTokenNetworkListener.swapChooseTokenNetworkResultFlow
.onEach { currency ->

View file

@ -27,11 +27,9 @@ import androidx.constraintlayout.compose.ConstraintLayout
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.amountScreen.ui.AmountFieldV2
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.SecondaryButton
import com.tangem.core.ui.components.SpacerWMax
import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.components.atoms.text.EllipsisText
import com.tangem.core.ui.components.buttons.common.TangemButtonSize
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.extensions.conditionalCompose
@ -81,6 +79,7 @@ internal fun SwapAmountContent(
},
)
SwapAmountBlockSeparator(
onClick = clickIntents::onSeparatorClick,
modifier = Modifier
.constrainAs(middleButtonRef) {
top.linkTo(amountFromRef.bottom)
@ -93,7 +92,7 @@ internal fun SwapAmountContent(
}
@Composable
private fun SwapAmountBlockSeparator(modifier: Modifier = Modifier) {
private fun SwapAmountBlockSeparator(onClick: () -> Unit, modifier: Modifier = Modifier) {
// todo add swap type like [Swap, SendViaSwap, SendIncognito]
Row(
verticalAlignment = Alignment.CenterVertically,
@ -102,13 +101,11 @@ private fun SwapAmountBlockSeparator(modifier: Modifier = Modifier) {
.heightIn(max = 28.dp)
.clip(RoundedCornerShape(32.dp))
.background(TangemTheme.colors.background.secondary)
.padding(
vertical = 6.dp,
horizontal = 12.dp,
),
.clickable(onClick = onClick)
.padding(vertical = 6.dp, horizontal = 12.dp),
) {
Text(
text = "Convert",
text = stringResourceSafe(R.string.common_convert),
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.text.tertiary,
)
@ -180,8 +177,7 @@ private fun SwapAmountEditBlock(
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(12.dp),
modifier = modifier
.padding(vertical = 48.dp),
modifier = modifier.padding(top = 48.dp, bottom = 28.dp),
) {
if (amountFieldUM.amountField !is AmountState.Data) {
TextShimmer(
@ -263,24 +259,10 @@ private fun SwapAmountInfo(
@Composable
private fun SwapAmountInfoMain(amountFieldUM: SwapAmountFieldUM, modifier: Modifier = Modifier) {
AnimatedContent(
targetState = amountFieldUM !is SwapAmountFieldUM.Content,
targetState = amountFieldUM is SwapAmountFieldUM.Content,
modifier = modifier,
) { isContent ->
if (isContent) {
Column(
verticalArrangement = Arrangement.spacedBy(2.dp),
) {
TextShimmer(
style = TangemTheme.typography.subtitle2,
modifier = Modifier.width(56.dp),
)
TextShimmer(
style = TangemTheme.typography.caption2,
modifier = Modifier.width(72.dp),
)
}
} else {
val amountFieldUM = amountFieldUM as SwapAmountFieldUM.Content
if (isContent && amountFieldUM is SwapAmountFieldUM.Content) {
Column(
verticalArrangement = Arrangement.spacedBy(2.dp),
) {
@ -297,6 +279,19 @@ private fun SwapAmountInfoMain(amountFieldUM: SwapAmountFieldUM, modifier: Modif
ellipsis = amountFieldUM.subtitleEllipsis,
)
}
} else {
Column(
verticalArrangement = Arrangement.spacedBy(2.dp),
) {
TextShimmer(
style = TangemTheme.typography.subtitle2,
modifier = Modifier.width(56.dp),
)
TextShimmer(
style = TangemTheme.typography.caption2,
modifier = Modifier.width(72.dp),
)
}
}
}
}
@ -323,8 +318,7 @@ private fun SwapAmountInfoExtra(
SwapAmountInfoQuote(
quoteUM = amountUM.selectedQuote,
swapRateType = amountUM.swapRateType,
onSelectTokenClick =
onSelectTokenClick,
onSelectTokenClick = onSelectTokenClick,
)
}
}
@ -352,11 +346,20 @@ private fun SwapAmountInfoExtra(
@Composable
private fun AmountMaxButton(onMaxAmountClick: () -> Unit) {
SecondaryButton(
Text(
text = stringResourceSafe(R.string.send_max_amount),
size = TangemButtonSize.Small,
onClick = onMaxAmountClick,
modifier = Modifier.padding(end = 16.dp),
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.text.primary1,
modifier = Modifier
.padding(end = 16.dp)
.clip(RoundedCornerShape(16.dp))
.background(TangemTheme.colors.background.secondary)
.padding(horizontal = 12.dp, vertical = 4.dp)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = ripple(),
onClick = onMaxAmountClick,
),
)
}

View file

@ -10,6 +10,8 @@ internal object SwapAmountClickIntentsStub : SwapAmountClickIntents {
override fun onSelectTokenClick() {}
override fun onSeparatorClick() {}
override fun onAmountValueChange(value: String) {}
override fun onAmountPasteTriggerDismiss() {}