Updated on 2026-08-14
This commit is contained in:
commit
438126dc82
665 changed files with 16174 additions and 6245 deletions
|
|
@ -8,4 +8,6 @@ internal class DefaultSendFeatureToggles(
|
|||
) : SendFeatureToggles {
|
||||
override val isSendRedesignEnabled: Boolean
|
||||
get() = featureToggles.isFeatureEnabled("SEND_REDESIGN_ENABLED")
|
||||
override val isSendWithSwapEnabled: Boolean
|
||||
get() = featureToggles.isFeatureEnabled("SEND_VIA_SWAP_ENABLED")
|
||||
}
|
||||
|
|
@ -18,6 +18,11 @@ internal sealed class CommonSendRoute : Route {
|
|||
override val isEditMode: Boolean = true
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data object ConfirmSuccess : CommonSendRoute() {
|
||||
override val isEditMode: Boolean = false
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class Destination(
|
||||
override val isEditMode: Boolean,
|
||||
|
|
|
|||
|
|
@ -6,8 +6,7 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.arkivanov.decompose.extensions.compose.stack.Children
|
||||
import com.arkivanov.decompose.extensions.compose.stack.animation.slide
|
||||
import com.arkivanov.decompose.extensions.compose.stack.animation.stackAnimation
|
||||
import com.arkivanov.decompose.extensions.compose.stack.animation.*
|
||||
import com.arkivanov.decompose.router.stack.ChildStack
|
||||
import com.tangem.common.ui.navigationButtons.NavigationUM
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButtonAndIcon
|
||||
|
|
@ -15,6 +14,8 @@ import com.tangem.core.ui.decompose.ComposableContentComponent
|
|||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.send.v2.common.CommonSendRoute
|
||||
import com.tangem.features.send.v2.send.confirm.SendConfirmComponent
|
||||
import com.tangem.features.send.v2.send.success.SendConfirmSuccessComponent
|
||||
|
||||
@Composable
|
||||
internal fun SendContent(
|
||||
|
|
@ -22,22 +23,30 @@ internal fun SendContent(
|
|||
stackState: ChildStack<CommonSendRoute, ComposableContentComponent>,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.Companion
|
||||
modifier = Modifier
|
||||
.background(color = TangemTheme.colors.background.tertiary)
|
||||
.fillMaxSize()
|
||||
.imePadding()
|
||||
.systemBarsPadding(),
|
||||
horizontalAlignment = Alignment.Companion.CenterHorizontally,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
SendAppBar(navigationUM = navigationUM)
|
||||
Children(
|
||||
stack = stackState,
|
||||
animation = stackAnimation(slide()),
|
||||
animation = stackAnimation { child ->
|
||||
when (child.instance) {
|
||||
is SendConfirmSuccessComponent -> fade(minAlpha = 1.0f)
|
||||
is SendConfirmComponent -> fade()
|
||||
else -> slide()
|
||||
}
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
) {
|
||||
it.instance.Content(Modifier.weight(1f))
|
||||
}
|
||||
SendNavigationButtons(navigationUM = navigationUM)
|
||||
if (stackState.active.configuration != CommonSendRoute.ConfirmSuccess) {
|
||||
SendNavigationButtons(navigationUM = navigationUM)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,8 @@ package com.tangem.features.send.v2.di
|
|||
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.features.send.v2.DefaultSendFeatureToggles
|
||||
import com.tangem.features.send.v2.api.NFTSendComponent
|
||||
import com.tangem.features.send.v2.api.SendComponent
|
||||
import com.tangem.features.send.v2.api.SendFeatureToggles
|
||||
import com.tangem.features.send.v2.api.SendNotificationsComponent
|
||||
import com.tangem.features.send.v2.api.*
|
||||
import com.tangem.features.send.v2.entrypoint.DefaultSendEntryPointComponent
|
||||
import com.tangem.features.send.v2.send.DefaultSendComponent
|
||||
import com.tangem.features.send.v2.sendnft.DefaultNFTSendComponent
|
||||
import com.tangem.features.send.v2.subcomponents.notifications.DefaultSendNotificationsComponent
|
||||
|
|
@ -43,4 +41,10 @@ internal interface SendFeatureModuleBinds {
|
|||
fun provideNotificationComponentFactory(
|
||||
impl: DefaultSendNotificationsComponent.Factory,
|
||||
): SendNotificationsComponent.Factory
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun provideSendEntryPointComponentFactory(
|
||||
impl: DefaultSendEntryPointComponent.Factory,
|
||||
): SendEntryPointComponent.Factory
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
package com.tangem.features.send.v2.entrypoint
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.context.child
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.features.send.v2.api.SendComponent
|
||||
import com.tangem.features.send.v2.api.SendEntryPointComponent
|
||||
import com.tangem.features.send.v2.entrypoint.model.SendEntryPoint
|
||||
import com.tangem.features.send.v2.entrypoint.model.SendEntryPointModel
|
||||
import com.tangem.features.swap.v2.api.SendWithSwapComponent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class DefaultSendEntryPointComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted private val params: SendEntryPointComponent.Params,
|
||||
sendWithSwapComponentFactory: SendWithSwapComponent.Factory,
|
||||
sendComponentFactory: SendComponent.Factory,
|
||||
) : SendEntryPointComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: SendEntryPointModel = getOrCreateModel(params = params)
|
||||
|
||||
private val sendWithSwapComponent = sendWithSwapComponentFactory.create(
|
||||
context = child("sendEntrySendWithSwap"),
|
||||
params = SendWithSwapComponent.Params(
|
||||
userWalletId = params.userWalletId,
|
||||
currency = params.cryptoCurrency,
|
||||
callback = model,
|
||||
),
|
||||
)
|
||||
|
||||
private val sendComponent = sendComponentFactory.create(
|
||||
context = child("sendEntryVanillaSend"),
|
||||
params = SendComponent.Params(
|
||||
userWalletId = params.userWalletId,
|
||||
currency = params.cryptoCurrency,
|
||||
callback = model,
|
||||
),
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val sendEntryState by model.sendEntryPointState.collectAsStateWithLifecycle()
|
||||
|
||||
sendComponent.Content(modifier)
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = sendEntryState == SendEntryPoint.SendWithSwap,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
sendWithSwapComponent.Content(modifier)
|
||||
}
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : SendEntryPointComponent.Factory {
|
||||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: SendEntryPointComponent.Params,
|
||||
): DefaultSendEntryPointComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.features.send.v2.entrypoint.di
|
||||
|
||||
import com.tangem.core.decompose.di.ModelComponent
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.send.v2.entrypoint.model.SendEntryPointModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
|
||||
@Module
|
||||
@InstallIn(ModelComponent::class)
|
||||
internal interface SendEntryPointModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(SendEntryPointModel::class)
|
||||
fun provideSendEntryPointModel(model: SendEntryPointModel): Model
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
package com.tangem.features.send.v2.entrypoint.model
|
||||
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.features.send.v2.api.SendComponent
|
||||
import com.tangem.features.send.v2.api.SendEntryPointComponent
|
||||
import com.tangem.features.send.v2.subcomponents.amount.SendAmountUpdateTrigger
|
||||
import com.tangem.features.swap.v2.api.SendWithSwapComponent
|
||||
import com.tangem.features.swap.v2.api.choosetoken.SwapChooseTokenNetworkListener
|
||||
import com.tangem.features.swap.v2.api.subcomponents.SwapAmountUpdateTrigger
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveIn
|
||||
import jakarta.inject.Inject
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@ModelScoped
|
||||
internal class SendEntryPointModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
val appRouter: AppRouter,
|
||||
private val swapChooseTokenNetworkListener: SwapChooseTokenNetworkListener,
|
||||
private val sendAmountUpdateTrigger: SendAmountUpdateTrigger,
|
||||
private val swapAmountUpdateTrigger: SwapAmountUpdateTrigger,
|
||||
) : Model(), SendComponent.ModelCallback, SendWithSwapComponent.ModelCallback {
|
||||
|
||||
private val params: SendEntryPointComponent.Params = paramsContainer.require()
|
||||
|
||||
val sendEntryPointState: StateFlow<SendEntryPoint>
|
||||
field = MutableStateFlow(SendEntryPoint.SendVanilla)
|
||||
|
||||
private var swapChooseTokenListenerJobHolder = JobHolder()
|
||||
|
||||
override fun onConvertToAnotherToken(lastAmount: String) {
|
||||
appRouter.push(
|
||||
AppRoute.ChooseManagedTokens(
|
||||
userWalletId = params.userWalletId,
|
||||
initialCurrency = params.cryptoCurrency,
|
||||
source = AppRoute.ChooseManagedTokens.Source.SendViaSwap,
|
||||
),
|
||||
)
|
||||
observeChooseSelectToken(lastAmount)
|
||||
}
|
||||
|
||||
override fun onCloseSwap(lastAmount: String) {
|
||||
modelScope.launch {
|
||||
if (lastAmount.isNotBlank()) {
|
||||
sendAmountUpdateTrigger.triggerUpdateAmount(lastAmount)
|
||||
}
|
||||
triggerScreenUpdate(SendEntryPoint.SendVanilla)
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeChooseSelectToken(lastAmount: String) {
|
||||
swapChooseTokenNetworkListener.swapChooseTokenNetworkResultFlow
|
||||
.onEach { currency ->
|
||||
if (lastAmount.isNotBlank()) {
|
||||
swapAmountUpdateTrigger.triggerUpdateAmount(lastAmount)
|
||||
}
|
||||
triggerScreenUpdate(SendEntryPoint.SendWithSwap)
|
||||
}
|
||||
.launchIn(modelScope)
|
||||
.saveIn(swapChooseTokenListenerJobHolder)
|
||||
}
|
||||
|
||||
private fun triggerScreenUpdate(entry: SendEntryPoint) {
|
||||
swapChooseTokenListenerJobHolder.cancel()
|
||||
sendEntryPointState.update { entry }
|
||||
}
|
||||
}
|
||||
|
||||
enum class SendEntryPoint {
|
||||
SendVanilla,
|
||||
SendWithSwap,
|
||||
}
|
||||
|
|
@ -1,62 +1,66 @@
|
|||
package com.tangem.features.send.v2.feeselector
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Devices
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.common.ui.amountScreen.utils.getFiatString
|
||||
import com.arkivanov.decompose.extensions.compose.subscribeAsState
|
||||
import com.arkivanov.decompose.router.slot.activate
|
||||
import com.arkivanov.decompose.router.slot.childSlot
|
||||
import com.arkivanov.decompose.router.slot.dismiss
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.context.childByContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
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.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.format.bigdecimal.BigDecimalFormatConstants.EMPTY_BALANCE_SIGN
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.fee
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.core.ui.extensions.conditional
|
||||
import com.tangem.features.send.v2.api.FeeSelectorBlockComponent
|
||||
import com.tangem.features.send.v2.api.entity.FeeFiatRateUM
|
||||
import com.tangem.features.send.v2.api.entity.FeeItem
|
||||
import com.tangem.features.send.v2.api.FeeSelectorComponent
|
||||
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.v2.api.params.FeeSelectorParams
|
||||
import com.tangem.features.send.v2.feeselector.model.FeeSelectorModel
|
||||
import com.tangem.features.send.v2.impl.R
|
||||
import com.tangem.features.send.v2.feeselector.ui.FeeSelectorBlockContent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import java.math.BigDecimal
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
|
||||
internal class DefaultFeeSelectorBlockComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted params: FeeSelectorParams.FeeSelectorBlockParams,
|
||||
@Assisted private val params: FeeSelectorParams.FeeSelectorBlockParams,
|
||||
@Assisted onResult: (feeSelectorUM: FeeSelectorUM) -> Unit,
|
||||
private val feeSelectorComponentFactory: FeeSelectorComponent.Factory,
|
||||
) : FeeSelectorBlockComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: FeeSelectorModel = getOrCreateModel(params = params)
|
||||
|
||||
private val bottomSheetSlot = childSlot(
|
||||
source = model.feeSelectorBottomSheet,
|
||||
serializer = Unit.serializer(),
|
||||
handleBackButton = false,
|
||||
childFactory = { _, componentContext ->
|
||||
feeSelectorComponentFactory.create(
|
||||
context = childByContext(componentContext),
|
||||
params = FeeSelectorParams.FeeSelectorDetailsParams(
|
||||
state = model.uiState.value,
|
||||
onLoadFee = params.onLoadFee,
|
||||
feeCryptoCurrencyStatus = params.feeCryptoCurrencyStatus,
|
||||
cryptoCurrencyStatus = params.cryptoCurrencyStatus,
|
||||
callback = model,
|
||||
suggestedFeeState = FeeSelectorParams.SuggestedFeeState.None,
|
||||
feeDisplaySource = FeeSelectorParams.FeeDisplaySource.Screen,
|
||||
),
|
||||
onDismiss = {
|
||||
model.feeSelectorBottomSheet.dismiss()
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
init {
|
||||
model.uiState
|
||||
.onEach(params.callback::onFeeResult)
|
||||
.onEach(onResult)
|
||||
.launchIn(componentScope)
|
||||
}
|
||||
|
||||
|
|
@ -67,7 +71,18 @@ internal class DefaultFeeSelectorBlockComponent @AssistedInject constructor(
|
|||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
FeeSelectorBlockContent(modifier = modifier, state = state)
|
||||
val bottomSheet by bottomSheetSlot.subscribeAsState()
|
||||
|
||||
FeeSelectorBlockContent(
|
||||
state = state,
|
||||
modifier = modifier
|
||||
.conditional(params.feeDisplaySource == FeeSelectorParams.FeeDisplaySource.Screen) {
|
||||
Modifier.clickable {
|
||||
model.feeSelectorBottomSheet.activate(Unit)
|
||||
}
|
||||
},
|
||||
)
|
||||
bottomSheet.child?.instance?.BottomSheet()
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
|
|
@ -75,121 +90,7 @@ internal class DefaultFeeSelectorBlockComponent @AssistedInject constructor(
|
|||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: FeeSelectorParams.FeeSelectorBlockParams,
|
||||
onResult: (feeSelectorUM: FeeSelectorUM) -> Unit,
|
||||
): DefaultFeeSelectorBlockComponent
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FeeSelectorBlockContent(state: FeeSelectorUM, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.background(TangemTheme.colors.background.action)
|
||||
.padding(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(24.dp),
|
||||
painter = painterResource(R.drawable.ic_fee_new_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.accent,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(start = TangemTheme.dimens.spacing4),
|
||||
text = stringResourceSafe(R.string.common_network_fee_title),
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.padding(start = TangemTheme.dimens.spacing6)
|
||||
.size(TangemTheme.dimens.size16),
|
||||
painter = painterResource(id = R.drawable.ic_token_info_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
)
|
||||
SpacerWMax()
|
||||
when (state) {
|
||||
is FeeSelectorUM.Content -> FeeContent(state)
|
||||
is FeeSelectorUM.Loading -> FeeLoading()
|
||||
is FeeSelectorUM.Error -> FeeError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RowScope.FeeError() {
|
||||
Text(
|
||||
text = EMPTY_BALANCE_SIGN,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
style = TangemTheme.typography.body2,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RowScope.FeeLoading() {
|
||||
TextShimmer(
|
||||
radius = TangemTheme.dimens.radius3,
|
||||
style = TangemTheme.typography.body1,
|
||||
modifier = Modifier.width(width = TangemTheme.dimens.size90),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RowScope.FeeContent(state: FeeSelectorUM.Content) {
|
||||
val fiatRate = state.feeFiatRateUM
|
||||
EllipsisText(
|
||||
text = if (fiatRate != null) {
|
||||
getFiatString(
|
||||
value = state.selectedFeeItem.fee.amount.value,
|
||||
rate = fiatRate.rate,
|
||||
appCurrency = fiatRate.appCurrency,
|
||||
approximate = state.isFeeApproximate,
|
||||
)
|
||||
} else {
|
||||
state.selectedFeeItem.fee.amount.value.format {
|
||||
crypto(
|
||||
symbol = state.selectedFeeItem.fee.amount.currencySymbol,
|
||||
decimals = state.selectedFeeItem.fee.amount.decimals,
|
||||
).fee(canBeLower = state.isFeeApproximate)
|
||||
}
|
||||
},
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
textAlign = TextAlign.End,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(start = TangemTheme.dimens.spacing4),
|
||||
)
|
||||
Icon(
|
||||
modifier = Modifier.size(width = 18.dp, height = 24.dp),
|
||||
painter = painterResource(id = R.drawable.ic_select_18_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, device = Devices.PIXEL_7_PRO)
|
||||
@Preview(showBackground = true, device = Devices.PIXEL_7_PRO, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun FeeSelectorBlockContent_Preview() {
|
||||
TangemThemePreview {
|
||||
val feeItem = FeeItem.Market(
|
||||
Fee.Common(amount = Amount(value = BigDecimal("0.0002876"), blockchain = Blockchain.Ethereum)),
|
||||
)
|
||||
FeeSelectorBlockContent(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
state = FeeSelectorUM.Content(
|
||||
feeItems = persistentListOf(feeItem),
|
||||
selectedFeeItem = feeItem,
|
||||
isFeeApproximate = false,
|
||||
feeFiatRateUM = FeeFiatRateUM(
|
||||
rate = BigDecimal("2500"),
|
||||
appCurrency = AppCurrency.Default,
|
||||
),
|
||||
displayNonceInput = false,
|
||||
nonce = null,
|
||||
onNonceChange = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -16,18 +16,24 @@ import dagger.assisted.AssistedInject
|
|||
internal class DefaultFeeSelectorComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted private val params: FeeSelectorParams.FeeSelectorDetailsParams,
|
||||
@Assisted private val onDismiss: () -> Unit,
|
||||
) : FeeSelectorComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: FeeSelectorModel = getOrCreateModel(params = params)
|
||||
|
||||
override fun dismiss() {
|
||||
model.dismiss()
|
||||
onDismiss()
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun BottomSheet() {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
FeeSelectorModalBottomSheet(onDismiss = ::dismiss, state = state, feeSelectorIntents = model)
|
||||
FeeSelectorModalBottomSheet(
|
||||
onDismiss = ::dismiss,
|
||||
state = state,
|
||||
feeSelectorIntents = model,
|
||||
feeDisplaySource = params.feeDisplaySource,
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
|
|
@ -35,6 +41,7 @@ internal class DefaultFeeSelectorComponent @AssistedInject constructor(
|
|||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: FeeSelectorParams.FeeSelectorDetailsParams,
|
||||
onDismiss: () -> Unit,
|
||||
): DefaultFeeSelectorComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.features.send.v2.feeselector
|
||||
|
||||
import com.tangem.features.send.v2.api.feeselector.FeeSelectorReloadData
|
||||
import com.tangem.features.send.v2.api.feeselector.FeeSelectorReloadListener
|
||||
import com.tangem.features.send.v2.api.feeselector.FeeSelectorReloadTrigger
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
internal class DefaultFeeSelectorReload @Inject constructor() : FeeSelectorReloadListener, FeeSelectorReloadTrigger {
|
||||
private val _reloadTriggerFlow = MutableSharedFlow<FeeSelectorReloadData>()
|
||||
override val reloadTriggerFlow: Flow<FeeSelectorReloadData> = _reloadTriggerFlow.asSharedFlow()
|
||||
|
||||
override suspend fun triggerUpdate(data: FeeSelectorReloadData) {
|
||||
_reloadTriggerFlow.emit(data)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,8 +2,11 @@ package com.tangem.features.send.v2.feeselector.di
|
|||
|
||||
import com.tangem.features.send.v2.api.FeeSelectorBlockComponent
|
||||
import com.tangem.features.send.v2.api.FeeSelectorComponent
|
||||
import com.tangem.features.send.v2.api.feeselector.FeeSelectorReloadListener
|
||||
import com.tangem.features.send.v2.api.feeselector.FeeSelectorReloadTrigger
|
||||
import com.tangem.features.send.v2.feeselector.DefaultFeeSelectorBlockComponent
|
||||
import com.tangem.features.send.v2.feeselector.DefaultFeeSelectorComponent
|
||||
import com.tangem.features.send.v2.feeselector.DefaultFeeSelectorReload
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -21,4 +24,12 @@ internal interface FeeSelectorFeatureModule {
|
|||
@Binds
|
||||
@Singleton
|
||||
fun bindBlockComponentFactory(factory: DefaultFeeSelectorBlockComponent.Factory): FeeSelectorBlockComponent.Factory
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun provideFeeSelectorReloadTrigger(impl: DefaultFeeSelectorReload): FeeSelectorReloadTrigger
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun provideFeeSelectorReloadListener(impl: DefaultFeeSelectorReload): FeeSelectorReloadListener
|
||||
}
|
||||
|
|
@ -5,13 +5,13 @@ import com.tangem.features.send.v2.api.entity.FeeItem
|
|||
internal interface FeeSelectorIntents {
|
||||
fun onFeeItemSelected(feeItem: FeeItem)
|
||||
fun onCustomFeeValueChange(index: Int, value: String)
|
||||
fun onCustomFeeNextClick()
|
||||
fun onNonceChange(value: String)
|
||||
fun onDoneClick()
|
||||
}
|
||||
|
||||
internal class StubFeeSelectorIntents : FeeSelectorIntents {
|
||||
override fun onFeeItemSelected(feeItem: FeeItem) {}
|
||||
override fun onCustomFeeValueChange(index: Int, value: String) {}
|
||||
override fun onCustomFeeNextClick() {}
|
||||
override fun onNonceChange(value: String) {}
|
||||
override fun onDoneClick() {}
|
||||
}
|
||||
|
|
@ -2,24 +2,27 @@ package com.tangem.features.send.v2.feeselector.model
|
|||
|
||||
import androidx.compose.runtime.Stable
|
||||
import arrow.core.getOrElse
|
||||
import com.arkivanov.decompose.router.slot.SlotNavigation
|
||||
import com.arkivanov.decompose.router.slot.dismiss
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.transaction.usecase.IsFeeApproximateUseCase
|
||||
import com.tangem.features.send.v2.api.callbacks.FeeSelectorModelCallback
|
||||
import com.tangem.features.send.v2.api.entity.FeeItem
|
||||
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.v2.api.feeselector.FeeSelectorReloadListener
|
||||
import com.tangem.features.send.v2.api.params.FeeSelectorParams
|
||||
import com.tangem.features.send.v2.feeselector.model.transformers.FeeItemSelectedTransformer
|
||||
import com.tangem.features.send.v2.feeselector.model.transformers.FeeSelectorErrorTransformer
|
||||
import com.tangem.features.send.v2.feeselector.model.transformers.FeeSelectorLoadedTransformer
|
||||
import com.tangem.features.send.v2.feeselector.model.transformers.*
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.transformer.update
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -27,29 +30,39 @@ import javax.inject.Inject
|
|||
@ModelScoped
|
||||
internal class FeeSelectorModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
private val feeSelectorReloadListener: FeeSelectorReloadListener,
|
||||
private val isFeeApproximateUseCase: IsFeeApproximateUseCase,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val router: Router,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
) : Model(), FeeSelectorIntents {
|
||||
) : Model(), FeeSelectorIntents, FeeSelectorModelCallback {
|
||||
|
||||
private val params = paramsContainer.require<FeeSelectorParams>()
|
||||
private var appCurrency: AppCurrency = AppCurrency.Default
|
||||
|
||||
val feeSelectorBottomSheet = SlotNavigation<Unit>()
|
||||
|
||||
val uiState: StateFlow<FeeSelectorUM>
|
||||
field = MutableStateFlow<FeeSelectorUM>(params.state)
|
||||
|
||||
init {
|
||||
initAppCurrency()
|
||||
loadFee()
|
||||
listenReloadTrigger()
|
||||
}
|
||||
|
||||
fun updateState(feeSelectorUM: FeeSelectorUM) {
|
||||
uiState.value = feeSelectorUM
|
||||
}
|
||||
|
||||
fun dismiss() {
|
||||
router.pop()
|
||||
private fun listenReloadTrigger() {
|
||||
feeSelectorReloadListener.reloadTriggerFlow
|
||||
.onEach { data ->
|
||||
if (data.removeSuggestedFee) {
|
||||
uiState.update(FeeSelectorRemoveSuggestedTransformer)
|
||||
}
|
||||
loadFee()
|
||||
}
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun initAppCurrency() {
|
||||
|
|
@ -59,6 +72,7 @@ internal class FeeSelectorModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun loadFee() {
|
||||
uiState.update(FeeSelectorLoadingTransformers)
|
||||
modelScope.launch {
|
||||
params.onLoadFee()
|
||||
.fold(
|
||||
|
|
@ -67,6 +81,7 @@ internal class FeeSelectorModel @Inject constructor(
|
|||
uiState.update(
|
||||
FeeSelectorLoadedTransformer(
|
||||
cryptoCurrencyStatus = params.cryptoCurrencyStatus,
|
||||
feeCryptoCurrencyStatus = params.feeCryptoCurrencyStatus,
|
||||
appCurrency = appCurrency,
|
||||
fees = fee,
|
||||
suggestedFeeState = params.suggestedFeeState,
|
||||
|
|
@ -80,7 +95,7 @@ internal class FeeSelectorModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun isFeeApproximate(amountType: AmountType): Boolean {
|
||||
val networkId = params.cryptoCurrencyStatus.currency.network.id
|
||||
val networkId = params.feeCryptoCurrencyStatus.currency.network.id
|
||||
return isFeeApproximateUseCase(networkId = networkId, amountType = amountType)
|
||||
}
|
||||
|
||||
|
|
@ -89,15 +104,27 @@ internal class FeeSelectorModel @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onCustomFeeValueChange(index: Int, value: String) {
|
||||
// TODO: [REDACTED_JIRA]
|
||||
uiState.update(
|
||||
FeeSelectorCustomValueChangedTransformer(
|
||||
index = index,
|
||||
value = value,
|
||||
intents = this,
|
||||
appCurrency = appCurrency,
|
||||
feeCryptoCurrencyStatus = params.feeCryptoCurrencyStatus,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override fun onCustomFeeNextClick() {
|
||||
// TODO: [REDACTED_JIRA]
|
||||
override fun onNonceChange(value: String) {
|
||||
uiState.update(FeeSelectorNonceChangeTransformer(value = value))
|
||||
}
|
||||
|
||||
override fun onDoneClick() {
|
||||
params.callback.onFeeResult(uiState.value)
|
||||
dismiss()
|
||||
(params as? FeeSelectorParams.FeeSelectorDetailsParams)?.callback?.onFeeResult(uiState.value)
|
||||
}
|
||||
|
||||
override fun onFeeResult(feeSelectorUM: FeeSelectorUM) {
|
||||
uiState.value = feeSelectorUM
|
||||
feeSelectorBottomSheet.dismiss()
|
||||
}
|
||||
}
|
||||
|
|
@ -4,8 +4,8 @@ import com.tangem.blockchain.common.transaction.Fee
|
|||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.send.v2.api.params.FeeSelectorParams
|
||||
import com.tangem.features.send.v2.api.entity.FeeItem
|
||||
import com.tangem.features.send.v2.api.params.FeeSelectorParams
|
||||
import com.tangem.features.send.v2.feeselector.model.FeeSelectorIntents
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
|
@ -17,7 +17,7 @@ internal class FeeItemConverter(
|
|||
private val feeSelectorIntents: FeeSelectorIntents,
|
||||
private val appCurrency: AppCurrency,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
) : Converter<TransactionFee, ImmutableList<FeeItem>> {
|
||||
) : Converter<FeeItemConverter.Input, ImmutableList<FeeItem>> {
|
||||
|
||||
private val customFeeFieldConverter = FeeSelectorCustomFieldConverter(
|
||||
feeSelectorIntents = feeSelectorIntents,
|
||||
|
|
@ -26,7 +26,7 @@ internal class FeeItemConverter(
|
|||
normalFee = normalFee,
|
||||
)
|
||||
|
||||
override fun convert(value: TransactionFee): ImmutableList<FeeItem> {
|
||||
override fun convert(value: Input): ImmutableList<FeeItem> {
|
||||
val fees = mutableListOf<FeeItem>()
|
||||
|
||||
when (suggestedFeeState) {
|
||||
|
|
@ -38,26 +38,33 @@ internal class FeeItemConverter(
|
|||
),
|
||||
)
|
||||
}
|
||||
when (value) {
|
||||
when (value.transactionFee) {
|
||||
is TransactionFee.Choosable -> {
|
||||
fees.add(FeeItem.Slow(fee = value.minimum))
|
||||
fees.add(FeeItem.Market(fee = value.normal))
|
||||
fees.add(FeeItem.Fast(fee = value.priority))
|
||||
fees.add(FeeItem.Slow(fee = value.transactionFee.minimum))
|
||||
fees.add(FeeItem.Market(fee = value.transactionFee.normal))
|
||||
fees.add(FeeItem.Fast(fee = value.transactionFee.priority))
|
||||
}
|
||||
is TransactionFee.Single -> {
|
||||
fees.add(FeeItem.Market(fee = value.normal))
|
||||
fees.add(FeeItem.Market(fee = value.transactionFee.normal))
|
||||
}
|
||||
}
|
||||
val customFeeFields = customFeeFieldConverter.convert(normalFee)
|
||||
if (customFeeFields.isNotEmpty()) {
|
||||
fees.add(
|
||||
FeeItem.Custom(
|
||||
fee = customFeeFieldConverter.convertBack(customFeeFields),
|
||||
customValues = customFeeFields,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
val customFee = value.customFee ?: constructCustomFee()
|
||||
customFee?.let(fees::add)
|
||||
|
||||
return fees.toImmutableList()
|
||||
}
|
||||
|
||||
private fun constructCustomFee(): FeeItem.Custom? {
|
||||
val customFeeFields = customFeeFieldConverter.convert(normalFee)
|
||||
|
||||
if (customFeeFields.isEmpty()) return null
|
||||
|
||||
return FeeItem.Custom(
|
||||
fee = customFeeFieldConverter.convertBack(customFeeFields),
|
||||
customValues = customFeeFields,
|
||||
)
|
||||
}
|
||||
|
||||
data class Input(val transactionFee: TransactionFee, val customFee: FeeItem.Custom?)
|
||||
}
|
||||
|
|
@ -5,12 +5,12 @@ import com.tangem.blockchain.common.transaction.TransactionFee
|
|||
import com.tangem.core.ui.utils.parseToBigDecimal
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.send.v2.api.entity.CustomFeeFieldUM
|
||||
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.v2.feeselector.model.FeeSelectorIntents
|
||||
import com.tangem.features.send.v2.subcomponents.fee.model.converters.custom.bitcoin.BitcoinCustomFeeConverter
|
||||
import com.tangem.features.send.v2.subcomponents.fee.model.converters.custom.ethereum.EthereumCustomFeeConverter
|
||||
import com.tangem.features.send.v2.subcomponents.fee.model.converters.custom.kaspa.KaspaCustomFeeConverter
|
||||
import com.tangem.features.send.v2.api.entity.CustomFeeFieldUM
|
||||
import com.tangem.features.send.v2.feeselector.model.FeeSelectorIntents
|
||||
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeSelectorUM
|
||||
import com.tangem.utils.converter.TwoWayConverter
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
|
@ -25,7 +25,7 @@ internal class FeeSelectorCustomFieldConverter(
|
|||
private val ethereumCustomFeeConverter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
EthereumCustomFeeConverter(
|
||||
onCustomFeeValueChange = feeSelectorIntents::onCustomFeeValueChange,
|
||||
onNextClick = feeSelectorIntents::onCustomFeeNextClick,
|
||||
onNextClick = null,
|
||||
appCurrency = appCurrency,
|
||||
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
|
||||
)
|
||||
|
|
@ -34,7 +34,7 @@ internal class FeeSelectorCustomFieldConverter(
|
|||
private val bitcoinCustomFeeConverter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
BitcoinCustomFeeConverter(
|
||||
onCustomFeeValueChange = feeSelectorIntents::onCustomFeeValueChange,
|
||||
onNextClick = feeSelectorIntents::onCustomFeeNextClick,
|
||||
onNextClick = null,
|
||||
appCurrency = appCurrency,
|
||||
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
|
||||
)
|
||||
|
|
@ -77,38 +77,43 @@ internal class FeeSelectorCustomFieldConverter(
|
|||
}
|
||||
}
|
||||
|
||||
fun onValueChange(feeSelectorState: FeeSelectorUM.Content, index: Int, value: String) =
|
||||
when (val fee = feeSelectorState.fees.normal) {
|
||||
is Fee.Ethereum -> ethereumCustomFeeConverter.onValueChange(
|
||||
feeValue = fee,
|
||||
customValues = feeSelectorState.customValues,
|
||||
index = index,
|
||||
value = value,
|
||||
)
|
||||
is Fee.Bitcoin -> bitcoinCustomFeeConverter.onValueChange(
|
||||
customValues = feeSelectorState.customValues,
|
||||
index = index,
|
||||
value = value,
|
||||
txSize = fee.txSize,
|
||||
)
|
||||
is Fee.Kaspa -> kaspaCustomFeeConverter.onValueChange(
|
||||
customValues = feeSelectorState.customValues,
|
||||
index = index,
|
||||
value = value,
|
||||
)
|
||||
else -> feeSelectorState.customValues
|
||||
}
|
||||
|
||||
fun tryAutoFixValue(feeSelectorState: FeeSelectorUM.Content) = when (feeSelectorState.fees) {
|
||||
is TransactionFee.Choosable -> feeSelectorState.fees.minimum
|
||||
is TransactionFee.Single -> feeSelectorState.fees.normal
|
||||
}.let {
|
||||
when (it) {
|
||||
is Fee.Kaspa -> kaspaCustomFeeConverter.tryAutoFixValue(
|
||||
minimumFee = it,
|
||||
customValues = feeSelectorState.customValues,
|
||||
)
|
||||
else -> feeSelectorState.customValues
|
||||
}
|
||||
fun onValueChange(
|
||||
feeSelectorState: FeeSelectorUM.Content,
|
||||
customValues: ImmutableList<CustomFeeFieldUM>,
|
||||
index: Int,
|
||||
value: String,
|
||||
) = when (val fee = feeSelectorState.fees.normal) {
|
||||
is Fee.Ethereum -> ethereumCustomFeeConverter.onValueChange(
|
||||
feeValue = fee,
|
||||
customValues = customValues,
|
||||
index = index,
|
||||
value = value,
|
||||
)
|
||||
is Fee.Bitcoin -> bitcoinCustomFeeConverter.onValueChange(
|
||||
customValues = customValues,
|
||||
index = index,
|
||||
value = value,
|
||||
txSize = fee.txSize,
|
||||
)
|
||||
is Fee.Kaspa -> kaspaCustomFeeConverter.onValueChange(
|
||||
customValues = customValues,
|
||||
index = index,
|
||||
value = value,
|
||||
)
|
||||
else -> customValues
|
||||
}
|
||||
|
||||
fun tryAutoFixValue(feeSelectorState: FeeSelectorUM.Content, customValues: ImmutableList<CustomFeeFieldUM>) =
|
||||
when (val fees = feeSelectorState.fees) {
|
||||
is TransactionFee.Choosable -> fees.minimum
|
||||
is TransactionFee.Single -> fees.normal
|
||||
}.let {
|
||||
when (it) {
|
||||
is Fee.Kaspa -> kaspaCustomFeeConverter.tryAutoFixValue(
|
||||
minimumFee = it,
|
||||
customValues = customValues,
|
||||
)
|
||||
else -> customValues
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package com.tangem.features.send.v2.feeselector.model.transformers
|
||||
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.send.v2.api.entity.FeeItem
|
||||
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.v2.feeselector.model.FeeSelectorIntents
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
internal class FeeSelectorCustomValueChangedTransformer(
|
||||
private val index: Int,
|
||||
private val value: String,
|
||||
private val intents: FeeSelectorIntents,
|
||||
private val appCurrency: AppCurrency,
|
||||
private val feeCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
) : Transformer<FeeSelectorUM> {
|
||||
|
||||
override fun transform(prevState: FeeSelectorUM): FeeSelectorUM {
|
||||
val state = prevState as? FeeSelectorUM.Content ?: return prevState
|
||||
val customFee = state.feeItems.filterIsInstance<FeeItem.Custom>().firstOrNull() ?: return prevState
|
||||
val customFeeConverter = FeeSelectorCustomFieldConverter(
|
||||
feeSelectorIntents = intents,
|
||||
appCurrency = appCurrency,
|
||||
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
|
||||
normalFee = state.selectedFeeItem.fee,
|
||||
)
|
||||
val updatedCustomValues = customFeeConverter.onValueChange(state, customFee.customValues, index, value)
|
||||
val newCustomFee = customFee.copy(
|
||||
fee = customFeeConverter.convertBack(updatedCustomValues),
|
||||
customValues = updatedCustomValues,
|
||||
)
|
||||
return state.copy(
|
||||
feeItems = state.feeItems.map { if (it is FeeItem.Custom) newCustomFee else it }.toImmutableList(),
|
||||
selectedFeeItem = newCustomFee,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +1,21 @@
|
|||
package com.tangem.features.send.v2.feeselector.model.transformers
|
||||
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.send.v2.api.entity.FeeFiatRateUM
|
||||
import com.tangem.features.send.v2.api.entity.FeeItem
|
||||
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.v2.api.entity.*
|
||||
import com.tangem.features.send.v2.api.params.FeeSelectorParams
|
||||
import com.tangem.features.send.v2.feeselector.model.FeeSelectorIntents
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isTron
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class FeeSelectorLoadedTransformer(
|
||||
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
private val feeCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
private val appCurrency: AppCurrency,
|
||||
private val fees: TransactionFee,
|
||||
private val suggestedFeeState: FeeSelectorParams.SuggestedFeeState,
|
||||
|
|
@ -26,32 +28,50 @@ internal class FeeSelectorLoadedTransformer(
|
|||
normalFee = fees.normal,
|
||||
feeSelectorIntents = feeSelectorIntents,
|
||||
appCurrency = appCurrency,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
cryptoCurrencyStatus = feeCryptoCurrencyStatus,
|
||||
)
|
||||
|
||||
override fun transform(prevState: FeeSelectorUM): FeeSelectorUM {
|
||||
val feeItems: ImmutableList<FeeItem> = feeItemsConverter.convert(fees)
|
||||
val prevCustomFee = if (prevState is FeeSelectorUM.Content) {
|
||||
prevState.feeItems.find { it is FeeItem.Custom } as? FeeItem.Custom
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val feeItems: ImmutableList<FeeItem> = feeItemsConverter.convert(FeeItemConverter.Input(fees, prevCustomFee))
|
||||
|
||||
val selectedFee = when (prevState) {
|
||||
is FeeSelectorUM.Content -> feeItems.first { it.isSame(prevState.selectedFeeItem) }
|
||||
is FeeSelectorUM.Content -> feeItems.first { it.isSameClass(prevState.selectedFeeItem) }
|
||||
is FeeSelectorUM.Error,
|
||||
FeeSelectorUM.Loading,
|
||||
-> feeItems.find { it is FeeItem.Suggested } ?: feeItems.first { it is FeeItem.Market }
|
||||
}
|
||||
|
||||
val nonce = ((prevState as? FeeSelectorUM.Content)?.feeNonce as? FeeNonce.Nonce)?.nonce
|
||||
|
||||
return FeeSelectorUM.Content(
|
||||
fees = fees,
|
||||
feeItems = feeItems,
|
||||
selectedFeeItem = selectedFee,
|
||||
isFeeApproximate = isFeeApproximate,
|
||||
feeFiatRateUM = cryptoCurrencyStatus.value.fiatRate?.let { rate ->
|
||||
feeExtraInfo = FeeExtraInfo(
|
||||
isFeeApproximate = isFeeApproximate,
|
||||
isFeeConvertibleToFiat = feeCryptoCurrencyStatus.currency.network.hasFiatFeeRate,
|
||||
isTronToken = cryptoCurrencyStatus.currency is CryptoCurrency.Token &&
|
||||
isTron(cryptoCurrencyStatus.currency.network.rawId),
|
||||
),
|
||||
feeFiatRateUM = feeCryptoCurrencyStatus.value.fiatRate?.let { rate ->
|
||||
FeeFiatRateUM(
|
||||
rate = rate,
|
||||
appCurrency = appCurrency,
|
||||
)
|
||||
},
|
||||
displayNonceInput = false,
|
||||
nonce = null,
|
||||
onNonceChange = {},
|
||||
feeNonce = if (fees.normal is Fee.Ethereum) {
|
||||
FeeNonce.Nonce(
|
||||
nonce = nonce,
|
||||
onNonceChange = feeSelectorIntents::onNonceChange,
|
||||
)
|
||||
} else {
|
||||
FeeNonce.None
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.features.send.v2.feeselector.model.transformers
|
||||
|
||||
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal object FeeSelectorLoadingTransformers : Transformer<FeeSelectorUM> {
|
||||
|
||||
override fun transform(prevState: FeeSelectorUM): FeeSelectorUM {
|
||||
return FeeSelectorUM.Loading
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.features.send.v2.feeselector.model.transformers
|
||||
|
||||
import com.tangem.features.send.v2.api.entity.FeeNonce
|
||||
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal class FeeSelectorNonceChangeTransformer(
|
||||
private val value: String,
|
||||
) : Transformer<FeeSelectorUM> {
|
||||
|
||||
override fun transform(prevState: FeeSelectorUM): FeeSelectorUM {
|
||||
val state = prevState as? FeeSelectorUM.Content ?: return prevState
|
||||
val feeNonce = state.feeNonce as? FeeNonce.Nonce ?: return prevState
|
||||
if (value.isEmpty()) {
|
||||
return state.copy(feeNonce = feeNonce.copy(null))
|
||||
}
|
||||
|
||||
val nonce = value.toBigIntegerOrNull() ?: return prevState
|
||||
|
||||
return state.copy(feeNonce = feeNonce.copy(nonce = nonce))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.features.send.v2.feeselector.model.transformers
|
||||
|
||||
import com.tangem.features.send.v2.api.entity.FeeItem
|
||||
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
internal object FeeSelectorRemoveSuggestedTransformer : Transformer<FeeSelectorUM> {
|
||||
|
||||
override fun transform(prevState: FeeSelectorUM): FeeSelectorUM {
|
||||
val state = prevState as? FeeSelectorUM.Content ?: return prevState
|
||||
val newFeeItems = state.feeItems.filterNot { item -> item is FeeItem.Suggested }
|
||||
val newSelectedFee = if (state.selectedFeeItem is FeeItem.Suggested) {
|
||||
state.feeItems.first { item -> item is FeeItem.Market }
|
||||
} else {
|
||||
state.selectedFeeItem
|
||||
}
|
||||
|
||||
return state.copy(
|
||||
feeItems = newFeeItems.toImmutableList(),
|
||||
selectedFeeItem = newSelectedFee,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,196 @@
|
|||
package com.tangem.features.send.v2.feeselector.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Devices
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.common.ui.amountScreen.utils.getFiatString
|
||||
import com.tangem.core.ui.components.TextShimmer
|
||||
import com.tangem.core.ui.components.atoms.text.EllipsisText
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.format.bigdecimal.BigDecimalFormatConstants.EMPTY_BALANCE_SIGN
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.fee
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
import com.tangem.features.send.v2.api.entity.*
|
||||
import com.tangem.features.send.v2.impl.R
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Composable
|
||||
internal fun FeeSelectorBlockContent(state: FeeSelectorUM, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.background(TangemTheme.colors.background.action)
|
||||
.padding(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(24.dp),
|
||||
painter = painterResource(R.drawable.ic_fee_new_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.accent,
|
||||
)
|
||||
FeeSelectorDescription(state = state)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FeeSelectorDescription(state: FeeSelectorUM, modifier: Modifier = Modifier) {
|
||||
Row(modifier = modifier, horizontalArrangement = Arrangement.SpaceBetween) {
|
||||
FeeSelectorStaticPart(modifier = Modifier.weight(1f))
|
||||
when (state) {
|
||||
is FeeSelectorUM.Content -> FeeContent(state)
|
||||
is FeeSelectorUM.Loading -> FeeLoading()
|
||||
is FeeSelectorUM.Error -> FeeError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FeeSelectorStaticPart(modifier: Modifier = Modifier) {
|
||||
Row(modifier = modifier, verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(start = TangemTheme.dimens.spacing4)
|
||||
.weight(1f, fill = false),
|
||||
text = stringResourceSafe(R.string.common_network_fee_title),
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.padding(start = TangemTheme.dimens.spacing6)
|
||||
.size(TangemTheme.dimens.size16),
|
||||
painter = painterResource(id = R.drawable.ic_token_info_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FeeError() {
|
||||
Text(
|
||||
text = EMPTY_BALANCE_SIGN,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
style = TangemTheme.typography.body2,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FeeLoading() {
|
||||
TextShimmer(
|
||||
radius = TangemTheme.dimens.radius3,
|
||||
style = TangemTheme.typography.body1,
|
||||
modifier = Modifier.width(width = TangemTheme.dimens.size90),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FeeContent(state: FeeSelectorUM.Content, modifier: Modifier = Modifier) {
|
||||
val fiatRate = state.feeFiatRateUM
|
||||
Row(modifier = modifier, verticalAlignment = Alignment.CenterVertically) {
|
||||
EllipsisText(
|
||||
text = if (fiatRate != null) {
|
||||
getFiatString(
|
||||
value = state.selectedFeeItem.fee.amount.value,
|
||||
rate = fiatRate.rate,
|
||||
appCurrency = fiatRate.appCurrency,
|
||||
approximate = state.feeExtraInfo.isFeeApproximate,
|
||||
)
|
||||
} else {
|
||||
state.selectedFeeItem.fee.amount.value.format {
|
||||
crypto(
|
||||
symbol = state.selectedFeeItem.fee.amount.currencySymbol,
|
||||
decimals = state.selectedFeeItem.fee.amount.decimals,
|
||||
).fee(canBeLower = state.feeExtraInfo.isFeeApproximate)
|
||||
}
|
||||
},
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
textAlign = TextAlign.End,
|
||||
modifier = Modifier.padding(start = TangemTheme.dimens.spacing4),
|
||||
)
|
||||
Icon(
|
||||
modifier = Modifier.size(width = 18.dp, height = 24.dp),
|
||||
painter = painterResource(id = R.drawable.ic_select_18_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, device = Devices.PIXEL_7_PRO)
|
||||
@Preview(showBackground = true, device = Devices.PIXEL_7_PRO, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun FeeSelectorBlockContent_Preview(@PreviewParameter(FeeSelectorUMProvider::class) state: FeeSelectorUM) {
|
||||
TangemThemePreview {
|
||||
FeeSelectorBlockContent(modifier = Modifier.fillMaxWidth(), state = state)
|
||||
}
|
||||
}
|
||||
|
||||
private class FeeSelectorUMProvider : PreviewParameterProvider<FeeSelectorUM> {
|
||||
private val maxFeeItem = FeeItem.Market(
|
||||
fee = Fee.Common(amount = Amount(value = BigDecimal("100000000"), blockchain = Blockchain.Ethereum)),
|
||||
)
|
||||
private val lowFeeItem =
|
||||
FeeItem.Market(Fee.Common(amount = Amount(value = BigDecimal("0.0002876"), blockchain = Blockchain.Ethereum)))
|
||||
|
||||
override val values: Sequence<FeeSelectorUM> = sequenceOf(
|
||||
FeeSelectorUM.Content(
|
||||
feeItems = persistentListOf(lowFeeItem),
|
||||
selectedFeeItem = lowFeeItem,
|
||||
feeExtraInfo = FeeExtraInfo(
|
||||
isFeeApproximate = false,
|
||||
isFeeConvertibleToFiat = true,
|
||||
isTronToken = false,
|
||||
),
|
||||
feeNonce = FeeNonce.None,
|
||||
feeFiatRateUM = FeeFiatRateUM(
|
||||
rate = BigDecimal("2500"),
|
||||
appCurrency = AppCurrency.Default,
|
||||
),
|
||||
fees = TransactionFee.Single(lowFeeItem.fee),
|
||||
),
|
||||
FeeSelectorUM.Content(
|
||||
feeItems = persistentListOf(maxFeeItem),
|
||||
selectedFeeItem = maxFeeItem,
|
||||
feeExtraInfo = FeeExtraInfo(
|
||||
isFeeApproximate = false,
|
||||
isFeeConvertibleToFiat = true,
|
||||
isTronToken = false,
|
||||
),
|
||||
feeNonce = FeeNonce.None,
|
||||
feeFiatRateUM = FeeFiatRateUM(
|
||||
rate = BigDecimal("2500000000000"),
|
||||
appCurrency = AppCurrency.Default,
|
||||
),
|
||||
fees = TransactionFee.Single(maxFeeItem.fee),
|
||||
),
|
||||
FeeSelectorUM.Error(GetFeeError.UnknownError),
|
||||
FeeSelectorUM.Loading,
|
||||
)
|
||||
}
|
||||
|
|
@ -4,10 +4,8 @@ import android.content.res.Configuration
|
|||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
|
|
@ -17,7 +15,6 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.getValue
|
||||
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.res.painterResource
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
|
|
@ -31,6 +28,7 @@ import androidx.compose.ui.util.fastForEachIndexed
|
|||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.common.ui.amountScreen.utils.getFiatReference
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.atoms.text.EllipsisText
|
||||
|
|
@ -39,6 +37,7 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
|||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle
|
||||
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetWithFooter
|
||||
import com.tangem.core.ui.components.inputrow.InputRowEnter
|
||||
import com.tangem.core.ui.components.inputrow.InputRowEnterInfoAmountV2
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
|
|
@ -47,10 +46,8 @@ import com.tangem.core.ui.format.bigdecimal.format
|
|||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.features.send.v2.api.entity.CustomFeeFieldUM
|
||||
import com.tangem.features.send.v2.api.entity.FeeFiatRateUM
|
||||
import com.tangem.features.send.v2.api.entity.FeeItem
|
||||
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.v2.api.entity.*
|
||||
import com.tangem.features.send.v2.api.params.FeeSelectorParams
|
||||
import com.tangem.features.send.v2.feeselector.model.FeeSelectorIntents
|
||||
import com.tangem.features.send.v2.feeselector.model.StubFeeSelectorIntents
|
||||
import com.tangem.features.send.v2.impl.R
|
||||
|
|
@ -58,12 +55,12 @@ import com.tangem.utils.StringsSigns
|
|||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
|
||||
@Composable
|
||||
internal fun FeeSelectorModalBottomSheet(
|
||||
state: FeeSelectorUM,
|
||||
feeSelectorIntents: FeeSelectorIntents,
|
||||
feeDisplaySource: FeeSelectorParams.FeeDisplaySource,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
if (state !is FeeSelectorUM.Content) return
|
||||
|
|
@ -76,17 +73,13 @@ internal fun FeeSelectorModalBottomSheet(
|
|||
),
|
||||
containerColor = TangemTheme.colors.background.primary,
|
||||
title = {
|
||||
TangemModalBottomSheetTitle(
|
||||
title = resourceReference(R.string.common_network_fee_title),
|
||||
startIconRes = R.drawable.ic_back_24,
|
||||
onStartClick = onDismiss,
|
||||
)
|
||||
FeeTitle(feeDisplaySource = feeDisplaySource, onDismiss = onDismiss)
|
||||
},
|
||||
content = {
|
||||
FeeSelectorItems(
|
||||
state = state,
|
||||
feeSelectorIntents = feeSelectorIntents,
|
||||
modifier = Modifier.padding(vertical = 4.dp, horizontal = 16.dp),
|
||||
modifier = Modifier.padding(vertical = 4.dp, horizontal = 13.dp),
|
||||
)
|
||||
},
|
||||
footer = {
|
||||
|
|
@ -101,6 +94,26 @@ internal fun FeeSelectorModalBottomSheet(
|
|||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FeeTitle(feeDisplaySource: FeeSelectorParams.FeeDisplaySource, onDismiss: () -> Unit) {
|
||||
when (feeDisplaySource) {
|
||||
FeeSelectorParams.FeeDisplaySource.Screen -> {
|
||||
TangemModalBottomSheetTitle(
|
||||
title = resourceReference(R.string.common_network_fee_title),
|
||||
endIconRes = R.drawable.ic_close_24,
|
||||
onEndClick = onDismiss,
|
||||
)
|
||||
}
|
||||
FeeSelectorParams.FeeDisplaySource.BottomSheet -> {
|
||||
TangemModalBottomSheetTitle(
|
||||
title = resourceReference(R.string.common_network_fee_title),
|
||||
startIconRes = R.drawable.ic_back_24,
|
||||
onStartClick = onDismiss,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@Composable
|
||||
private fun FeeSelectorItems(
|
||||
|
|
@ -111,7 +124,7 @@ private fun FeeSelectorItems(
|
|||
Column(modifier = modifier) {
|
||||
val feeFiatRateUM = state.feeFiatRateUM
|
||||
state.feeItems.fastForEachIndexed { index, item ->
|
||||
val isSelected = item.isSame(state.selectedFeeItem)
|
||||
val isSelected = item.isSameClass(state.selectedFeeItem)
|
||||
val lastItem = index == state.feeItems.size - 1
|
||||
val iconTint by animateColorAsState(
|
||||
targetValue = if (isSelected) TangemTheme.colors.icon.accent else TangemTheme.colors.text.tertiary,
|
||||
|
|
@ -128,21 +141,7 @@ private fun FeeSelectorItems(
|
|||
val itemModifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(TangemTheme.colors.background.primary)
|
||||
.then(
|
||||
if (isSelected) {
|
||||
Modifier
|
||||
.border(
|
||||
width = 2.5.dp,
|
||||
color = iconTint.copy(alpha = 0.2F),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
)
|
||||
.padding(2.5.dp)
|
||||
.border(width = 1.dp, color = iconTint, shape = RoundedCornerShape(14.dp))
|
||||
.clip(RoundedCornerShape(14.dp))
|
||||
} else {
|
||||
Modifier
|
||||
},
|
||||
)
|
||||
.selectedBorder(isSelected = isSelected)
|
||||
.clickableSingle(onClick = { feeSelectorIntents.onFeeItemSelected(item) })
|
||||
when (item) {
|
||||
is FeeItem.Suggested -> RegularFeeItemContent(
|
||||
|
|
@ -156,7 +155,7 @@ private fun FeeSelectorItems(
|
|||
crypto(
|
||||
symbol = item.fee.amount.currencySymbol,
|
||||
decimals = item.fee.amount.decimals,
|
||||
).fee(canBeLower = state.isFeeApproximate)
|
||||
).fee(canBeLower = state.feeExtraInfo.isFeeApproximate)
|
||||
},
|
||||
),
|
||||
postDot = if (feeFiatRateUM != null) {
|
||||
|
|
@ -182,7 +181,7 @@ private fun FeeSelectorItems(
|
|||
crypto(
|
||||
symbol = item.fee.amount.currencySymbol,
|
||||
decimals = item.fee.amount.decimals,
|
||||
).fee(canBeLower = state.isFeeApproximate)
|
||||
).fee(canBeLower = state.feeExtraInfo.isFeeApproximate)
|
||||
},
|
||||
),
|
||||
postDot = if (feeFiatRateUM != null) {
|
||||
|
|
@ -208,7 +207,7 @@ private fun FeeSelectorItems(
|
|||
crypto(
|
||||
symbol = item.fee.amount.currencySymbol,
|
||||
decimals = item.fee.amount.decimals,
|
||||
).fee(canBeLower = state.isFeeApproximate)
|
||||
).fee(canBeLower = state.feeExtraInfo.isFeeApproximate)
|
||||
},
|
||||
),
|
||||
postDot = if (feeFiatRateUM != null) {
|
||||
|
|
@ -234,7 +233,7 @@ private fun FeeSelectorItems(
|
|||
crypto(
|
||||
symbol = item.fee.amount.currencySymbol,
|
||||
decimals = item.fee.amount.decimals,
|
||||
).fee(canBeLower = state.isFeeApproximate)
|
||||
).fee(canBeLower = state.feeExtraInfo.isFeeApproximate)
|
||||
},
|
||||
),
|
||||
postDot = if (feeFiatRateUM != null) {
|
||||
|
|
@ -255,9 +254,8 @@ private fun FeeSelectorItems(
|
|||
isSelected = isSelected,
|
||||
iconBackgroundColor = iconBackgroundColor,
|
||||
iconTint = iconTint,
|
||||
displayNonceInput = state.displayNonceInput,
|
||||
nonce = state.nonce,
|
||||
onNonceChange = state.onNonceChange,
|
||||
onValueChange = feeSelectorIntents::onCustomFeeValueChange,
|
||||
nonce = state.feeNonce,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -271,9 +269,8 @@ private fun CustomFeeBlock(
|
|||
isSelected: Boolean,
|
||||
iconBackgroundColor: Color,
|
||||
iconTint: Color,
|
||||
displayNonceInput: Boolean,
|
||||
nonce: BigInteger?,
|
||||
onNonceChange: (String) -> Unit,
|
||||
onValueChange: (Int, String) -> Unit,
|
||||
nonce: FeeNonce,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
|
|
@ -305,10 +302,8 @@ private fun CustomFeeBlock(
|
|||
) {
|
||||
ExpandedCustomFeeItems(
|
||||
customFeeFields = customFee.customValues,
|
||||
onValueChange = { _, _ -> },
|
||||
displayNonceInput = displayNonceInput,
|
||||
onValueChange = onValueChange,
|
||||
nonce = nonce,
|
||||
onNonceChange = onNonceChange,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -318,14 +313,12 @@ private fun CustomFeeBlock(
|
|||
private fun ExpandedCustomFeeItems(
|
||||
customFeeFields: ImmutableList<CustomFeeFieldUM>,
|
||||
onValueChange: (Int, String) -> Unit,
|
||||
displayNonceInput: Boolean,
|
||||
nonce: BigInteger?,
|
||||
onNonceChange: (String) -> Unit,
|
||||
nonce: FeeNonce,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
customFeeFields.fastForEachIndexed { index, field ->
|
||||
val showDivider = index != customFeeFields.size - 1 || displayNonceInput
|
||||
val showDivider = index != customFeeFields.size - 1 || nonce is FeeNonce.Nonce
|
||||
if (field.label != null) {
|
||||
InputRowEnterInfoAmountV2(
|
||||
text = field.value,
|
||||
|
|
@ -334,6 +327,7 @@ private fun ExpandedCustomFeeItems(
|
|||
title = field.title,
|
||||
titleColor = TangemTheme.colors.text.tertiary,
|
||||
info = field.label,
|
||||
description = field.footer,
|
||||
keyboardOptions = field.keyboardOptions,
|
||||
keyboardActions = field.keyboardActions,
|
||||
onValueChange = { onValueChange(index, it) },
|
||||
|
|
@ -347,6 +341,7 @@ private fun ExpandedCustomFeeItems(
|
|||
title = field.title,
|
||||
titleColor = TangemTheme.colors.text.tertiary,
|
||||
symbol = field.symbol,
|
||||
description = field.footer,
|
||||
onValueChange = { onValueChange(index, it) },
|
||||
keyboardOptions = field.keyboardOptions,
|
||||
keyboardActions = field.keyboardActions,
|
||||
|
|
@ -355,18 +350,21 @@ private fun ExpandedCustomFeeItems(
|
|||
}
|
||||
}
|
||||
|
||||
if (displayNonceInput) {
|
||||
// TODO implement v2 input without binding to amount
|
||||
InputRowEnterInfoAmountV2(
|
||||
text = nonce?.toString() ?: "",
|
||||
decimals = 0,
|
||||
if (nonce is FeeNonce.Nonce) {
|
||||
InputRowEnter(
|
||||
text = nonce.nonce?.toString().orEmpty(),
|
||||
title = resourceReference(R.string.send_nonce),
|
||||
titleColor = TangemTheme.colors.text.tertiary,
|
||||
symbol = null,
|
||||
onValueChange = onNonceChange,
|
||||
description = resourceReference(R.string.send_nonce_footer),
|
||||
onValueChange = nonce.onNonceChange,
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
keyboardActions = KeyboardActions(),
|
||||
placeholder = resourceReference(R.string.send_nonce_hint),
|
||||
titleColor = TangemTheme.colors.text.secondary,
|
||||
showDivider = false,
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = TangemTheme.colors.background.action,
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -474,7 +472,12 @@ private fun FeeSelectorBS_Preview(
|
|||
state: FeeSelectorUM.Content,
|
||||
) {
|
||||
TangemThemePreview {
|
||||
FeeSelectorModalBottomSheet(onDismiss = {}, state = state, feeSelectorIntents = StubFeeSelectorIntents())
|
||||
FeeSelectorModalBottomSheet(
|
||||
onDismiss = {},
|
||||
state = state,
|
||||
feeSelectorIntents = StubFeeSelectorIntents(),
|
||||
feeDisplaySource = FeeSelectorParams.FeeDisplaySource.BottomSheet,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -495,14 +498,17 @@ private class FeeSelectorUMContentProvider : CollectionPreviewParameterProvider<
|
|||
// amount = Amount(value = BigDecimal("0.02"), blockchain = Blockchain.Ethereum),
|
||||
// ),
|
||||
selectedFeeItem = customFeeItem,
|
||||
isFeeApproximate = true,
|
||||
feeExtraInfo = FeeExtraInfo(
|
||||
isFeeApproximate = true,
|
||||
isFeeConvertibleToFiat = true,
|
||||
isTronToken = false,
|
||||
),
|
||||
feeFiatRateUM = FeeFiatRateUM(
|
||||
rate = BigDecimal.TEN,
|
||||
appCurrency = AppCurrency.Default,
|
||||
),
|
||||
displayNonceInput = true,
|
||||
onNonceChange = {},
|
||||
nonce = null,
|
||||
feeNonce = FeeNonce.None,
|
||||
fees = TransactionFee.Single(customFeeItem.fee),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -14,12 +14,14 @@ import com.arkivanov.decompose.value.subscribe
|
|||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.context.child
|
||||
import com.tangem.core.decompose.context.childByContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.decompose.navigation.inner.InnerRouter
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.send.v2.api.FeeSelectorBlockComponent
|
||||
import com.tangem.features.send.v2.api.SendComponent
|
||||
import com.tangem.features.send.v2.common.CommonSendRoute
|
||||
import com.tangem.features.send.v2.common.analytics.CommonSendAnalyticEvents
|
||||
|
|
@ -30,11 +32,14 @@ import com.tangem.features.send.v2.common.utils.safeNextClick
|
|||
import com.tangem.features.send.v2.impl.R
|
||||
import com.tangem.features.send.v2.send.confirm.SendConfirmComponent
|
||||
import com.tangem.features.send.v2.send.model.SendModel
|
||||
import com.tangem.features.send.v2.send.success.SendConfirmSuccessComponent
|
||||
import com.tangem.features.send.v2.subcomponents.amount.SendAmountComponent
|
||||
import com.tangem.features.send.v2.subcomponents.amount.SendAmountComponentParams
|
||||
import com.tangem.features.send.v2.subcomponents.fee.SendFeeBlockComponent
|
||||
import com.tangem.features.send.v2.subcomponents.destination.DefaultSendDestinationComponent
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationComponentParams
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationUM
|
||||
import com.tangem.features.send.v2.subcomponents.destination.DefaultSendDestinationBlockComponent
|
||||
import com.tangem.features.send.v2.subcomponents.fee.SendFeeComponent
|
||||
import com.tangem.features.send.v2.subcomponents.fee.SendFeeComponentParams
|
||||
import dagger.assisted.Assisted
|
||||
|
|
@ -44,10 +49,12 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
import kotlinx.coroutines.flow.filterIsInstance
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Suppress("LargeClass")
|
||||
internal class DefaultSendComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted private val params: SendComponent.Params,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val feeSelectorComponentFactory: FeeSelectorBlockComponent.Factory,
|
||||
) : SendComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val stackNavigation = StackNavigation<CommonSendRoute>()
|
||||
|
|
@ -142,7 +149,8 @@ internal class DefaultSendComponent @AssistedInject constructor(
|
|||
is CommonSendRoute.Destination -> getDestinationComponent(factoryContext, route)
|
||||
is CommonSendRoute.Amount -> getAmountComponent(factoryContext, route)
|
||||
is CommonSendRoute.Fee -> getFeeComponent(factoryContext)
|
||||
CommonSendRoute.Confirm -> getConfirmComponent(factoryContext)
|
||||
is CommonSendRoute.Confirm -> getConfirmComponent(factoryContext)
|
||||
is CommonSendRoute.ConfirmSuccess -> getConfirmSuccessComponent(factoryContext)
|
||||
}
|
||||
|
||||
private fun getDestinationComponent(
|
||||
|
|
@ -288,7 +296,11 @@ internal class DefaultSendComponent @AssistedInject constructor(
|
|||
callback = model,
|
||||
predefinedValues = model.predefinedValues,
|
||||
onLoadFee = model::loadFee,
|
||||
onSendTransaction = {
|
||||
innerRouter.replaceAll(CommonSendRoute.ConfirmSuccess)
|
||||
},
|
||||
),
|
||||
feeSelectorComponentFactory = feeSelectorComponentFactory,
|
||||
)
|
||||
} else {
|
||||
model.showAlertError()
|
||||
|
|
@ -296,6 +308,69 @@ internal class DefaultSendComponent @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getConfirmSuccessComponent(factoryContext: AppComponentContext): ComposableContentComponent {
|
||||
val state = model.uiState.value
|
||||
val sendAmount = (state.amountUM as? AmountState.Data)?.amountTextField?.cryptoAmount?.value
|
||||
val destinationAddress = (state.destinationUM as? DestinationUM.Content)?.addressTextField?.value
|
||||
val txUrl = (state.confirmUM as? ConfirmUM.Success)?.txUrl
|
||||
val cryptoCurrencyStatus = model.cryptoCurrencyStatusFlow.value
|
||||
val feeCryptoCurrencyStatus = model.feeCryptoCurrencyStatusFlow.value
|
||||
|
||||
if (sendAmount == null ||
|
||||
destinationAddress == null ||
|
||||
txUrl == null
|
||||
) {
|
||||
model.showAlertError()
|
||||
return getStubComponent()
|
||||
}
|
||||
|
||||
val destinationBlockComponent =
|
||||
DefaultSendDestinationBlockComponent(
|
||||
appComponentContext = child("sendConfirmDestinationBlock"),
|
||||
params = SendDestinationComponentParams.DestinationBlockParams(
|
||||
state = model.uiState.value.destinationUM,
|
||||
analyticsCategoryName = analyticCategoryName,
|
||||
userWalletId = model.userWallet.walletId,
|
||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
||||
blockClickEnableFlow = MutableStateFlow(true),
|
||||
predefinedValues = model.predefinedValues,
|
||||
),
|
||||
onResult = { },
|
||||
onClick = {},
|
||||
)
|
||||
|
||||
val feeBlockComponent = SendFeeBlockComponent(
|
||||
appComponentContext = child("sendConfirmFeeBlock"),
|
||||
params = SendFeeComponentParams.FeeBlockParams(
|
||||
state = model.uiState.value.feeUM,
|
||||
analyticsCategoryName = analyticCategoryName,
|
||||
userWallet = model.userWallet,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
|
||||
appCurrency = model.appCurrency,
|
||||
sendAmount = sendAmount,
|
||||
destinationAddress = destinationAddress,
|
||||
blockClickEnableFlow = MutableStateFlow(true),
|
||||
onLoadFee = model::loadFee,
|
||||
),
|
||||
onResult = { },
|
||||
onClick = {},
|
||||
)
|
||||
|
||||
return SendConfirmSuccessComponent(
|
||||
appComponentContext = factoryContext,
|
||||
params = SendConfirmSuccessComponent.Params(
|
||||
sendUMFlow = model.uiState,
|
||||
feeBlockComponent = feeBlockComponent,
|
||||
destinationBlockComponent = destinationBlockComponent,
|
||||
analyticsCategoryName = analyticCategoryName,
|
||||
currentRoute = currentRoute,
|
||||
txUrl = txUrl,
|
||||
callback = model,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getStubComponent() = StubComponent()
|
||||
|
||||
class StubComponent : ComposableContentComponent {
|
||||
|
|
|
|||
|
|
@ -14,9 +14,11 @@ import com.tangem.domain.appcurrency.model.AppCurrency
|
|||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.features.send.v2.api.FeeSelectorBlockComponent
|
||||
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.entity.PredefinedValues
|
||||
import com.tangem.features.send.v2.api.params.FeeSelectorParams
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationComponentParams.DestinationBlockParams
|
||||
import com.tangem.features.send.v2.common.CommonSendRoute
|
||||
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
|
||||
|
|
@ -35,6 +37,7 @@ import kotlinx.coroutines.flow.*
|
|||
internal class SendConfirmComponent(
|
||||
appComponentContext: AppComponentContext,
|
||||
params: Params,
|
||||
private val feeSelectorComponentFactory: FeeSelectorBlockComponent.Factory,
|
||||
) : ComposableContentComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: SendConfirmModel = getOrCreateModel(params = params)
|
||||
|
|
@ -92,6 +95,19 @@ internal class SendConfirmComponent(
|
|||
onClick = model::showEditFee,
|
||||
)
|
||||
|
||||
private val feeSelectorBlockComponent = feeSelectorComponentFactory.create(
|
||||
context = appComponentContext,
|
||||
params = FeeSelectorParams.FeeSelectorBlockParams(
|
||||
state = model.uiState.value.feeSelectorUM,
|
||||
onLoadFee = params.onLoadFee,
|
||||
feeCryptoCurrencyStatus = params.feeCryptoCurrencyStatus,
|
||||
cryptoCurrencyStatus = params.cryptoCurrencyStatus,
|
||||
suggestedFeeState = model.suggestedFeeState,
|
||||
feeDisplaySource = FeeSelectorParams.FeeDisplaySource.Screen,
|
||||
),
|
||||
onResult = model::onFeeResult,
|
||||
)
|
||||
|
||||
private val notificationsComponent = DefaultSendNotificationsComponent(
|
||||
appComponentContext = child("sendConfirmNotifications"),
|
||||
params = SendNotificationsComponent.Params(
|
||||
|
|
@ -136,6 +152,7 @@ internal class SendConfirmComponent(
|
|||
destinationBlockComponent = destinationBlockComponent,
|
||||
amountBlockComponent = amountBlockComponent,
|
||||
feeBlockComponent = feeBlockComponent,
|
||||
feeSelectorBlockComponent = feeSelectorBlockComponent,
|
||||
notificationsComponent = notificationsComponent,
|
||||
notificationsUM = notificationState,
|
||||
)
|
||||
|
|
@ -155,6 +172,7 @@ internal class SendConfirmComponent(
|
|||
val isBalanceHidingFlow: StateFlow<Boolean>,
|
||||
val predefinedValues: PredefinedValues,
|
||||
val onLoadFee: suspend () -> Either<GetFeeError, TransactionFee>,
|
||||
val onSendTransaction: () -> Unit,
|
||||
)
|
||||
|
||||
interface ModelCallback {
|
||||
|
|
|
|||
|
|
@ -35,7 +35,13 @@ import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
|
|||
import com.tangem.domain.utils.convertToSdkAmount
|
||||
import com.tangem.domain.wallets.models.requireColdWallet
|
||||
import com.tangem.features.send.v2.api.SendNotificationsComponent.Params.NotificationData
|
||||
import com.tangem.features.send.v2.api.callbacks.FeeSelectorModelCallback
|
||||
import com.tangem.features.send.v2.api.entity.FeeNonce
|
||||
import com.tangem.features.send.v2.api.params.FeeSelectorParams
|
||||
import com.tangem.features.send.v2.api.entity.FeeSelectorUM as FeeSelectorUMRedesigned
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationUM
|
||||
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.v2.common.CommonSendRoute
|
||||
import com.tangem.features.send.v2.common.SendBalanceUpdater
|
||||
import com.tangem.features.send.v2.common.SendConfirmAlertFactory
|
||||
|
|
@ -49,13 +55,13 @@ import com.tangem.features.send.v2.send.confirm.model.transformers.SendConfirmIn
|
|||
import com.tangem.features.send.v2.send.confirm.model.transformers.SendConfirmSendingStateTransformer
|
||||
import com.tangem.features.send.v2.send.confirm.model.transformers.SendConfirmSentStateTransformer
|
||||
import com.tangem.features.send.v2.send.confirm.model.transformers.SendConfirmationNotificationsTransformer
|
||||
import com.tangem.features.send.v2.send.confirm.model.transformers.SendConfirmationNotificationsTransformerV2
|
||||
import com.tangem.features.send.v2.send.ui.state.SendUM
|
||||
import com.tangem.features.send.v2.subcomponents.fee.SendFeeCheckReloadListener
|
||||
import com.tangem.features.send.v2.subcomponents.fee.SendFeeCheckReloadTrigger
|
||||
import com.tangem.features.send.v2.subcomponents.fee.model.checkAndCalculateSubtractedAmount
|
||||
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeSelectorUM
|
||||
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeUM
|
||||
import com.tangem.features.send.v2.subcomponents.notifications.NotificationsUpdateTrigger
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import com.tangem.utils.extensions.stripZeroPlainString
|
||||
|
|
@ -86,13 +92,14 @@ internal class SendConfirmModel @Inject constructor(
|
|||
private val isAmountSubtractAvailableUseCase: IsAmountSubtractAvailableUseCase,
|
||||
private val sendFeeCheckReloadTrigger: SendFeeCheckReloadTrigger,
|
||||
private val sendFeeCheckReloadListener: SendFeeCheckReloadListener,
|
||||
private val notificationsUpdateTrigger: NotificationsUpdateTrigger,
|
||||
private val notificationsUpdateTrigger: SendNotificationsUpdateTrigger,
|
||||
private val notificationsUpdateListener: SendNotificationsUpdateListener,
|
||||
private val alertFactory: SendConfirmAlertFactory,
|
||||
private val sendAnalyticHelper: SendAnalyticHelper,
|
||||
private val urlOpener: UrlOpener,
|
||||
private val shareManager: ShareManager,
|
||||
sendBalanceUpdaterFactory: SendBalanceUpdater.Factory,
|
||||
) : Model(), SendConfirmClickIntents {
|
||||
) : Model(), SendConfirmClickIntents, FeeSelectorModelCallback {
|
||||
|
||||
private val params: SendConfirmComponent.Params = paramsContainer.require()
|
||||
|
||||
|
|
@ -115,6 +122,8 @@ internal class SendConfirmModel @Inject constructor(
|
|||
get() = uiState.value.feeUM as? FeeUM.Content
|
||||
private val feeSelectorUM
|
||||
get() = feeUM?.feeSelectorUM as? FeeSelectorUM.Content
|
||||
private val feeUMV2
|
||||
get() = uiState.value.feeSelectorUM as? FeeSelectorUMRedesigned.Content
|
||||
|
||||
val confirmData: ConfirmData
|
||||
get() = ConfirmData(
|
||||
|
|
@ -129,6 +138,7 @@ internal class SendConfirmModel @Inject constructor(
|
|||
|
||||
private var sendIdleTimer: Long = 0L
|
||||
private var isAmountSubtractAvailable = false
|
||||
internal var suggestedFeeState: FeeSelectorParams.SuggestedFeeState = FeeSelectorParams.SuggestedFeeState.None
|
||||
|
||||
init {
|
||||
modelScope.launch {
|
||||
|
|
@ -291,6 +301,7 @@ internal class SendConfirmModel @Inject constructor(
|
|||
isShowTapHelp = isShowTapHelp,
|
||||
walletName = stringReference(userWallet.name),
|
||||
).transform(uiState.value.confirmUM),
|
||||
confirmData = confirmData,
|
||||
)
|
||||
}
|
||||
updateConfirmNotifications()
|
||||
|
|
@ -299,16 +310,25 @@ internal class SendConfirmModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun subscribeOnNotificationsUpdateTrigger() {
|
||||
notificationsUpdateTrigger.hasErrorFlow
|
||||
notificationsUpdateListener.hasErrorFlow
|
||||
.onEach { hasError ->
|
||||
_uiState.update {
|
||||
val feeUM = it.feeUM as? FeeUM.Content
|
||||
val feeSelectorUM = feeUM?.feeSelectorUM as? FeeSelectorUM.Content
|
||||
it.copy(
|
||||
confirmUM = (it.confirmUM as? ConfirmUM.Content)?.copy(
|
||||
isPrimaryButtonEnabled = !hasError && feeSelectorUM != null,
|
||||
) ?: it.confirmUM,
|
||||
)
|
||||
if (_uiState.value.isRedesignEnabled) {
|
||||
val feeUM = it.feeSelectorUM as? FeeSelectorUMRedesigned.Content
|
||||
it.copy(
|
||||
confirmUM = (it.confirmUM as? ConfirmUM.Content)?.copy(
|
||||
isPrimaryButtonEnabled = !hasError && feeUM != null,
|
||||
) ?: it.confirmUM,
|
||||
)
|
||||
} else {
|
||||
val feeUM = it.feeUM as? FeeUM.Content
|
||||
val feeSelectorUM = feeUM?.feeSelectorUM as? FeeSelectorUM.Content
|
||||
it.copy(
|
||||
confirmUM = (it.confirmUM as? ConfirmUM.Content)?.copy(
|
||||
isPrimaryButtonEnabled = !hasError && feeSelectorUM != null,
|
||||
) ?: it.confirmUM,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
.launchIn(modelScope)
|
||||
|
|
@ -318,9 +338,18 @@ internal class SendConfirmModel @Inject constructor(
|
|||
val amountValue = amountState?.amountTextField?.cryptoAmount?.value ?: return
|
||||
val destination = destinationUM?.addressTextField?.actualAddress ?: return
|
||||
val memo = destinationUM?.memoTextField?.value
|
||||
val fee = feeSelectorUM?.selectedFee
|
||||
val isRedesignEnabled = uiState.value.isRedesignEnabled
|
||||
val fee = if (isRedesignEnabled) {
|
||||
feeUMV2?.selectedFeeItem?.fee
|
||||
} else {
|
||||
feeSelectorUM?.selectedFee
|
||||
}
|
||||
val nonce = if (isRedesignEnabled) {
|
||||
(feeUMV2?.feeNonce as? FeeNonce.Nonce)?.nonce
|
||||
} else {
|
||||
feeSelectorUM?.nonce
|
||||
}
|
||||
val feeValue = fee?.amount?.value ?: return
|
||||
val nonce = feeSelectorUM?.nonce
|
||||
|
||||
val receivingAmount = checkAndCalculateSubtractedAmount(
|
||||
isAmountSubtractAvailable = isAmountSubtractAvailable,
|
||||
|
|
@ -385,6 +414,10 @@ internal class SendConfirmModel @Inject constructor(
|
|||
addTokenToWalletIfNeeded()
|
||||
sendBalanceUpdater.scheduleUpdates()
|
||||
sendAnalyticHelper.sendSuccessAnalytics(cryptoCurrency, uiState.value)
|
||||
if (uiState.value.isRedesignEnabled) {
|
||||
params.callback.onResult(uiState.value)
|
||||
params.onSendTransaction()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -444,14 +477,25 @@ internal class SendConfirmModel @Inject constructor(
|
|||
)
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
confirmUM = SendConfirmationNotificationsTransformer(
|
||||
feeUM = uiState.value.feeUM,
|
||||
amountUM = uiState.value.amountUM,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
||||
appCurrency = appCurrency,
|
||||
analyticsCategoryName = params.analyticsCategoryName,
|
||||
).transform(uiState.value.confirmUM),
|
||||
confirmUM = if (uiState.value.isRedesignEnabled) {
|
||||
SendConfirmationNotificationsTransformerV2(
|
||||
feeSelectorUM = uiState.value.feeSelectorUM,
|
||||
amountUM = uiState.value.amountUM,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
||||
appCurrency = appCurrency,
|
||||
analyticsCategoryName = params.analyticsCategoryName,
|
||||
).transform(uiState.value.confirmUM)
|
||||
} else {
|
||||
SendConfirmationNotificationsTransformer(
|
||||
feeUM = uiState.value.feeUM,
|
||||
amountUM = uiState.value.amountUM,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
||||
appCurrency = appCurrency,
|
||||
analyticsCategoryName = params.analyticsCategoryName,
|
||||
).transform(uiState.value.confirmUM)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -463,23 +507,35 @@ internal class SendConfirmModel @Inject constructor(
|
|||
flow = uiState,
|
||||
flow2 = params.currentRoute,
|
||||
transform = { state, route -> state to route },
|
||||
).filter { it.second is CommonSendRoute.Confirm }.onEach { (state, _) ->
|
||||
).filter {
|
||||
it.second is CommonSendRoute.Confirm
|
||||
}.onEach { (state, _) ->
|
||||
val amountUM = state.amountUM as? AmountState.Data
|
||||
val confirmUM = state.confirmUM
|
||||
val isReadyToSend = confirmUM is ConfirmUM.Content && !confirmUM.isSending
|
||||
params.callback.onResult(
|
||||
state.copy(
|
||||
navigationUM = NavigationUM.Content(
|
||||
title = resourceReference(
|
||||
id = R.string.send_summary_title,
|
||||
formatArgs = wrappedList(params.cryptoCurrencyStatus.currency.name),
|
||||
),
|
||||
title = if (state.isRedesignEnabled) {
|
||||
stringReference("")
|
||||
} else {
|
||||
resourceReference(
|
||||
id = R.string.send_summary_title,
|
||||
formatArgs = wrappedList(params.cryptoCurrencyStatus.currency.name),
|
||||
)
|
||||
},
|
||||
subtitle = if (uiState.value.isRedesignEnabled) {
|
||||
null
|
||||
} else {
|
||||
amountUM?.title
|
||||
},
|
||||
backIconRes = R.drawable.ic_close_24,
|
||||
backIconRes = if (state.isRedesignEnabled) {
|
||||
when (confirmUM) {
|
||||
is ConfirmUM.Success -> R.drawable.ic_close_24
|
||||
else -> R.drawable.ic_back_24
|
||||
}
|
||||
} else {
|
||||
R.drawable.ic_close_24
|
||||
},
|
||||
backIconClick = {
|
||||
analyticsEventHandler.send(
|
||||
CommonSendAnalyticEvents.CloseButtonClicked(
|
||||
|
|
@ -491,31 +547,7 @@ internal class SendConfirmModel @Inject constructor(
|
|||
)
|
||||
appRouter.pop()
|
||||
},
|
||||
primaryButton = NavigationButton(
|
||||
textReference = when (confirmUM) {
|
||||
is ConfirmUM.Success -> resourceReference(R.string.common_close)
|
||||
is ConfirmUM.Content -> if (confirmUM.isSending) {
|
||||
resourceReference(R.string.send_sending)
|
||||
} else {
|
||||
resourceReference(R.string.common_send)
|
||||
}
|
||||
else -> resourceReference(R.string.common_send)
|
||||
},
|
||||
iconRes = R.drawable.ic_tangem_24.takeIf { isReadyToSend },
|
||||
isEnabled = confirmUM.isPrimaryButtonEnabled,
|
||||
isHapticClick = isReadyToSend,
|
||||
onClick = {
|
||||
when (confirmUM) {
|
||||
is ConfirmUM.Success -> appRouter.pop()
|
||||
is ConfirmUM.Content -> if (confirmUM.isSending) {
|
||||
return@NavigationButton
|
||||
} else {
|
||||
onSendClick()
|
||||
}
|
||||
else -> return@NavigationButton
|
||||
}
|
||||
},
|
||||
),
|
||||
primaryButton = primaryButtonUM(),
|
||||
prevButton = null,
|
||||
secondaryPairButtonsUM = (
|
||||
NavigationButton(
|
||||
|
|
@ -534,6 +566,42 @@ internal class SendConfirmModel @Inject constructor(
|
|||
}.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun primaryButtonUM(): NavigationButton {
|
||||
val confirmUM = uiState.value.confirmUM
|
||||
val isReadyToSend = confirmUM is ConfirmUM.Content && !confirmUM.isSending
|
||||
return NavigationButton(
|
||||
textReference = when (confirmUM) {
|
||||
is ConfirmUM.Success -> resourceReference(R.string.common_close)
|
||||
is ConfirmUM.Content -> if (confirmUM.isSending) {
|
||||
resourceReference(R.string.send_sending)
|
||||
} else {
|
||||
resourceReference(R.string.common_send)
|
||||
}
|
||||
else -> resourceReference(R.string.common_send)
|
||||
},
|
||||
iconRes = R.drawable.ic_tangem_24.takeIf { isReadyToSend },
|
||||
isEnabled = confirmUM.isPrimaryButtonEnabled,
|
||||
isHapticClick = isReadyToSend,
|
||||
onClick = {
|
||||
when (confirmUM) {
|
||||
is ConfirmUM.Success -> appRouter.pop()
|
||||
is ConfirmUM.Content -> if (confirmUM.isSending) {
|
||||
return@NavigationButton
|
||||
} else {
|
||||
onSendClick()
|
||||
}
|
||||
else -> return@NavigationButton
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
override fun onFeeResult(feeSelectorUM: FeeSelectorUMRedesigned) {
|
||||
sendIdleTimer = SystemClock.elapsedRealtime()
|
||||
_uiState.update { it.copy(feeSelectorUM = feeSelectorUM) }
|
||||
updateConfirmNotifications()
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val CHECK_FEE_UPDATE_DELAY = 10_000L
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,110 @@
|
|||
package com.tangem.features.send.v2.send.confirm.model.transformers
|
||||
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
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.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.v2.api.subcomponents.feeSelector.utils.FeeCalculationUtils
|
||||
import com.tangem.features.send.v2.common.analytics.CommonSendAnalyticEvents
|
||||
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
|
||||
import com.tangem.features.send.v2.common.utils.formatFooterFiatFee
|
||||
import com.tangem.features.send.v2.common.utils.getTronTokenFeeSendingText
|
||||
import com.tangem.features.send.v2.impl.R
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
internal class SendConfirmationNotificationsTransformerV2(
|
||||
private val feeSelectorUM: FeeSelectorUM,
|
||||
private val amountUM: AmountState,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val cryptoCurrency: CryptoCurrency,
|
||||
private val appCurrency: AppCurrency,
|
||||
private val analyticsCategoryName: String,
|
||||
) : Transformer<ConfirmUM> {
|
||||
override fun transform(prevState: ConfirmUM): ConfirmUM {
|
||||
val state = prevState as? ConfirmUM.Content ?: return prevState
|
||||
val feeSelectorUM = feeSelectorUM as? FeeSelectorUM.Content ?: return prevState
|
||||
return state.copy(
|
||||
sendingFooter = getSendingFooterText(),
|
||||
notifications = buildList {
|
||||
addTooHighNotification(feeSelectorUM)
|
||||
addTooLowNotification(feeSelectorUM)
|
||||
}.toPersistentList(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun MutableList<NotificationUM>.addTooLowNotification(feeSelectorUM: FeeSelectorUM.Content) {
|
||||
if (FeeCalculationUtils.checkIfCustomFeeTooLow(feeSelectorUM)) {
|
||||
add(NotificationUM.Warning.FeeTooLow)
|
||||
analyticsEventHandler.send(
|
||||
CommonSendAnalyticEvents.NoticeTransactionDelays(
|
||||
categoryName = analyticsCategoryName,
|
||||
token = cryptoCurrency.symbol,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<NotificationUM>.addTooHighNotification(feeSelectorUM: FeeSelectorUM.Content) {
|
||||
val (isFeeTooHigh, diff) = FeeCalculationUtils.checkIfCustomFeeTooHigh(feeSelectorUM)
|
||||
if (isFeeTooHigh) {
|
||||
add(NotificationUM.Warning.TooHigh(diff))
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSendingFooterText(): TextReference {
|
||||
val feeSelectorUM = feeSelectorUM as? FeeSelectorUM.Content
|
||||
val amountUM = amountUM as? AmountState.Data
|
||||
val fee = feeSelectorUM?.selectedFeeItem?.fee
|
||||
|
||||
if (fee == null || amountUM == null) return TextReference.EMPTY
|
||||
|
||||
val fiatAmountValue = amountUM.amountTextField.fiatAmount.value
|
||||
val fiatFeeValue = feeSelectorUM.feeFiatRateUM?.rate?.let { fee.amount.value?.multiply(it) }
|
||||
|
||||
val fiatSendingValue = if (feeSelectorUM.feeFiatRateUM != null) {
|
||||
fiatFeeValue?.let { fiatAmountValue?.plus(it) }
|
||||
} else {
|
||||
fiatAmountValue
|
||||
}
|
||||
|
||||
val fiatSending = fiatSendingValue.format {
|
||||
fiat(
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
)
|
||||
}
|
||||
val fiatFee = formatFooterFiatFee(
|
||||
amount = fee.amount.copy(value = fiatFeeValue),
|
||||
isFeeConvertibleToFiat = feeSelectorUM.feeFiatRateUM != null,
|
||||
isFeeApproximate = feeSelectorUM.feeExtraInfo.isFeeApproximate,
|
||||
appCurrency = appCurrency,
|
||||
)
|
||||
|
||||
return if (fee is Fee.Tron) {
|
||||
getTronTokenFeeSendingText(
|
||||
fee = fee,
|
||||
fiatFee = fiatFee,
|
||||
fiatSending = stringReference(fiatSending),
|
||||
)
|
||||
} else {
|
||||
resourceReference(
|
||||
id = if (feeSelectorUM.feeFiatRateUM != null) {
|
||||
R.string.send_summary_transaction_description
|
||||
} else {
|
||||
R.string.send_summary_transaction_description_no_fiat_fee
|
||||
},
|
||||
formatArgs = wrappedList(fiatSending, fiatFee),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.features.send.v2.send.confirm.ui
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
|
|
@ -9,6 +10,7 @@ import androidx.compose.foundation.lazy.LazyListScope
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.ui.components.SpacerHMax
|
||||
|
|
@ -19,6 +21,7 @@ import com.tangem.core.ui.extensions.wrappedList
|
|||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.utils.DateTimeFormatters
|
||||
import com.tangem.core.ui.utils.toTimeFormat
|
||||
import com.tangem.features.send.v2.api.FeeSelectorBlockComponent
|
||||
import com.tangem.features.send.v2.common.ui.SendingText
|
||||
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
|
||||
import com.tangem.features.send.v2.common.ui.tapHelp
|
||||
|
|
@ -40,6 +43,7 @@ internal fun SendConfirmContent(
|
|||
destinationBlockComponent: DefaultSendDestinationBlockComponent,
|
||||
amountBlockComponent: SendAmountBlockComponent,
|
||||
feeBlockComponent: SendFeeBlockComponent,
|
||||
feeSelectorBlockComponent: FeeSelectorBlockComponent,
|
||||
notificationsComponent: DefaultSendNotificationsComponent,
|
||||
notificationsUM: ImmutableList<NotificationUM>,
|
||||
) {
|
||||
|
|
@ -54,6 +58,7 @@ internal fun SendConfirmContent(
|
|||
destinationBlockComponent = destinationBlockComponent,
|
||||
amountBlockComponent = amountBlockComponent,
|
||||
feeBlockComponent = feeBlockComponent,
|
||||
feeSelectorBlockComponent = feeSelectorBlockComponent,
|
||||
)
|
||||
if (confirmUM != null) {
|
||||
tapHelp(isDisplay = confirmUM.showTapHelp)
|
||||
|
|
@ -79,34 +84,45 @@ private fun LazyListScope.blocks(
|
|||
destinationBlockComponent: DefaultSendDestinationBlockComponent,
|
||||
amountBlockComponent: SendAmountBlockComponent,
|
||||
feeBlockComponent: SendFeeBlockComponent,
|
||||
feeSelectorBlockComponent: FeeSelectorBlockComponent,
|
||||
) {
|
||||
item(key = BLOCKS_KEY) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
AnimatedVisibility(
|
||||
visible = uiState.confirmUM is ConfirmUM.Success,
|
||||
modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
val wrappedConfirmUM = remember(this) { uiState.confirmUM as ConfirmUM.Success }
|
||||
TransactionDoneTitle(
|
||||
title = resourceReference(R.string.sent_transaction_sent_title),
|
||||
subtitle = resourceReference(
|
||||
R.string.send_date_format,
|
||||
wrappedList(
|
||||
wrappedConfirmUM.transactionDate.toTimeFormat(DateTimeFormatters.dateFormatter),
|
||||
wrappedConfirmUM.transactionDate.toTimeFormat(),
|
||||
),
|
||||
),
|
||||
modifier = Modifier.padding(vertical = 12.dp),
|
||||
)
|
||||
}
|
||||
if (uiState.isRedesignEnabled) {
|
||||
amountBlockComponent.Content(modifier = Modifier)
|
||||
destinationBlockComponent.Content(modifier = Modifier)
|
||||
feeSelectorBlockComponent.Content(
|
||||
modifier = Modifier
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.background(TangemTheme.colors.background.action),
|
||||
)
|
||||
} else {
|
||||
TransactionDoneTitleAnimated(uiState)
|
||||
destinationBlockComponent.Content(modifier = Modifier)
|
||||
amountBlockComponent.Content(modifier = Modifier)
|
||||
feeBlockComponent.Content(modifier = Modifier)
|
||||
}
|
||||
feeBlockComponent.Content(modifier = Modifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun TransactionDoneTitleAnimated(uiState: SendUM) {
|
||||
AnimatedVisibility(
|
||||
visible = uiState.confirmUM is ConfirmUM.Success,
|
||||
modifier = Modifier.padding(vertical = 12.dp),
|
||||
) {
|
||||
val wrappedConfirmUM = remember(this) { uiState.confirmUM as ConfirmUM.Success }
|
||||
TransactionDoneTitle(
|
||||
title = resourceReference(R.string.sent_transaction_sent_title),
|
||||
subtitle = resourceReference(
|
||||
R.string.send_date_format,
|
||||
wrappedList(
|
||||
wrappedConfirmUM.transactionDate.toTimeFormat(DateTimeFormatters.dateFormatter),
|
||||
wrappedConfirmUM.transactionDate.toTimeFormat(),
|
||||
),
|
||||
),
|
||||
modifier = Modifier.padding(vertical = 12.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import com.tangem.core.decompose.di.ModelComponent
|
|||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.send.v2.send.confirm.model.SendConfirmModel
|
||||
import com.tangem.features.send.v2.send.model.SendModel
|
||||
import com.tangem.features.send.v2.send.success.model.SendConfirmSuccessModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -23,4 +24,9 @@ internal interface CommonSendModelModule {
|
|||
@IntoMap
|
||||
@ClassKey(SendConfirmModel::class)
|
||||
fun provideSendConfirmModel(model: SendConfirmModel): Model
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(SendConfirmSuccessModel::class)
|
||||
fun provideSendConfirmSuccessModel(model: SendConfirmSuccessModel): Model
|
||||
}
|
||||
|
|
@ -39,16 +39,19 @@ import com.tangem.domain.wallets.models.requireColdWallet
|
|||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.features.send.v2.api.SendComponent
|
||||
import com.tangem.features.send.v2.api.SendFeatureToggles
|
||||
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.v2.api.entity.PredefinedValues
|
||||
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.v2.common.CommonSendRoute
|
||||
import com.tangem.domain.wallets.models.GetUserWalletError
|
||||
import com.tangem.features.send.v2.common.SendConfirmAlertFactory
|
||||
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
|
||||
import com.tangem.features.send.v2.send.confirm.SendConfirmComponent
|
||||
import com.tangem.features.send.v2.send.success.SendConfirmSuccessComponent
|
||||
import com.tangem.features.send.v2.send.ui.state.SendUM
|
||||
import com.tangem.features.send.v2.subcomponents.amount.SendAmountComponent
|
||||
import com.tangem.features.send.v2.subcomponents.amount.SendAmountUpdateQRTrigger
|
||||
import com.tangem.features.send.v2.subcomponents.amount.SendAmountUpdateTrigger
|
||||
import com.tangem.features.send.v2.subcomponents.destination.model.transformers.SendDestinationInitialStateTransformer
|
||||
import com.tangem.features.send.v2.subcomponents.fee.SendFeeComponent
|
||||
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeUM
|
||||
|
|
@ -65,7 +68,8 @@ internal interface SendComponentCallback :
|
|||
SendAmountComponent.ModelCallback,
|
||||
SendFeeComponent.ModelCallback,
|
||||
SendDestinationComponent.ModelCallback,
|
||||
SendConfirmComponent.ModelCallback
|
||||
SendConfirmComponent.ModelCallback,
|
||||
SendConfirmSuccessComponent.ModelCallback
|
||||
|
||||
@Stable
|
||||
@ModelScoped
|
||||
|
|
@ -87,7 +91,7 @@ internal class SendModel @Inject constructor(
|
|||
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
||||
private val createTransferTransactionUseCase: CreateTransferTransactionUseCase,
|
||||
private val getFeeUseCase: GetFeeUseCase,
|
||||
private val sendAmountUpdateQRTrigger: SendAmountUpdateQRTrigger,
|
||||
private val sendAmountUpdateTrigger: SendAmountUpdateTrigger,
|
||||
private val sendFeatureToggles: SendFeatureToggles,
|
||||
) : Model(), SendComponentCallback {
|
||||
|
||||
|
|
@ -151,6 +155,15 @@ internal class SendModel @Inject constructor(
|
|||
_uiState.update { sendUM }
|
||||
}
|
||||
|
||||
override fun onConvertToAnotherToken(lastAmount: String) {
|
||||
params.callback?.onConvertToAnotherToken(lastAmount = lastAmount)
|
||||
}
|
||||
|
||||
override fun onError(error: GetUserWalletError) {
|
||||
Timber.w(error.toString())
|
||||
showAlertError()
|
||||
}
|
||||
|
||||
suspend fun loadFee(): Either<GetFeeError, TransactionFee> {
|
||||
val predefinedValues = predefinedValues
|
||||
val transferTransaction = if (predefinedValues is PredefinedValues.Content.Deeplink) {
|
||||
|
|
@ -347,7 +360,7 @@ internal class SendModel @Inject constructor(
|
|||
)
|
||||
// If it is in active state use flow to update value in amount component
|
||||
modelScope.launch {
|
||||
amount?.let { sendAmountUpdateQRTrigger.triggerUpdateAmount(it) }
|
||||
amount?.let { sendAmountUpdateTrigger.triggerUpdateAmount(it) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -382,5 +395,7 @@ internal class SendModel @Inject constructor(
|
|||
confirmUM = ConfirmUM.Empty,
|
||||
navigationUM = NavigationUM.Empty,
|
||||
isRedesignEnabled = sendFeatureToggles.isSendRedesignEnabled,
|
||||
confirmData = null,
|
||||
feeSelectorUM = FeeSelectorUM.Loading,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package com.tangem.features.send.v2.send.success
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationBlockComponent
|
||||
import com.tangem.features.send.v2.common.CommonSendRoute
|
||||
import com.tangem.features.send.v2.send.success.model.SendConfirmSuccessModel
|
||||
import com.tangem.features.send.v2.send.success.ui.SendConfirmSuccessContent
|
||||
import com.tangem.features.send.v2.send.ui.state.SendUM
|
||||
import com.tangem.features.send.v2.subcomponents.fee.SendFeeBlockComponent
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
internal class SendConfirmSuccessComponent(
|
||||
appComponentContext: AppComponentContext,
|
||||
params: Params,
|
||||
) : ComposableContentComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: SendConfirmSuccessModel = getOrCreateModel(params = params)
|
||||
private val destinationBlockComponent: SendDestinationBlockComponent = params.destinationBlockComponent
|
||||
private val feeBlockComponent: SendFeeBlockComponent = params.feeBlockComponent
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.uiState.collectAsState()
|
||||
SendConfirmSuccessContent(
|
||||
sendUM = state,
|
||||
destinationBlockComponent = destinationBlockComponent,
|
||||
feeBlockComponent = feeBlockComponent,
|
||||
)
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val sendUMFlow: StateFlow<SendUM>,
|
||||
val destinationBlockComponent: SendDestinationBlockComponent,
|
||||
val feeBlockComponent: SendFeeBlockComponent,
|
||||
val analyticsCategoryName: String,
|
||||
val currentRoute: Flow<CommonSendRoute>,
|
||||
val txUrl: String,
|
||||
val callback: ModelCallback,
|
||||
)
|
||||
|
||||
interface ModelCallback {
|
||||
fun onResult(sendUM: SendUM)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
package com.tangem.features.send.v2.send.success.model
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.common.ui.navigationButtons.NavigationButton
|
||||
import com.tangem.common.ui.navigationButtons.NavigationUM
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.navigation.share.ShareManager
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.features.send.v2.common.CommonSendRoute
|
||||
import com.tangem.features.send.v2.common.analytics.CommonSendAnalyticEvents
|
||||
import com.tangem.features.send.v2.common.analytics.CommonSendAnalyticEvents.SendScreenSource
|
||||
import com.tangem.features.send.v2.impl.R
|
||||
import com.tangem.features.send.v2.send.success.SendConfirmSuccessComponent
|
||||
import com.tangem.features.send.v2.send.ui.state.SendUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.*
|
||||
import javax.inject.Inject
|
||||
|
||||
@Stable
|
||||
@ModelScoped
|
||||
internal class SendConfirmSuccessModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val appRouter: AppRouter,
|
||||
private val urlOpener: UrlOpener,
|
||||
private val shareManager: ShareManager,
|
||||
) : Model() {
|
||||
private val params: SendConfirmSuccessComponent.Params = paramsContainer.require()
|
||||
private val _uiState = params.sendUMFlow
|
||||
val uiState = _uiState
|
||||
|
||||
init {
|
||||
configConfirmSuccessNavigation()
|
||||
}
|
||||
|
||||
private fun configConfirmSuccessNavigation() {
|
||||
combine(
|
||||
flow = uiState,
|
||||
flow2 = params.currentRoute,
|
||||
transform = { state, route -> state to route },
|
||||
).filter { it.second is CommonSendRoute.ConfirmSuccess }.onEach { (state, _) ->
|
||||
params.callback.onResult(
|
||||
state.copy(
|
||||
navigationUM = NavigationUM.Content(
|
||||
title = stringReference(""),
|
||||
subtitle = null,
|
||||
backIconRes = R.drawable.ic_close_24,
|
||||
backIconClick = {
|
||||
analyticsEventHandler.send(
|
||||
CommonSendAnalyticEvents.CloseButtonClicked(
|
||||
categoryName = params.analyticsCategoryName,
|
||||
source = SendScreenSource.Confirm,
|
||||
isFromSummary = true,
|
||||
isValid = true,
|
||||
),
|
||||
)
|
||||
appRouter.pop()
|
||||
},
|
||||
primaryButton = NavigationButton(
|
||||
textReference = resourceReference(R.string.common_close),
|
||||
iconRes = null,
|
||||
isEnabled = true,
|
||||
isHapticClick = false,
|
||||
onClick = {
|
||||
appRouter.pop()
|
||||
},
|
||||
),
|
||||
prevButton = null,
|
||||
secondaryPairButtonsUM = NavigationButton(
|
||||
textReference = resourceReference(R.string.common_explore),
|
||||
iconRes = R.drawable.ic_web_24,
|
||||
onClick = ::onExploreClick,
|
||||
) to NavigationButton(
|
||||
textReference = resourceReference(R.string.common_share),
|
||||
iconRes = R.drawable.ic_share_24,
|
||||
onClick = ::onShareClick,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun onExploreClick() {
|
||||
analyticsEventHandler.send(CommonSendAnalyticEvents.ExploreButtonClicked(params.analyticsCategoryName))
|
||||
urlOpener.openUrl(params.txUrl)
|
||||
}
|
||||
|
||||
private fun onShareClick() {
|
||||
analyticsEventHandler.send(CommonSendAnalyticEvents.ShareButtonClicked(params.analyticsCategoryName))
|
||||
shareManager.shareText(params.txUrl)
|
||||
}
|
||||
|
||||
interface ModelCallback {
|
||||
fun onResult(sendUM: SendUM)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
package com.tangem.features.send.v2.send.success.ui
|
||||
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.gestures.scrollable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.common.ui.amountScreen.ui.AmountBlock
|
||||
import com.tangem.core.ui.components.SpacerHMax
|
||||
import com.tangem.core.ui.components.transactions.TransactionDoneTitle
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.utils.DateTimeFormatters
|
||||
import com.tangem.core.ui.utils.toPx
|
||||
import com.tangem.core.ui.utils.toTimeFormat
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationBlockComponent
|
||||
import com.tangem.features.send.v2.common.ui.SendNavigationButtons
|
||||
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
|
||||
import com.tangem.features.send.v2.impl.R
|
||||
import com.tangem.features.send.v2.send.ui.state.SendUM
|
||||
import com.tangem.features.send.v2.subcomponents.fee.SendFeeBlockComponent
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
@Composable
|
||||
internal fun SendConfirmSuccessContent(
|
||||
sendUM: SendUM,
|
||||
destinationBlockComponent: SendDestinationBlockComponent,
|
||||
feeBlockComponent: SendFeeBlockComponent,
|
||||
) {
|
||||
var visible by remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
delay(ANIMATION_DELAY)
|
||||
visible = true
|
||||
}
|
||||
|
||||
val height = ANIMATION_OFFSET.toPx().toInt()
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = slideInVertically(
|
||||
initialOffsetY = { height },
|
||||
).plus(fadeIn()),
|
||||
exit = slideOutVertically().plus(fadeOut()),
|
||||
label = "Animate success content",
|
||||
) {
|
||||
Column {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.scrollable(
|
||||
state = rememberScrollState(),
|
||||
orientation = Orientation.Horizontal,
|
||||
),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
if (sendUM.confirmUM is ConfirmUM.Success) {
|
||||
TransactionDoneTitle(
|
||||
title = resourceReference(R.string.sent_transaction_sent_title),
|
||||
subtitle = resourceReference(
|
||||
R.string.send_date_format,
|
||||
wrappedList(
|
||||
sendUM.confirmUM.transactionDate.toTimeFormat(DateTimeFormatters.dateFormatter),
|
||||
sendUM.confirmUM.transactionDate.toTimeFormat(),
|
||||
),
|
||||
),
|
||||
modifier = Modifier.padding(vertical = 12.dp),
|
||||
)
|
||||
}
|
||||
AmountBlock(
|
||||
amountState = sendUM.amountUM,
|
||||
isClickDisabled = true,
|
||||
isEditingDisabled = true,
|
||||
onClick = {},
|
||||
)
|
||||
destinationBlockComponent.Content(modifier = Modifier)
|
||||
feeBlockComponent.Content(modifier = Modifier)
|
||||
}
|
||||
SpacerHMax()
|
||||
SendNavigationButtons(navigationUM = sendUM.navigationUM)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val ANIMATION_DELAY = 600L
|
||||
private val ANIMATION_OFFSET = (-40).dp
|
||||
|
|
@ -2,15 +2,19 @@ package com.tangem.features.send.v2.send.ui.state
|
|||
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
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.v2.common.ui.state.ConfirmUM
|
||||
import com.tangem.features.send.v2.send.confirm.model.ConfirmData
|
||||
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeUM
|
||||
|
||||
internal data class SendUM(
|
||||
val amountUM: AmountState,
|
||||
val destinationUM: DestinationUM,
|
||||
val feeUM: FeeUM,
|
||||
val feeSelectorUM: FeeSelectorUM,
|
||||
val confirmUM: ConfirmUM,
|
||||
val navigationUM: NavigationUM,
|
||||
val isRedesignEnabled: Boolean,
|
||||
val confirmData: ConfirmData?,
|
||||
)
|
||||
|
|
@ -30,6 +30,8 @@ import com.tangem.domain.wallets.models.requireColdWallet
|
|||
import com.tangem.features.nft.entity.NFTSendSuccessTrigger
|
||||
import com.tangem.features.send.v2.api.SendNotificationsComponent.Params.NotificationData
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationUM
|
||||
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.v2.common.CommonSendRoute
|
||||
import com.tangem.features.send.v2.common.SendBalanceUpdater
|
||||
import com.tangem.features.send.v2.common.SendConfirmAlertFactory
|
||||
|
|
@ -47,7 +49,6 @@ import com.tangem.features.send.v2.subcomponents.fee.SendFeeCheckReloadListener
|
|||
import com.tangem.features.send.v2.subcomponents.fee.SendFeeCheckReloadTrigger
|
||||
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeSelectorUM
|
||||
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeUM
|
||||
import com.tangem.features.send.v2.subcomponents.notifications.NotificationsUpdateTrigger
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.extensions.stripZeroPlainString
|
||||
import com.tangem.utils.transformer.update
|
||||
|
|
@ -72,7 +73,8 @@ internal class NFTSendConfirmModel @Inject constructor(
|
|||
private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase,
|
||||
private val getCardInfoUseCase: GetCardInfoUseCase,
|
||||
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
|
||||
private val notificationsUpdateTrigger: NotificationsUpdateTrigger,
|
||||
private val notificationsUpdateTrigger: SendNotificationsUpdateTrigger,
|
||||
private val notificationsUpdateListener: SendNotificationsUpdateListener,
|
||||
private val sendFeeCheckReloadTrigger: SendFeeCheckReloadTrigger,
|
||||
private val sendFeeCheckReloadListener: SendFeeCheckReloadListener,
|
||||
private val alertFactory: SendConfirmAlertFactory,
|
||||
|
|
@ -242,7 +244,7 @@ internal class NFTSendConfirmModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun subscribeOnNotificationsUpdateTrigger() {
|
||||
notificationsUpdateTrigger.hasErrorFlow
|
||||
notificationsUpdateListener.hasErrorFlow
|
||||
.onEach { hasError ->
|
||||
_uiState.update {
|
||||
val feeUM = it.feeUM as? FeeUM.Content
|
||||
|
|
|
|||
|
|
@ -20,9 +20,7 @@ import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
|||
import com.tangem.domain.feedback.models.BlockchainErrorInfo
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase
|
||||
import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase
|
||||
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
|
||||
import com.tangem.domain.tokens.*
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
import com.tangem.domain.transaction.usecase.CreateNFTTransferTransactionUseCase
|
||||
|
|
@ -61,6 +59,8 @@ internal class NFTSendModel @Inject constructor(
|
|||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val getCryptoCurrenciesUseCase: GetCryptoCurrenciesUseCase,
|
||||
private val multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
private val tokensFeatureToggles: TokensFeatureToggles,
|
||||
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
|
||||
private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase,
|
||||
private val createNFTTransferTransactionUseCase: CreateNFTTransferTransactionUseCase,
|
||||
|
|
@ -146,9 +146,16 @@ internal class NFTSendModel @Inject constructor(
|
|||
ifRight = { wallet ->
|
||||
userWallet = wallet
|
||||
|
||||
cryptoCurrency = getCryptoCurrenciesUseCase(userWalletId).getOrNull()
|
||||
?.filterIsInstance<CryptoCurrency.Coin>()
|
||||
?.firstOrNull { it.network == nftAsset.network }
|
||||
cryptoCurrency = if (tokensFeatureToggles.isWalletBalanceFetcherEnabled) {
|
||||
multiWalletCryptoCurrenciesSupplier.getSyncOrNull(
|
||||
params = MultiWalletCryptoCurrenciesProducer.Params(userWalletId),
|
||||
)
|
||||
?.firstOrNull { it is CryptoCurrency.Coin && it.network == nftAsset.network }
|
||||
} else {
|
||||
getCryptoCurrenciesUseCase(userWalletId).getOrNull()
|
||||
?.filterIsInstance<CryptoCurrency.Coin>()
|
||||
?.firstOrNull { it.network == nftAsset.network }
|
||||
}
|
||||
?: return@launch
|
||||
|
||||
getCurrenciesStatusUpdates(
|
||||
|
|
|
|||
|
|
@ -1,19 +1,18 @@
|
|||
package com.tangem.features.send.v2.subcomponents.amount
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.common.ui.amountScreen.AmountScreenContent
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.common.ui.navigationButtons.NavigationModelCallback
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.domain.wallets.models.GetUserWalletError
|
||||
import com.tangem.features.send.v2.subcomponents.amount.SendAmountComponentParams.AmountParams
|
||||
import com.tangem.features.send.v2.subcomponents.amount.model.SendAmountModel
|
||||
import com.tangem.features.send.v2.subcomponents.amount.ui.SendAmountContent
|
||||
|
||||
internal class SendAmountComponent(
|
||||
appComponentContext: AppComponentContext,
|
||||
|
|
@ -29,15 +28,18 @@ internal class SendAmountComponent(
|
|||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
val isBalanceHidden by params.isBalanceHidingFlow.collectAsStateWithLifecycle()
|
||||
|
||||
AmountScreenContent(
|
||||
SendAmountContent(
|
||||
amountState = state,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
clickIntents = model,
|
||||
modifier = Modifier.background(TangemTheme.colors.background.tertiary),
|
||||
isSendWithSwapEnabled = model.isSendWithSwapEnabled,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
interface ModelCallback : NavigationModelCallback {
|
||||
fun onAmountResult(amountUM: AmountState, isResetPredefined: Boolean)
|
||||
fun onConvertToAnotherToken(lastAmount: String)
|
||||
fun onError(error: GetUserWalletError)
|
||||
}
|
||||
}
|
||||
|
|
@ -29,7 +29,7 @@ interface SendAmountReduceListener {
|
|||
* Trigger amount change from another component.
|
||||
* Different from another triggers because it takes raw string instead of BigDecimal
|
||||
*/
|
||||
interface SendAmountUpdateQRTrigger {
|
||||
interface SendAmountUpdateTrigger {
|
||||
suspend fun triggerUpdateAmount(amountValue: String)
|
||||
}
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ interface SendAmountUpdateQRTrigger {
|
|||
* Trigger amount change from another component.
|
||||
* Different from another triggers because it takes raw string instead of BigDecimal
|
||||
*/
|
||||
interface SendAmountUpdateQRListener {
|
||||
interface SendAmountUpdateListener {
|
||||
val updateAmountTriggerFlow: Flow<String>
|
||||
}
|
||||
|
||||
|
|
@ -45,8 +45,8 @@ interface SendAmountUpdateQRListener {
|
|||
internal class DefaultSendAmountReduceTrigger @Inject constructor() :
|
||||
SendAmountReduceTrigger,
|
||||
SendAmountReduceListener,
|
||||
SendAmountUpdateQRTrigger,
|
||||
SendAmountUpdateQRListener {
|
||||
SendAmountUpdateTrigger,
|
||||
SendAmountUpdateListener {
|
||||
|
||||
override val reduceToTriggerFlow = MutableSharedFlow<BigDecimal>()
|
||||
override val reduceByTriggerFlow = MutableSharedFlow<ReduceByData>()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.features.send.v2.subcomponents.amount.di
|
||||
|
||||
import com.tangem.features.send.v2.subcomponents.amount.*
|
||||
import com.tangem.features.send.v2.subcomponents.amount.DefaultSendAmountReduceTrigger
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -22,9 +21,9 @@ internal interface SendAmountModule {
|
|||
|
||||
@Singleton
|
||||
@Binds
|
||||
fun provideSendAmountUpdateQRListener(impl: DefaultSendAmountReduceTrigger): SendAmountUpdateQRListener
|
||||
fun provideSendAmountUpdateListener(impl: DefaultSendAmountReduceTrigger): SendAmountUpdateListener
|
||||
|
||||
@Singleton
|
||||
@Binds
|
||||
fun provideSendAmountUpdateQRTrigger(impl: DefaultSendAmountReduceTrigger): SendAmountUpdateQRTrigger
|
||||
fun provideSendAmountUpdateTrigger(impl: DefaultSendAmountReduceTrigger): SendAmountUpdateTrigger
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.features.send.v2.subcomponents.amount.model
|
||||
|
||||
import com.tangem.common.ui.amountScreen.AmountScreenClickIntents
|
||||
|
||||
interface SendAmountClickIntents : AmountScreenClickIntents {
|
||||
|
||||
fun onConvertToAnotherToken()
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@ package com.tangem.features.send.v2.subcomponents.amount.model
|
|||
|
||||
import androidx.compose.runtime.Stable
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.common.ui.amountScreen.AmountScreenClickIntents
|
||||
import com.tangem.common.ui.amountScreen.converters.*
|
||||
import com.tangem.common.ui.amountScreen.converters.field.AmountBoundaryUpdateTransformer
|
||||
import com.tangem.common.ui.amountScreen.converters.field.AmountFieldChangeTransformer
|
||||
|
|
@ -31,7 +30,7 @@ import com.tangem.features.send.v2.api.entity.PredefinedValues
|
|||
import com.tangem.features.send.v2.impl.R
|
||||
import com.tangem.features.send.v2.subcomponents.amount.SendAmountComponentParams
|
||||
import com.tangem.features.send.v2.subcomponents.amount.SendAmountReduceListener
|
||||
import com.tangem.features.send.v2.subcomponents.amount.SendAmountUpdateQRListener
|
||||
import com.tangem.features.send.v2.subcomponents.amount.SendAmountUpdateListener
|
||||
import com.tangem.features.send.v2.subcomponents.amount.analytics.SendAmountAnalyticEvents
|
||||
import com.tangem.features.send.v2.subcomponents.amount.analytics.SendAmountAnalyticEvents.SelectedCurrencyType
|
||||
import com.tangem.features.send.v2.subcomponents.fee.SendFeeData
|
||||
|
|
@ -54,12 +53,12 @@ internal class SendAmountModel @Inject constructor(
|
|||
private val getMinimumTransactionAmountSyncUseCase: GetMinimumTransactionAmountSyncUseCase,
|
||||
private val sendAmountReduceListener: SendAmountReduceListener,
|
||||
private val feeReloadTrigger: SendFeeReloadTrigger,
|
||||
private val sendAmountUpdateQRListener: SendAmountUpdateQRListener,
|
||||
private val sendAmountUpdateListener: SendAmountUpdateListener,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val sendFeatureToggles: SendFeatureToggles,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
) : Model(), AmountScreenClickIntents {
|
||||
) : Model(), SendAmountClickIntents {
|
||||
|
||||
private val params: SendAmountComponentParams = paramsContainer.require()
|
||||
private var appCurrency: AppCurrency = AppCurrency.Default
|
||||
|
|
@ -68,6 +67,8 @@ internal class SendAmountModel @Inject constructor(
|
|||
private val _uiState = MutableStateFlow(params.state)
|
||||
val uiState = _uiState.asStateFlow()
|
||||
|
||||
val isSendWithSwapEnabled = sendFeatureToggles.isSendWithSwapEnabled
|
||||
|
||||
private val analyticsCategoryName = params.analyticsCategoryName
|
||||
private var cryptoCurrencyStatus: CryptoCurrencyStatus = CryptoCurrencyStatus(
|
||||
currency = params.cryptoCurrency,
|
||||
|
|
@ -84,14 +85,22 @@ internal class SendAmountModel @Inject constructor(
|
|||
subscribeOnAmountReduceByTriggerUpdates()
|
||||
subscribeOnAmountReduceToTriggerUpdates()
|
||||
subscribeOnAmountIgnoreReduceTriggerUpdates()
|
||||
subscribeOnAmountUpdateQRTriggerUpdates()
|
||||
subscribeOnAmountUpdateTriggerUpdates()
|
||||
}
|
||||
|
||||
private fun initAppCurrency() {
|
||||
modelScope.launch {
|
||||
userWallet = getUserWalletUseCase(params.userWalletId).getOrNull()
|
||||
appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default }
|
||||
}
|
||||
getUserWalletUseCase.invokeFlow(params.userWalletId)
|
||||
.onEach { either ->
|
||||
either.fold(
|
||||
ifLeft = { error ->
|
||||
val amountParams = params as? SendAmountComponentParams.AmountParams
|
||||
amountParams?.callback?.onError(error)
|
||||
},
|
||||
ifRight = { wallet ->
|
||||
userWallet = wallet
|
||||
},
|
||||
)
|
||||
}.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun subscribeOnCryptoCurrencyStatusFlow() {
|
||||
|
|
@ -116,6 +125,8 @@ internal class SendAmountModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default }
|
||||
|
||||
if (uiState.value is AmountState.Data) {
|
||||
_uiState.update(
|
||||
AmountBoundaryUpdateTransformer(
|
||||
|
|
@ -215,6 +226,12 @@ internal class SendAmountModel @Inject constructor(
|
|||
saveResult()
|
||||
}
|
||||
|
||||
override fun onConvertToAnotherToken() {
|
||||
val amountFieldData = uiState.value as? AmountState.Data
|
||||
val amountParams = params as? SendAmountComponentParams.AmountParams
|
||||
amountParams?.callback?.onConvertToAnotherToken(amountFieldData?.amountTextField?.value.orEmpty())
|
||||
}
|
||||
|
||||
private fun subscribeOnAmountReduceToTriggerUpdates() {
|
||||
sendAmountReduceListener.reduceToTriggerFlow
|
||||
.onEach { reduceTo ->
|
||||
|
|
@ -259,8 +276,8 @@ internal class SendAmountModel @Inject constructor(
|
|||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun subscribeOnAmountUpdateQRTriggerUpdates() {
|
||||
sendAmountUpdateQRListener.updateAmountTriggerFlow
|
||||
private fun subscribeOnAmountUpdateTriggerUpdates() {
|
||||
sendAmountUpdateListener.updateAmountTriggerFlow
|
||||
.onEach { amount ->
|
||||
onAmountValueChange(amount)
|
||||
saveResult()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,109 @@
|
|||
package com.tangem.features.send.v2.subcomponents.amount.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.common.ui.amountScreen.AmountScreenContent
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.common.ui.amountScreen.preview.AmountStatePreviewData
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.send.v2.impl.R
|
||||
import com.tangem.features.send.v2.subcomponents.amount.model.SendAmountClickIntents
|
||||
import com.tangem.features.send.v2.subcomponents.amount.ui.preview.SendAmountClickIntentsStub
|
||||
|
||||
@Composable
|
||||
fun SendAmountContent(
|
||||
amountState: AmountState,
|
||||
isBalanceHidden: Boolean,
|
||||
clickIntents: SendAmountClickIntents,
|
||||
isSendWithSwapEnabled: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier = modifier.background(TangemTheme.colors.background.tertiary)) {
|
||||
AmountScreenContent(
|
||||
amountState = amountState,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
clickIntents = clickIntents,
|
||||
)
|
||||
if (isSendWithSwapEnabled) {
|
||||
SendConvertTokenButton(
|
||||
onConvertToAnother = clickIntents::onConvertToAnotherToken,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SendConvertTokenButton(onConvertToAnother: () -> Unit) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.fillMaxWidth()
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = null,
|
||||
onClick = onConvertToAnother,
|
||||
),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(12.dp)
|
||||
.align(Alignment.Center),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(R.drawable.ic_convert_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
modifier = Modifier
|
||||
.size(20.dp)
|
||||
.background(TangemTheme.colors.control.unchecked, CircleShape)
|
||||
.padding(2.dp),
|
||||
)
|
||||
Text(
|
||||
text = stringResourceSafe(com.tangem.common.ui.R.string.send_amount_convert_to_another_token),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun SendAmountContent_Preview(@PreviewParameter(SendAmountContentPreviewProvider::class) params: AmountState) {
|
||||
TangemThemePreview {
|
||||
SendAmountContent(
|
||||
amountState = params,
|
||||
isBalanceHidden = true,
|
||||
clickIntents = SendAmountClickIntentsStub,
|
||||
isSendWithSwapEnabled = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class SendAmountContentPreviewProvider : PreviewParameterProvider<AmountState> {
|
||||
override val values: Sequence<AmountState>
|
||||
get() = sequenceOf(
|
||||
AmountStatePreviewData.amountStateV2,
|
||||
)
|
||||
}
|
||||
// endregion
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.features.send.v2.subcomponents.amount.ui.preview
|
||||
|
||||
import com.tangem.features.send.v2.subcomponents.amount.model.SendAmountClickIntents
|
||||
|
||||
internal object SendAmountClickIntentsStub : SendAmountClickIntents {
|
||||
override fun onConvertToAnotherToken() {}
|
||||
|
||||
override fun onAmountValueChange(value: String) {}
|
||||
|
||||
override fun onAmountPasteTriggerDismiss() {}
|
||||
|
||||
override fun onMaxValueClick() {}
|
||||
|
||||
override fun onCurrencyChangeClick(isFiat: Boolean) {}
|
||||
|
||||
override fun onAmountNext() {}
|
||||
}
|
||||
|
|
@ -21,8 +21,8 @@ import kotlinx.coroutines.flow.onEach
|
|||
internal class DefaultSendDestinationBlockComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted private val params: SendDestinationComponentParams.DestinationBlockParams,
|
||||
val onClick: () -> Unit,
|
||||
val onResult: (DestinationUM) -> Unit,
|
||||
@Assisted val onClick: () -> Unit,
|
||||
@Assisted val onResult: (DestinationUM) -> Unit,
|
||||
) : SendDestinationBlockComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: SendDestinationModel = getOrCreateModel(params = params)
|
||||
|
|
@ -33,7 +33,7 @@ internal class DefaultSendDestinationBlockComponent @AssistedInject constructor(
|
|||
}.launchIn(componentScope)
|
||||
}
|
||||
|
||||
fun updateState(destinationUM: DestinationUM) = model.updateState(destinationUM)
|
||||
override fun updateState(destinationUM: DestinationUM) = model.updateState(destinationUM)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
|
|
@ -54,6 +54,8 @@ internal class DefaultSendDestinationBlockComponent @AssistedInject constructor(
|
|||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: SendDestinationComponentParams.DestinationBlockParams,
|
||||
onClick: () -> Unit,
|
||||
onResult: (DestinationUM) -> Unit,
|
||||
): DefaultSendDestinationBlockComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -22,7 +22,7 @@ internal class DefaultSendDestinationComponent @AssistedInject constructor(
|
|||
|
||||
private val model: SendDestinationModel = getOrCreateModel(params = params)
|
||||
|
||||
fun updateState(state: DestinationUM) = model.updateState(state)
|
||||
override fun updateState(destinationUM: DestinationUM) = model.updateState(destinationUM)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
|
|
|
|||
|
|
@ -347,7 +347,7 @@ internal class SendDestinationModel @Inject constructor(
|
|||
params.onNextClick()
|
||||
},
|
||||
),
|
||||
prevButton = if (!route.isEditMode && isRedesignEnabled) {
|
||||
prevButton = if (!route.isEditMode && !isRedesignEnabled) {
|
||||
NavigationButton(
|
||||
textReference = TextReference.EMPTY,
|
||||
iconRes = R.drawable.ic_back_24,
|
||||
|
|
|
|||
|
|
@ -11,10 +11,10 @@ import com.tangem.core.ui.utils.parseBigDecimal
|
|||
import com.tangem.core.ui.utils.parseToBigDecimal
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.send.v2.api.entity.CustomFeeFieldUM
|
||||
import com.tangem.features.send.v2.impl.R
|
||||
import com.tangem.features.send.v2.subcomponents.fee.model.checkExceedBalance
|
||||
import com.tangem.features.send.v2.subcomponents.fee.model.converters.custom.CustomFeeConverter
|
||||
import com.tangem.features.send.v2.api.entity.CustomFeeFieldUM
|
||||
import com.tangem.lib.crypto.BlockchainUtils
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
|
@ -24,7 +24,7 @@ import java.math.RoundingMode
|
|||
|
||||
internal class BitcoinCustomFeeConverter(
|
||||
private val onCustomFeeValueChange: (Int, String) -> Unit,
|
||||
private val onNextClick: () -> Unit,
|
||||
private val onNextClick: (() -> Unit)?,
|
||||
private val appCurrency: AppCurrency,
|
||||
private val feeCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
) : CustomFeeConverter<Fee.Bitcoin> {
|
||||
|
|
@ -78,7 +78,13 @@ internal class BitcoinCustomFeeConverter(
|
|||
},
|
||||
keyboardType = KeyboardType.Companion.Number,
|
||||
),
|
||||
keyboardActions = KeyboardActions(onDone = { onNextClick() }),
|
||||
keyboardActions = KeyboardActions(
|
||||
onDone = if (onNextClick != null) {
|
||||
{ onNextClick() }
|
||||
} else {
|
||||
null
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import kotlinx.collections.immutable.toImmutableList
|
|||
|
||||
internal class EthereumCustomFeeConverter(
|
||||
private val onCustomFeeValueChange: (Int, String) -> Unit,
|
||||
private val onNextClick: () -> Unit,
|
||||
private val onNextClick: (() -> Unit)?,
|
||||
private val appCurrency: AppCurrency,
|
||||
feeCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
) : BaseEthereumCustomFeeConverter<Fee.Ethereum> {
|
||||
|
|
@ -112,7 +112,11 @@ internal class EthereumCustomFeeConverter(
|
|||
keyboardType = KeyboardType.Number,
|
||||
),
|
||||
keyboardActions = KeyboardActions(
|
||||
onDone = { onNextClick() },
|
||||
onDone = if (onNextClick != null) {
|
||||
{ onNextClick() }
|
||||
} else {
|
||||
null
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +1,17 @@
|
|||
package com.tangem.features.send.v2.subcomponents.notifications
|
||||
|
||||
import com.tangem.features.send.v2.api.SendNotificationsComponent.Params.NotificationData
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import com.tangem.features.send.v2.api.subcomponents.notifications.SendNotificationsUpdateListener
|
||||
import com.tangem.features.send.v2.api.subcomponents.notifications.SendNotificationsUpdateTrigger
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
interface NotificationsUpdateTrigger {
|
||||
/** Flow triggers notifications update */
|
||||
val updateTriggerFlow: Flow<NotificationData>
|
||||
|
||||
/** Flow returns whether there is error notifications */
|
||||
val hasErrorFlow: Flow<Boolean>
|
||||
|
||||
/** Trigger return callback with check result */
|
||||
suspend fun callbackHasError(hasError: Boolean)
|
||||
|
||||
/** Trigger fee check reload */
|
||||
suspend fun triggerUpdate(data: NotificationData)
|
||||
}
|
||||
|
||||
@Singleton
|
||||
internal class DefaultNotificationsUpdateTrigger @Inject constructor() : NotificationsUpdateTrigger {
|
||||
internal class DefaultNotificationsUpdateTrigger @Inject constructor() :
|
||||
SendNotificationsUpdateListener,
|
||||
SendNotificationsUpdateTrigger {
|
||||
|
||||
private val _updateTriggerFlow = MutableSharedFlow<NotificationData>()
|
||||
override val updateTriggerFlow = _updateTriggerFlow.asSharedFlow()
|
||||
|
|
@ -1,20 +1,23 @@
|
|||
package com.tangem.features.send.v2.subcomponents.notifications.di
|
||||
|
||||
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.v2.subcomponents.notifications.DefaultNotificationsUpdateTrigger
|
||||
import com.tangem.features.send.v2.subcomponents.notifications.NotificationsUpdateTrigger
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@Module
|
||||
internal object NotificationsModule {
|
||||
internal interface NotificationsModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesNotificationsUpdateTrigger(): NotificationsUpdateTrigger {
|
||||
return DefaultNotificationsUpdateTrigger()
|
||||
}
|
||||
@Binds
|
||||
fun bindsNotificationsUpdateTrigger(impl: DefaultNotificationsUpdateTrigger): SendNotificationsUpdateTrigger
|
||||
|
||||
@Singleton
|
||||
@Binds
|
||||
fun bindsNotificationsUpdateListener(impl: DefaultNotificationsUpdateTrigger): SendNotificationsUpdateListener
|
||||
}
|
||||
|
|
@ -37,12 +37,13 @@ import com.tangem.domain.transaction.usecase.ValidateTransactionUseCase
|
|||
import com.tangem.domain.utils.convertToSdkAmount
|
||||
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.subcomponents.notifications.SendNotificationsUpdateListener
|
||||
import com.tangem.features.send.v2.api.subcomponents.notifications.SendNotificationsUpdateTrigger
|
||||
import com.tangem.features.send.v2.subcomponents.amount.SendAmountReduceTrigger
|
||||
import com.tangem.features.send.v2.subcomponents.fee.SendFeeData
|
||||
import com.tangem.features.send.v2.subcomponents.fee.SendFeeReloadTrigger
|
||||
import com.tangem.features.send.v2.subcomponents.fee.model.checkAndCalculateSubtractedAmount
|
||||
import com.tangem.features.send.v2.subcomponents.fee.model.checkFeeCoverage
|
||||
import com.tangem.features.send.v2.subcomponents.notifications.NotificationsUpdateTrigger
|
||||
import com.tangem.features.send.v2.subcomponents.notifications.analytics.NotificationsAnalyticEvents
|
||||
import com.tangem.lib.crypto.BlockchainUtils
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isTron
|
||||
|
|
@ -74,7 +75,8 @@ internal class NotificationsModel @Inject constructor(
|
|||
private val incrementNotificationsShowCountUseCase: IncrementNotificationsShowCountUseCase,
|
||||
private val sendFeeReloadTrigger: SendFeeReloadTrigger,
|
||||
private val sendAmountReduceTrigger: SendAmountReduceTrigger,
|
||||
private val notificationsUpdateTrigger: NotificationsUpdateTrigger,
|
||||
private val notificationsUpdateTrigger: SendNotificationsUpdateTrigger,
|
||||
private val notificationsUpdateListener: SendNotificationsUpdateListener,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
) : Model() {
|
||||
|
||||
|
|
@ -101,7 +103,7 @@ internal class NotificationsModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun subscribeToNotificationUpdateTrigger() {
|
||||
notificationsUpdateTrigger.updateTriggerFlow
|
||||
notificationsUpdateListener.updateTriggerFlow
|
||||
.onEach { updateState(it) }
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,439 @@
|
|||
package com.tangem.features.send.v2.send.confirm.model.transformers
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.common.ui.amountScreen.models.AmountFieldModel
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
|
||||
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeUM
|
||||
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeSelectorUM
|
||||
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeType
|
||||
import com.tangem.features.send.v2.api.entity.CustomFeeFieldUM
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Test
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
import java.util.Locale
|
||||
import org.junit.jupiter.api.BeforeAll
|
||||
|
||||
class SendConfirmationNotificationsTransformerTest {
|
||||
|
||||
private val analyticsEventHandler: AnalyticsEventHandler = mockk(relaxed = true)
|
||||
private val cryptoCurrency: CryptoCurrency = mockk(relaxed = true)
|
||||
private val appCurrency = AppCurrency(name = "US Dollar", code = "USD", symbol = "$")
|
||||
private val analyticsCategoryName = "test_category"
|
||||
|
||||
@Test
|
||||
fun `GIVEN non content state WHEN transform THEN returns original state`() = runTest {
|
||||
// GIVEN
|
||||
val feeUM: FeeUM = mockk(relaxed = true)
|
||||
val amountUM: AmountState = mockk(relaxed = true)
|
||||
val transformer = SendConfirmationNotificationsTransformer(
|
||||
feeUM = feeUM,
|
||||
amountUM = amountUM,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
appCurrency = appCurrency,
|
||||
analyticsCategoryName = analyticsCategoryName,
|
||||
)
|
||||
val initialState: ConfirmUM = ConfirmUM.Empty
|
||||
|
||||
// WHEN
|
||||
val result = transformer.transform(initialState)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isEqualTo(initialState)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN fee UM not content WHEN transform THEN returns original state`() = runTest {
|
||||
// GIVEN
|
||||
val feeUM: FeeUM = FeeUM.Empty()
|
||||
val amountUM: AmountState = mockk(relaxed = true)
|
||||
val transformer = SendConfirmationNotificationsTransformer(
|
||||
feeUM = feeUM,
|
||||
amountUM = amountUM,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
appCurrency = appCurrency,
|
||||
analyticsCategoryName = analyticsCategoryName,
|
||||
)
|
||||
val initialState: ConfirmUM.Content = createTestConfirmUM()
|
||||
|
||||
// WHEN
|
||||
val result = transformer.transform(initialState)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isEqualTo(initialState)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN normal fee WHEN transform THEN returns state with footer and no notifications`() = runTest {
|
||||
// GIVEN
|
||||
val feeUM = createNormalFeeUM()
|
||||
val amountUM = createTestAmountUM()
|
||||
val transformer = SendConfirmationNotificationsTransformer(
|
||||
feeUM = feeUM,
|
||||
amountUM = amountUM,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
appCurrency = appCurrency,
|
||||
analyticsCategoryName = analyticsCategoryName,
|
||||
)
|
||||
val initialState = createTestConfirmUM()
|
||||
|
||||
// WHEN
|
||||
val result = transformer.transform(initialState)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isInstanceOf(ConfirmUM.Content::class.java)
|
||||
val content = result as ConfirmUM.Content
|
||||
assertThat(content.notifications).isEmpty()
|
||||
assertThat(content.sendingFooter).isNotEqualTo(initialState.sendingFooter)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN fee too high WHEN transform THEN returns state with too high notification`() = runTest {
|
||||
// GIVEN
|
||||
val feeUM = createFeeTooHighUM()
|
||||
val amountUM = createTestAmountUM()
|
||||
val transformer = SendConfirmationNotificationsTransformer(
|
||||
feeUM = feeUM,
|
||||
amountUM = amountUM,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
appCurrency = appCurrency,
|
||||
analyticsCategoryName = analyticsCategoryName,
|
||||
)
|
||||
val initialState = createTestConfirmUM()
|
||||
|
||||
// WHEN
|
||||
val result = transformer.transform(initialState)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isInstanceOf(ConfirmUM.Content::class.java)
|
||||
val content = result as ConfirmUM.Content
|
||||
assertThat(content.notifications).hasSize(1)
|
||||
assertThat(content.notifications.first()).isInstanceOf(NotificationUM.Warning.TooHigh::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN fee too low WHEN transform THEN returns state with too low notification`() = runTest {
|
||||
// GIVEN
|
||||
val feeUM = createFeeTooLowUM()
|
||||
val amountUM = createTestAmountUM()
|
||||
val transformer = SendConfirmationNotificationsTransformer(
|
||||
feeUM = feeUM,
|
||||
amountUM = amountUM,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
appCurrency = appCurrency,
|
||||
analyticsCategoryName = analyticsCategoryName,
|
||||
)
|
||||
val initialState = createTestConfirmUM()
|
||||
|
||||
// WHEN
|
||||
val result = transformer.transform(initialState)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isInstanceOf(ConfirmUM.Content::class.java)
|
||||
val content = result as ConfirmUM.Content
|
||||
assertThat(content.notifications).hasSize(1)
|
||||
assertThat(content.notifications.first()).isInstanceOf(NotificationUM.Warning.FeeTooLow::class.java)
|
||||
verify { analyticsEventHandler.send(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN both fee too high and too low WHEN transform THEN returns state with both notifications`() = runTest {
|
||||
// GIVEN
|
||||
val feeUM = createFeeTooHighAndTooLowUM()
|
||||
val amountUM = createTestAmountUM()
|
||||
val transformer = SendConfirmationNotificationsTransformer(
|
||||
feeUM = feeUM,
|
||||
amountUM = amountUM,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
appCurrency = appCurrency,
|
||||
analyticsCategoryName = analyticsCategoryName,
|
||||
)
|
||||
val initialState = createTestConfirmUM()
|
||||
|
||||
// WHEN
|
||||
val result = transformer.transform(initialState)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isInstanceOf(ConfirmUM.Content::class.java)
|
||||
val content = result as ConfirmUM.Content
|
||||
assertThat(content.notifications).hasSize(2)
|
||||
assertThat(content.notifications.any { it is NotificationUM.Warning.TooHigh }).isTrue()
|
||||
assertThat(content.notifications.any { it is NotificationUM.Warning.FeeTooLow }).isTrue()
|
||||
}
|
||||
|
||||
private fun createTestConfirmUM(): ConfirmUM.Content {
|
||||
return ConfirmUM.Content(
|
||||
isPrimaryButtonEnabled = true,
|
||||
walletName = mockk(relaxed = true),
|
||||
isSending = false,
|
||||
showTapHelp = false,
|
||||
sendingFooter = mockk(relaxed = true),
|
||||
notifications = persistentListOf(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createTestAmountUM(): AmountState.Data {
|
||||
val cryptoAmount = com.tangem.domain.tokens.model.Amount(
|
||||
currencySymbol = "SOL",
|
||||
value = BigDecimal("1.5"),
|
||||
decimals = 8,
|
||||
)
|
||||
val fiatAmount = com.tangem.domain.tokens.model.Amount(
|
||||
currencySymbol = "USD",
|
||||
value = BigDecimal("50.00"),
|
||||
decimals = 2,
|
||||
)
|
||||
|
||||
return AmountState.Data(
|
||||
isPrimaryButtonEnabled = true,
|
||||
isRedesignEnabled = false,
|
||||
title = mockk(relaxed = true),
|
||||
availableBalance = mockk(relaxed = true),
|
||||
tokenName = mockk(relaxed = true),
|
||||
tokenIconState = mockk(relaxed = true),
|
||||
segmentedButtonConfig = persistentListOf(),
|
||||
selectedButton = 0,
|
||||
isSegmentedButtonsEnabled = false,
|
||||
amountTextField = AmountFieldModel(
|
||||
value = "1.5",
|
||||
onValueChange = {},
|
||||
keyboardOptions = mockk(relaxed = true),
|
||||
keyboardActions = mockk(relaxed = true),
|
||||
cryptoAmount = cryptoAmount,
|
||||
fiatAmount = fiatAmount,
|
||||
isFiatValue = false,
|
||||
fiatValue = "50.00",
|
||||
isFiatUnavailable = false,
|
||||
isValuePasted = false,
|
||||
onValuePastedTriggerDismiss = {},
|
||||
isError = false,
|
||||
isWarning = false,
|
||||
error = mockk(relaxed = true),
|
||||
),
|
||||
appCurrency = appCurrency,
|
||||
isEditingDisabled = false,
|
||||
reduceAmountBy = BigDecimal.ZERO,
|
||||
isIgnoreReduce = false,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createNormalFeeUM(): FeeUM.Content {
|
||||
val fee = Fee.Common(
|
||||
amount = com.tangem.blockchain.common.Amount(
|
||||
currencySymbol = "SOL",
|
||||
value = BigDecimal("0.001"),
|
||||
decimals = 8,
|
||||
),
|
||||
)
|
||||
val transactionFee = TransactionFee.Single(fee)
|
||||
return FeeUM.Content(
|
||||
feeSelectorUM = FeeSelectorUM.Content(
|
||||
fees = transactionFee,
|
||||
selectedType = FeeType.Market,
|
||||
selectedFee = fee,
|
||||
customValues = persistentListOf(),
|
||||
nonce = BigInteger.ZERO,
|
||||
),
|
||||
rate = BigDecimal("50000"),
|
||||
isFeeConvertibleToFiat = true,
|
||||
isFeeApproximate = false,
|
||||
isTronToken = false,
|
||||
isEditingDisabled = false,
|
||||
isPrimaryButtonEnabled = true,
|
||||
appCurrency = AppCurrency.Default,
|
||||
isCustomSelected = false,
|
||||
notifications = persistentListOf(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createFeeTooHighUM(): FeeUM.Content {
|
||||
val priorityFee = Fee.Common(
|
||||
amount = com.tangem.blockchain.common.Amount(
|
||||
currencySymbol = "SOL",
|
||||
value = BigDecimal("0.001"),
|
||||
decimals = 8,
|
||||
),
|
||||
)
|
||||
val minimumFee = Fee.Common(
|
||||
amount = com.tangem.blockchain.common.Amount(
|
||||
currencySymbol = "SOL",
|
||||
value = BigDecimal("0.001"),
|
||||
decimals = 8,
|
||||
),
|
||||
)
|
||||
val customFee = Fee.Common(
|
||||
amount = com.tangem.blockchain.common.Amount(
|
||||
currencySymbol = "SOL",
|
||||
value = BigDecimal("0.01"),
|
||||
decimals = 8,
|
||||
),
|
||||
)
|
||||
val transactionFee = TransactionFee.Choosable(
|
||||
minimum = minimumFee,
|
||||
normal = minimumFee,
|
||||
priority = priorityFee,
|
||||
)
|
||||
return FeeUM.Content(
|
||||
feeSelectorUM = FeeSelectorUM.Content(
|
||||
fees = transactionFee,
|
||||
selectedType = FeeType.Custom,
|
||||
selectedFee = customFee,
|
||||
customValues = persistentListOf(
|
||||
CustomFeeFieldUM(
|
||||
value = "0.01",
|
||||
onValueChange = {},
|
||||
keyboardOptions = mockk(relaxed = true),
|
||||
keyboardActions = mockk(relaxed = true),
|
||||
symbol = "SOL",
|
||||
decimals = 8,
|
||||
title = mockk(relaxed = true),
|
||||
footer = mockk(relaxed = true),
|
||||
),
|
||||
),
|
||||
nonce = BigInteger.ZERO,
|
||||
),
|
||||
rate = BigDecimal("50000"),
|
||||
isFeeConvertibleToFiat = true,
|
||||
isFeeApproximate = false,
|
||||
isTronToken = false,
|
||||
isEditingDisabled = false,
|
||||
isPrimaryButtonEnabled = true,
|
||||
appCurrency = AppCurrency.Default,
|
||||
isCustomSelected = false,
|
||||
notifications = persistentListOf(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createFeeTooLowUM(): FeeUM.Content {
|
||||
val fee = Fee.Common(
|
||||
amount = com.tangem.blockchain.common.Amount(
|
||||
currencySymbol = "SOL",
|
||||
value = BigDecimal("0.0001"),
|
||||
decimals = 8,
|
||||
),
|
||||
)
|
||||
val minimumFee = Fee.Common(
|
||||
amount = com.tangem.blockchain.common.Amount(
|
||||
currencySymbol = "SOL",
|
||||
value = BigDecimal("0.001"),
|
||||
decimals = 8,
|
||||
),
|
||||
)
|
||||
val transactionFee = TransactionFee.Choosable(
|
||||
minimum = minimumFee,
|
||||
normal = fee,
|
||||
priority = fee,
|
||||
)
|
||||
return FeeUM.Content(
|
||||
feeSelectorUM = FeeSelectorUM.Content(
|
||||
fees = transactionFee,
|
||||
selectedType = FeeType.Custom,
|
||||
selectedFee = fee,
|
||||
customValues = persistentListOf(
|
||||
CustomFeeFieldUM(
|
||||
value = "0.0001",
|
||||
onValueChange = {},
|
||||
keyboardOptions = mockk(relaxed = true),
|
||||
keyboardActions = mockk(relaxed = true),
|
||||
symbol = "SOL",
|
||||
decimals = 8,
|
||||
title = mockk(relaxed = true),
|
||||
footer = mockk(relaxed = true),
|
||||
),
|
||||
),
|
||||
nonce = BigInteger.ZERO,
|
||||
),
|
||||
rate = BigDecimal("50000"),
|
||||
isFeeConvertibleToFiat = true,
|
||||
isFeeApproximate = false,
|
||||
isTronToken = false,
|
||||
isEditingDisabled = false,
|
||||
isPrimaryButtonEnabled = true,
|
||||
appCurrency = AppCurrency.Default,
|
||||
isCustomSelected = false,
|
||||
notifications = persistentListOf(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createFeeTooHighAndTooLowUM(): FeeUM.Content {
|
||||
val priorityFee = Fee.Common(
|
||||
amount = com.tangem.blockchain.common.Amount(
|
||||
currencySymbol = "SOL",
|
||||
value = BigDecimal("0.001"),
|
||||
decimals = 8,
|
||||
),
|
||||
)
|
||||
val minimumFee = Fee.Common(
|
||||
amount = com.tangem.blockchain.common.Amount(
|
||||
currencySymbol = "SOL",
|
||||
value = BigDecimal("0.01"),
|
||||
decimals = 8,
|
||||
),
|
||||
)
|
||||
val customFee = Fee.Common(
|
||||
amount = com.tangem.blockchain.common.Amount(
|
||||
currencySymbol = "SOL",
|
||||
value = BigDecimal("0.008"),
|
||||
decimals = 8,
|
||||
),
|
||||
)
|
||||
val transactionFee = TransactionFee.Choosable(
|
||||
minimum = minimumFee,
|
||||
normal = minimumFee,
|
||||
priority = priorityFee,
|
||||
)
|
||||
return FeeUM.Content(
|
||||
feeSelectorUM = FeeSelectorUM.Content(
|
||||
fees = transactionFee,
|
||||
selectedType = FeeType.Custom,
|
||||
selectedFee = customFee,
|
||||
customValues = persistentListOf(
|
||||
CustomFeeFieldUM(
|
||||
value = "0.008",
|
||||
onValueChange = {},
|
||||
keyboardOptions = mockk(relaxed = true),
|
||||
keyboardActions = mockk(relaxed = true),
|
||||
symbol = "SOL",
|
||||
decimals = 8,
|
||||
title = mockk(relaxed = true),
|
||||
footer = mockk(relaxed = true),
|
||||
),
|
||||
),
|
||||
nonce = BigInteger.ZERO,
|
||||
),
|
||||
rate = BigDecimal("50000"),
|
||||
isFeeConvertibleToFiat = true,
|
||||
isFeeApproximate = false,
|
||||
isTronToken = false,
|
||||
isEditingDisabled = false,
|
||||
isPrimaryButtonEnabled = true,
|
||||
appCurrency = AppCurrency.Default,
|
||||
isCustomSelected = false,
|
||||
notifications = persistentListOf(),
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@BeforeAll
|
||||
fun setUpLocale() {
|
||||
Locale.setDefault(Locale.US)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,505 @@
|
|||
package com.tangem.features.send.v2.send.confirm.model.transformers
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.common.ui.amountScreen.models.AmountFieldModel
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.features.send.v2.api.entity.*
|
||||
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.BeforeAll
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
import java.util.Locale
|
||||
import com.tangem.domain.tokens.model.Amount as DomainAmount
|
||||
|
||||
class SendConfirmationNotificationsTransformerV2Test {
|
||||
|
||||
private val analyticsEventHandler: AnalyticsEventHandler = mockk(relaxed = true)
|
||||
private val cryptoCurrency: CryptoCurrency = mockk(relaxed = true)
|
||||
private val appCurrency = AppCurrency(name = "US Dollar", code = "USD", symbol = "$")
|
||||
private val analyticsCategoryName = "test_category"
|
||||
|
||||
@Test
|
||||
fun `GIVEN non content state WHEN transform THEN returns original state`() = runTest {
|
||||
// GIVEN
|
||||
val feeSelectorUM: FeeSelectorUM = mockk(relaxed = true)
|
||||
val amountUM: AmountState = mockk(relaxed = true)
|
||||
val transformer = SendConfirmationNotificationsTransformerV2(
|
||||
feeSelectorUM = feeSelectorUM,
|
||||
amountUM = amountUM,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
appCurrency = appCurrency,
|
||||
analyticsCategoryName = analyticsCategoryName,
|
||||
)
|
||||
val initialState: ConfirmUM = ConfirmUM.Empty
|
||||
|
||||
// WHEN
|
||||
val result = transformer.transform(initialState)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isEqualTo(initialState)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN fee selector not content WHEN transform THEN returns original state`() = runTest {
|
||||
// GIVEN
|
||||
val feeSelectorUM: FeeSelectorUM = FeeSelectorUM.Loading
|
||||
val amountUM: AmountState = mockk(relaxed = true)
|
||||
val transformer = SendConfirmationNotificationsTransformerV2(
|
||||
feeSelectorUM = feeSelectorUM,
|
||||
amountUM = amountUM,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
appCurrency = appCurrency,
|
||||
analyticsCategoryName = analyticsCategoryName,
|
||||
)
|
||||
val initialState = createTestConfirmUM()
|
||||
|
||||
// WHEN
|
||||
val result = transformer.transform(initialState)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isEqualTo(initialState)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN normal fee WHEN transform THEN returns state with footer and no notifications`() = runTest {
|
||||
// GIVEN
|
||||
val feeSelectorUM = createNormalFeeSelectorUM()
|
||||
val amountUM = createTestAmountUM()
|
||||
val transformer = SendConfirmationNotificationsTransformerV2(
|
||||
feeSelectorUM = feeSelectorUM,
|
||||
amountUM = amountUM,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
appCurrency = appCurrency,
|
||||
analyticsCategoryName = analyticsCategoryName,
|
||||
)
|
||||
val initialState = createTestConfirmUM()
|
||||
|
||||
// WHEN
|
||||
val result = transformer.transform(initialState)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isInstanceOf(ConfirmUM.Content::class.java)
|
||||
val content = result as ConfirmUM.Content
|
||||
assertThat(content.notifications).isEmpty()
|
||||
assertThat(content.sendingFooter).isNotEqualTo(initialState.sendingFooter)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN fee too high WHEN transform THEN returns state with too high notification`() = runTest {
|
||||
// GIVEN
|
||||
val feeSelectorUM = createFeeTooHighUM()
|
||||
val amountUM = createTestAmountUM()
|
||||
val transformer = SendConfirmationNotificationsTransformerV2(
|
||||
feeSelectorUM = feeSelectorUM,
|
||||
amountUM = amountUM,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
appCurrency = appCurrency,
|
||||
analyticsCategoryName = analyticsCategoryName,
|
||||
)
|
||||
val initialState = createTestConfirmUM()
|
||||
|
||||
// WHEN
|
||||
val result = transformer.transform(initialState)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isInstanceOf(ConfirmUM.Content::class.java)
|
||||
val content = result as ConfirmUM.Content
|
||||
assertThat(content.notifications).hasSize(1)
|
||||
assertThat(content.notifications.first()).isInstanceOf(NotificationUM.Warning.TooHigh::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN fee too low WHEN transform THEN returns state with too low notification`() = runTest {
|
||||
// GIVEN
|
||||
val feeSelectorUM = createFeeTooLowUM()
|
||||
val amountUM = createTestAmountUM()
|
||||
val transformer = SendConfirmationNotificationsTransformerV2(
|
||||
feeSelectorUM = feeSelectorUM,
|
||||
amountUM = amountUM,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
appCurrency = appCurrency,
|
||||
analyticsCategoryName = analyticsCategoryName,
|
||||
)
|
||||
val initialState = createTestConfirmUM()
|
||||
|
||||
// WHEN
|
||||
val result = transformer.transform(initialState)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isInstanceOf(ConfirmUM.Content::class.java)
|
||||
val content = result as ConfirmUM.Content
|
||||
assertThat(content.notifications).hasSize(1)
|
||||
assertThat(content.notifications.first()).isInstanceOf(NotificationUM.Warning.FeeTooLow::class.java)
|
||||
verify { analyticsEventHandler.send(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN both fee too high and too low WHEN transform THEN returns state with both notifications`() = runTest {
|
||||
// GIVEN
|
||||
val feeSelectorUM = createFeeTooHighAndTooLowUM()
|
||||
val amountUM = createTestAmountUM()
|
||||
val transformer = SendConfirmationNotificationsTransformerV2(
|
||||
feeSelectorUM = feeSelectorUM,
|
||||
amountUM = amountUM,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
appCurrency = appCurrency,
|
||||
analyticsCategoryName = analyticsCategoryName,
|
||||
)
|
||||
val initialState = createTestConfirmUM()
|
||||
|
||||
// WHEN
|
||||
val result = transformer.transform(initialState)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isInstanceOf(ConfirmUM.Content::class.java)
|
||||
val content = result as ConfirmUM.Content
|
||||
assertThat(content.notifications).hasSize(2)
|
||||
assertThat(content.notifications.any { it is NotificationUM.Warning.TooHigh }).isTrue()
|
||||
assertThat(content.notifications.any { it is NotificationUM.Warning.FeeTooLow }).isTrue()
|
||||
}
|
||||
|
||||
private fun createTestConfirmUM(): ConfirmUM.Content {
|
||||
return ConfirmUM.Content(
|
||||
isPrimaryButtonEnabled = true,
|
||||
walletName = mockk(relaxed = true),
|
||||
isSending = false,
|
||||
showTapHelp = false,
|
||||
sendingFooter = mockk(relaxed = true),
|
||||
notifications = persistentListOf(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createTestAmountUM(): AmountState.Data {
|
||||
val cryptoAmount = DomainAmount(
|
||||
currencySymbol = "SOL",
|
||||
value = BigDecimal("1.5"),
|
||||
decimals = 8,
|
||||
)
|
||||
val fiatAmount = DomainAmount(
|
||||
currencySymbol = "USD",
|
||||
value = BigDecimal("50.00"),
|
||||
decimals = 2,
|
||||
)
|
||||
|
||||
return AmountState.Data(
|
||||
isPrimaryButtonEnabled = true,
|
||||
isRedesignEnabled = false,
|
||||
title = mockk(relaxed = true),
|
||||
availableBalance = mockk(relaxed = true),
|
||||
tokenName = mockk(relaxed = true),
|
||||
tokenIconState = mockk(relaxed = true),
|
||||
segmentedButtonConfig = persistentListOf(),
|
||||
selectedButton = 0,
|
||||
isSegmentedButtonsEnabled = false,
|
||||
amountTextField = AmountFieldModel(
|
||||
value = "1.5",
|
||||
onValueChange = {},
|
||||
keyboardOptions = mockk(relaxed = true),
|
||||
keyboardActions = mockk(relaxed = true),
|
||||
cryptoAmount = cryptoAmount,
|
||||
fiatAmount = fiatAmount,
|
||||
isFiatValue = false,
|
||||
fiatValue = "50.00",
|
||||
isFiatUnavailable = false,
|
||||
isValuePasted = false,
|
||||
onValuePastedTriggerDismiss = {},
|
||||
isError = false,
|
||||
isWarning = false,
|
||||
error = mockk(relaxed = true),
|
||||
),
|
||||
appCurrency = appCurrency,
|
||||
isEditingDisabled = false,
|
||||
reduceAmountBy = BigDecimal.ZERO,
|
||||
isIgnoreReduce = false,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createNormalFeeSelectorUM(): FeeSelectorUM.Content {
|
||||
val fee = Fee.Common(
|
||||
amount = Amount(
|
||||
currencySymbol = "SOL",
|
||||
value = BigDecimal("0.001"),
|
||||
decimals = 8,
|
||||
),
|
||||
)
|
||||
val transactionFee = TransactionFee.Single(fee)
|
||||
return FeeSelectorUM.Content(
|
||||
fees = transactionFee,
|
||||
feeItems = persistentListOf(FeeItem.Market(fee)),
|
||||
selectedFeeItem = FeeItem.Market(fee),
|
||||
feeExtraInfo = FeeExtraInfo(
|
||||
isFeeApproximate = false,
|
||||
isFeeConvertibleToFiat = false,
|
||||
isTronToken = false,
|
||||
),
|
||||
feeFiatRateUM = FeeFiatRateUM(
|
||||
rate = BigDecimal("50000"),
|
||||
appCurrency = appCurrency,
|
||||
),
|
||||
feeNonce = FeeNonce.Nonce(
|
||||
nonce = BigInteger.ZERO,
|
||||
onNonceChange = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createFeeTooHighUM(): FeeSelectorUM.Content {
|
||||
val priorityFee = Fee.Common(
|
||||
amount = Amount(
|
||||
currencySymbol = "SOL",
|
||||
value = BigDecimal("0.001"),
|
||||
decimals = 8,
|
||||
),
|
||||
)
|
||||
val minimumFee = Fee.Common(
|
||||
amount = Amount(
|
||||
currencySymbol = "SOL",
|
||||
value = BigDecimal("0.001"),
|
||||
decimals = 8,
|
||||
),
|
||||
)
|
||||
val transactionFee = TransactionFee.Choosable(
|
||||
minimum = minimumFee,
|
||||
normal = minimumFee,
|
||||
priority = priorityFee,
|
||||
)
|
||||
return FeeSelectorUM.Content(
|
||||
fees = transactionFee,
|
||||
feeItems = persistentListOf(
|
||||
FeeItem.Custom(
|
||||
fee = Fee.Common(
|
||||
amount = Amount(
|
||||
currencySymbol = "SOL",
|
||||
value = BigDecimal("0.01"),
|
||||
decimals = 8,
|
||||
),
|
||||
),
|
||||
customValues = persistentListOf(
|
||||
CustomFeeFieldUM(
|
||||
value = "0.01",
|
||||
onValueChange = {},
|
||||
keyboardOptions = mockk(relaxed = true),
|
||||
keyboardActions = mockk(relaxed = true),
|
||||
symbol = "SOL",
|
||||
decimals = 8,
|
||||
title = mockk(relaxed = true),
|
||||
footer = mockk(relaxed = true),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
selectedFeeItem = FeeItem.Custom(
|
||||
fee = Fee.Common(
|
||||
amount = Amount(
|
||||
currencySymbol = "SOL",
|
||||
value = BigDecimal("0.01"),
|
||||
decimals = 8,
|
||||
),
|
||||
),
|
||||
customValues = persistentListOf(
|
||||
CustomFeeFieldUM(
|
||||
value = "0.01",
|
||||
onValueChange = {},
|
||||
keyboardOptions = mockk(relaxed = true),
|
||||
keyboardActions = mockk(relaxed = true),
|
||||
symbol = "SOL",
|
||||
decimals = 8,
|
||||
title = mockk(relaxed = true),
|
||||
footer = mockk(relaxed = true),
|
||||
),
|
||||
),
|
||||
),
|
||||
feeExtraInfo = FeeExtraInfo(
|
||||
isFeeApproximate = false,
|
||||
isFeeConvertibleToFiat = false,
|
||||
isTronToken = false,
|
||||
),
|
||||
feeFiatRateUM = FeeFiatRateUM(
|
||||
rate = BigDecimal("50000"),
|
||||
appCurrency = appCurrency,
|
||||
),
|
||||
feeNonce = FeeNonce.Nonce(
|
||||
nonce = BigInteger.ZERO,
|
||||
onNonceChange = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createFeeTooLowUM(): FeeSelectorUM.Content {
|
||||
val fee = Fee.Common(
|
||||
amount = Amount(
|
||||
currencySymbol = "SOL",
|
||||
value = BigDecimal("0.0001"),
|
||||
decimals = 8,
|
||||
),
|
||||
)
|
||||
val minimumFee = Fee.Common(
|
||||
amount = Amount(
|
||||
currencySymbol = "SOL",
|
||||
value = BigDecimal("0.001"),
|
||||
decimals = 8,
|
||||
),
|
||||
)
|
||||
val transactionFee = TransactionFee.Choosable(
|
||||
minimum = minimumFee,
|
||||
normal = fee,
|
||||
priority = fee,
|
||||
)
|
||||
return FeeSelectorUM.Content(
|
||||
fees = transactionFee,
|
||||
feeItems = persistentListOf(
|
||||
FeeItem.Custom(
|
||||
fee = fee,
|
||||
customValues = persistentListOf(
|
||||
CustomFeeFieldUM(
|
||||
value = "0.0001",
|
||||
onValueChange = {},
|
||||
keyboardOptions = mockk(relaxed = true),
|
||||
keyboardActions = mockk(relaxed = true),
|
||||
symbol = "SOL",
|
||||
decimals = 8,
|
||||
title = mockk(relaxed = true),
|
||||
footer = mockk(relaxed = true),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
selectedFeeItem = FeeItem.Custom(
|
||||
fee = fee,
|
||||
customValues = persistentListOf(
|
||||
CustomFeeFieldUM(
|
||||
value = "0.0001",
|
||||
onValueChange = {},
|
||||
keyboardOptions = mockk(relaxed = true),
|
||||
keyboardActions = mockk(relaxed = true),
|
||||
symbol = "SOL",
|
||||
decimals = 8,
|
||||
title = mockk(relaxed = true),
|
||||
footer = mockk(relaxed = true),
|
||||
),
|
||||
),
|
||||
),
|
||||
feeExtraInfo = FeeExtraInfo(
|
||||
isFeeApproximate = false,
|
||||
isFeeConvertibleToFiat = false,
|
||||
isTronToken = false,
|
||||
),
|
||||
feeFiatRateUM = FeeFiatRateUM(
|
||||
rate = BigDecimal("50000"),
|
||||
appCurrency = appCurrency,
|
||||
),
|
||||
feeNonce = FeeNonce.Nonce(
|
||||
nonce = BigInteger.ZERO,
|
||||
onNonceChange = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createFeeTooHighAndTooLowUM(): FeeSelectorUM.Content {
|
||||
val priorityFee = Fee.Common(
|
||||
amount = Amount(
|
||||
currencySymbol = "SOL",
|
||||
value = BigDecimal("0.001"),
|
||||
decimals = 8,
|
||||
),
|
||||
)
|
||||
val minimumFee = Fee.Common(
|
||||
amount = Amount(
|
||||
currencySymbol = "SOL",
|
||||
value = BigDecimal("0.01"),
|
||||
decimals = 8,
|
||||
),
|
||||
)
|
||||
val transactionFee = TransactionFee.Choosable(
|
||||
minimum = minimumFee,
|
||||
normal = minimumFee,
|
||||
priority = priorityFee,
|
||||
)
|
||||
return FeeSelectorUM.Content(
|
||||
fees = transactionFee,
|
||||
feeItems = persistentListOf(
|
||||
FeeItem.Custom(
|
||||
fee = Fee.Common(
|
||||
amount = Amount(
|
||||
currencySymbol = "SOL",
|
||||
value = BigDecimal("0.008"),
|
||||
decimals = 8,
|
||||
),
|
||||
),
|
||||
customValues = persistentListOf(
|
||||
CustomFeeFieldUM(
|
||||
value = "0.008",
|
||||
onValueChange = {},
|
||||
keyboardOptions = mockk(relaxed = true),
|
||||
keyboardActions = mockk(relaxed = true),
|
||||
symbol = "SOL",
|
||||
decimals = 8,
|
||||
title = mockk(relaxed = true),
|
||||
footer = mockk(relaxed = true),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
selectedFeeItem = FeeItem.Custom(
|
||||
fee = Fee.Common(
|
||||
amount = Amount(
|
||||
currencySymbol = "SOL",
|
||||
value = BigDecimal("0.008"),
|
||||
decimals = 8,
|
||||
),
|
||||
),
|
||||
customValues = persistentListOf(
|
||||
CustomFeeFieldUM(
|
||||
value = "0.008",
|
||||
onValueChange = {},
|
||||
keyboardOptions = mockk(relaxed = true),
|
||||
keyboardActions = mockk(relaxed = true),
|
||||
symbol = "SOL",
|
||||
decimals = 8,
|
||||
title = mockk(relaxed = true),
|
||||
footer = mockk(relaxed = true),
|
||||
),
|
||||
),
|
||||
),
|
||||
feeExtraInfo = FeeExtraInfo(
|
||||
isFeeApproximate = false,
|
||||
isFeeConvertibleToFiat = false,
|
||||
isTronToken = false,
|
||||
),
|
||||
feeFiatRateUM = FeeFiatRateUM(
|
||||
rate = BigDecimal("50000"),
|
||||
appCurrency = appCurrency,
|
||||
),
|
||||
feeNonce = FeeNonce.Nonce(
|
||||
nonce = BigInteger.ZERO,
|
||||
onNonceChange = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@BeforeAll
|
||||
fun setUpLocale() {
|
||||
Locale.setDefault(Locale.US)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,870 @@
|
|||
package com.tangem.features.send.v2.send.confirm.model.transformers
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.common.ui.amountScreen.models.AmountFieldModel
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.features.send.v2.api.entity.*
|
||||
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
|
||||
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeSelectorUM
|
||||
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeType
|
||||
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeUM
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.BeforeAll
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
import java.util.Locale
|
||||
import com.tangem.domain.tokens.model.Amount as DomainAmount
|
||||
import com.tangem.features.send.v2.api.entity.FeeSelectorUM as FeeSelectorUMV2
|
||||
|
||||
class TransformersComparisonTest {
|
||||
|
||||
private val analyticsEventHandler: AnalyticsEventHandler = mockk(relaxed = true)
|
||||
private val cryptoCurrency: CryptoCurrency = mockk(relaxed = true)
|
||||
private val appCurrency = AppCurrency(name = "US Dollar", code = "USD", symbol = "$")
|
||||
private val analyticsCategoryName = "test_category"
|
||||
|
||||
@Test
|
||||
fun `GIVEN equivalent input data WHEN both transformers transform THEN they produce equal ConfirmUM`() = runTest {
|
||||
// GIVEN
|
||||
val initialConfirmUM = createTestConfirmUM()
|
||||
val amountUM = createTestAmountUM()
|
||||
|
||||
val feeUM = createTestFeeUM()
|
||||
val feeSelectorUMV2 = createTestFeeSelectorUMV2()
|
||||
|
||||
// WHEN
|
||||
val transformerV1 = SendConfirmationNotificationsTransformer(
|
||||
feeUM = feeUM,
|
||||
amountUM = amountUM,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
appCurrency = appCurrency,
|
||||
analyticsCategoryName = analyticsCategoryName,
|
||||
)
|
||||
|
||||
val transformerV2 = SendConfirmationNotificationsTransformerV2(
|
||||
feeSelectorUM = feeSelectorUMV2,
|
||||
amountUM = amountUM,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
appCurrency = appCurrency,
|
||||
analyticsCategoryName = analyticsCategoryName,
|
||||
)
|
||||
|
||||
val resultV1 = transformerV1.transform(initialConfirmUM)
|
||||
val resultV2 = transformerV2.transform(initialConfirmUM)
|
||||
|
||||
// THEN
|
||||
assertThat(resultV1).isInstanceOf(ConfirmUM.Content::class.java)
|
||||
assertThat(resultV2).isInstanceOf(ConfirmUM.Content::class.java)
|
||||
|
||||
val contentV1 = resultV1 as ConfirmUM.Content
|
||||
val contentV2 = resultV2 as ConfirmUM.Content
|
||||
|
||||
assertThat(contentV1.notifications.size).isEqualTo(contentV2.notifications.size)
|
||||
assertThat(contentV1.sendingFooter).isEqualTo(contentV2.sendingFooter)
|
||||
assertThat(contentV1.isPrimaryButtonEnabled).isEqualTo(contentV2.isPrimaryButtonEnabled)
|
||||
assertThat(contentV1.isSending).isEqualTo(contentV2.isSending)
|
||||
assertThat(contentV1.showTapHelp).isEqualTo(contentV2.showTapHelp)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN fee too low WHEN both transformers transform THEN they produce equal notifications`() = runTest {
|
||||
// GIVEN
|
||||
val initialConfirmUM = createTestConfirmUM()
|
||||
val amountUM = createTestAmountUM()
|
||||
|
||||
val feeUM = createFeeTooLowUM()
|
||||
val feeSelectorUMV2 = createFeeTooLowUMV2()
|
||||
|
||||
// WHEN
|
||||
val transformerV1 = SendConfirmationNotificationsTransformer(
|
||||
feeUM = feeUM,
|
||||
amountUM = amountUM,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
appCurrency = appCurrency,
|
||||
analyticsCategoryName = analyticsCategoryName,
|
||||
)
|
||||
|
||||
val transformerV2 = SendConfirmationNotificationsTransformerV2(
|
||||
feeSelectorUM = feeSelectorUMV2,
|
||||
amountUM = amountUM,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
appCurrency = appCurrency,
|
||||
analyticsCategoryName = analyticsCategoryName,
|
||||
)
|
||||
|
||||
val resultV1 = transformerV1.transform(initialConfirmUM)
|
||||
val resultV2 = transformerV2.transform(initialConfirmUM)
|
||||
|
||||
// THEN
|
||||
assertThat(resultV1).isInstanceOf(ConfirmUM.Content::class.java)
|
||||
assertThat(resultV2).isInstanceOf(ConfirmUM.Content::class.java)
|
||||
|
||||
val contentV1 = resultV1 as ConfirmUM.Content
|
||||
val contentV2 = resultV2 as ConfirmUM.Content
|
||||
|
||||
assertThat(contentV1.notifications).hasSize(1)
|
||||
assertThat(contentV2.notifications).hasSize(1)
|
||||
assertThat(contentV1.notifications.first()).isInstanceOf(NotificationUM.Warning.FeeTooLow::class.java)
|
||||
assertThat(contentV2.notifications.first()).isInstanceOf(NotificationUM.Warning.FeeTooLow::class.java)
|
||||
assertThat(contentV1.sendingFooter).isEqualTo(contentV2.sendingFooter)
|
||||
|
||||
// Verify analytics event was sent for both transformers
|
||||
verify(exactly = 2) { analyticsEventHandler.send(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN fee too high WHEN both transformers transform THEN they produce equal notifications`() = runTest {
|
||||
// GIVEN
|
||||
val initialConfirmUM = createTestConfirmUM()
|
||||
val amountUM = createTestAmountUM()
|
||||
|
||||
val feeUM = createFeeTooHighUM()
|
||||
val feeSelectorUMV2 = createFeeTooHighUMV2()
|
||||
|
||||
// WHEN
|
||||
val transformerV1 = SendConfirmationNotificationsTransformer(
|
||||
feeUM = feeUM,
|
||||
amountUM = amountUM,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
appCurrency = appCurrency,
|
||||
analyticsCategoryName = analyticsCategoryName,
|
||||
)
|
||||
|
||||
val transformerV2 = SendConfirmationNotificationsTransformerV2(
|
||||
feeSelectorUM = feeSelectorUMV2,
|
||||
amountUM = amountUM,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
appCurrency = appCurrency,
|
||||
analyticsCategoryName = analyticsCategoryName,
|
||||
)
|
||||
|
||||
val resultV1 = transformerV1.transform(initialConfirmUM)
|
||||
val resultV2 = transformerV2.transform(initialConfirmUM)
|
||||
|
||||
// THEN
|
||||
assertThat(resultV1).isInstanceOf(ConfirmUM.Content::class.java)
|
||||
assertThat(resultV2).isInstanceOf(ConfirmUM.Content::class.java)
|
||||
|
||||
val contentV1 = resultV1 as ConfirmUM.Content
|
||||
val contentV2 = resultV2 as ConfirmUM.Content
|
||||
|
||||
assertThat(contentV1.notifications).hasSize(1)
|
||||
assertThat(contentV2.notifications).hasSize(1)
|
||||
assertThat(contentV1.notifications.first()).isInstanceOf(NotificationUM.Warning.TooHigh::class.java)
|
||||
assertThat(contentV2.notifications.first()).isInstanceOf(NotificationUM.Warning.TooHigh::class.java)
|
||||
|
||||
val tooHighV1 = contentV1.notifications.first() as NotificationUM.Warning.TooHigh
|
||||
val tooHighV2 = contentV2.notifications.first() as NotificationUM.Warning.TooHigh
|
||||
assertThat(tooHighV1.value).isEqualTo(tooHighV2.value)
|
||||
assertThat(contentV1.sendingFooter).isEqualTo(contentV2.sendingFooter)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN fee both too high and too low WHEN both transformers transform THEN they produce equal notifications`() =
|
||||
runTest {
|
||||
// GIVEN
|
||||
val initialConfirmUM = createTestConfirmUM()
|
||||
val amountUM = createTestAmountUM()
|
||||
|
||||
val feeUM = createFeeTooHighAndTooLowUM()
|
||||
val feeSelectorUMV2 = createFeeTooHighAndTooLowUMV2()
|
||||
|
||||
// WHEN
|
||||
val transformerV1 = SendConfirmationNotificationsTransformer(
|
||||
feeUM = feeUM,
|
||||
amountUM = amountUM,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
appCurrency = appCurrency,
|
||||
analyticsCategoryName = analyticsCategoryName,
|
||||
)
|
||||
|
||||
val transformerV2 = SendConfirmationNotificationsTransformerV2(
|
||||
feeSelectorUM = feeSelectorUMV2,
|
||||
amountUM = amountUM,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
appCurrency = appCurrency,
|
||||
analyticsCategoryName = analyticsCategoryName,
|
||||
)
|
||||
|
||||
val resultV1 = transformerV1.transform(initialConfirmUM)
|
||||
val resultV2 = transformerV2.transform(initialConfirmUM)
|
||||
|
||||
// THEN
|
||||
assertThat(resultV1).isInstanceOf(ConfirmUM.Content::class.java)
|
||||
assertThat(resultV2).isInstanceOf(ConfirmUM.Content::class.java)
|
||||
|
||||
val contentV1 = resultV1 as ConfirmUM.Content
|
||||
val contentV2 = resultV2 as ConfirmUM.Content
|
||||
|
||||
assertThat(contentV1.notifications).hasSize(2)
|
||||
assertThat(contentV2.notifications).hasSize(2)
|
||||
|
||||
assertThat(contentV1.notifications.any { it is NotificationUM.Warning.FeeTooLow }).isTrue()
|
||||
assertThat(contentV1.notifications.any { it is NotificationUM.Warning.TooHigh }).isTrue()
|
||||
assertThat(contentV2.notifications.any { it is NotificationUM.Warning.FeeTooLow }).isTrue()
|
||||
assertThat(contentV2.notifications.any { it is NotificationUM.Warning.TooHigh }).isTrue()
|
||||
|
||||
assertThat(contentV1.sendingFooter).isEqualTo(contentV2.sendingFooter)
|
||||
|
||||
verify(exactly = 2) { analyticsEventHandler.send(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN normal fee WHEN both transformers transform THEN they produce equal notifications`() = runTest {
|
||||
// GIVEN
|
||||
val initialConfirmUM = createTestConfirmUM()
|
||||
val amountUM = createTestAmountUM()
|
||||
|
||||
val feeUM = createNormalFeeUM()
|
||||
val feeSelectorUMV2 = createNormalFeeSelectorUMV2()
|
||||
|
||||
// WHEN
|
||||
val transformerV1 = SendConfirmationNotificationsTransformer(
|
||||
feeUM = feeUM,
|
||||
amountUM = amountUM,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
appCurrency = appCurrency,
|
||||
analyticsCategoryName = analyticsCategoryName,
|
||||
)
|
||||
|
||||
val transformerV2 = SendConfirmationNotificationsTransformerV2(
|
||||
feeSelectorUM = feeSelectorUMV2,
|
||||
amountUM = amountUM,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
appCurrency = appCurrency,
|
||||
analyticsCategoryName = analyticsCategoryName,
|
||||
)
|
||||
|
||||
val resultV1 = transformerV1.transform(initialConfirmUM)
|
||||
val resultV2 = transformerV2.transform(initialConfirmUM)
|
||||
|
||||
// THEN
|
||||
assertThat(resultV1).isInstanceOf(ConfirmUM.Content::class.java)
|
||||
assertThat(resultV2).isInstanceOf(ConfirmUM.Content::class.java)
|
||||
|
||||
val contentV1 = resultV1 as ConfirmUM.Content
|
||||
val contentV2 = resultV2 as ConfirmUM.Content
|
||||
|
||||
assertThat(contentV1.notifications).isEmpty()
|
||||
assertThat(contentV2.notifications).isEmpty()
|
||||
assertThat(contentV1.sendingFooter).isEqualTo(contentV2.sendingFooter)
|
||||
}
|
||||
|
||||
private fun createTestConfirmUM(): ConfirmUM.Content {
|
||||
return ConfirmUM.Content(
|
||||
isPrimaryButtonEnabled = true,
|
||||
walletName = mockk(relaxed = true),
|
||||
isSending = false,
|
||||
showTapHelp = false,
|
||||
sendingFooter = mockk(relaxed = true),
|
||||
notifications = persistentListOf(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createTestAmountUM(): AmountState.Data {
|
||||
val cryptoAmount = DomainAmount(
|
||||
currencySymbol = "TST",
|
||||
value = BigDecimal("1.5"),
|
||||
decimals = 8,
|
||||
)
|
||||
val fiatAmount = DomainAmount(
|
||||
currencySymbol = "USD",
|
||||
value = BigDecimal("50.00"),
|
||||
decimals = 2,
|
||||
)
|
||||
|
||||
return AmountState.Data(
|
||||
isPrimaryButtonEnabled = true,
|
||||
isRedesignEnabled = false,
|
||||
title = mockk(relaxed = true),
|
||||
availableBalance = mockk(relaxed = true),
|
||||
tokenName = mockk(relaxed = true),
|
||||
tokenIconState = mockk(relaxed = true),
|
||||
segmentedButtonConfig = persistentListOf(),
|
||||
selectedButton = 0,
|
||||
isSegmentedButtonsEnabled = false,
|
||||
amountTextField = AmountFieldModel(
|
||||
value = "1.5",
|
||||
onValueChange = {},
|
||||
keyboardOptions = mockk(relaxed = true),
|
||||
keyboardActions = mockk(relaxed = true),
|
||||
cryptoAmount = cryptoAmount,
|
||||
fiatAmount = fiatAmount,
|
||||
isFiatValue = false,
|
||||
fiatValue = "50.00",
|
||||
isFiatUnavailable = false,
|
||||
isValuePasted = false,
|
||||
onValuePastedTriggerDismiss = {},
|
||||
isError = false,
|
||||
isWarning = false,
|
||||
error = mockk(relaxed = true),
|
||||
),
|
||||
appCurrency = appCurrency,
|
||||
isEditingDisabled = false,
|
||||
reduceAmountBy = BigDecimal.ZERO,
|
||||
isIgnoreReduce = false,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createTestFeeUM(): FeeUM.Content {
|
||||
val fee = Fee.Common(
|
||||
amount = Amount(
|
||||
currencySymbol = "TST",
|
||||
value = BigDecimal("0.001"),
|
||||
decimals = 8,
|
||||
),
|
||||
)
|
||||
val transactionFee = TransactionFee.Single(fee)
|
||||
return FeeUM.Content(
|
||||
feeSelectorUM = FeeSelectorUM.Content(
|
||||
fees = transactionFee,
|
||||
selectedType = FeeType.Market,
|
||||
selectedFee = fee,
|
||||
customValues = persistentListOf(
|
||||
CustomFeeFieldUM(
|
||||
value = "0.001",
|
||||
onValueChange = {},
|
||||
keyboardOptions = mockk(relaxed = true),
|
||||
keyboardActions = mockk(relaxed = true),
|
||||
symbol = "TST",
|
||||
decimals = 8,
|
||||
title = mockk(relaxed = true),
|
||||
footer = mockk(relaxed = true),
|
||||
),
|
||||
),
|
||||
nonce = BigInteger.ZERO,
|
||||
),
|
||||
rate = BigDecimal("50000"),
|
||||
isFeeConvertibleToFiat = true,
|
||||
isFeeApproximate = false,
|
||||
isTronToken = false,
|
||||
isEditingDisabled = false,
|
||||
isPrimaryButtonEnabled = true,
|
||||
appCurrency = AppCurrency.Default,
|
||||
isCustomSelected = false,
|
||||
notifications = persistentListOf(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createTestFeeSelectorUMV2(): FeeSelectorUMV2.Content {
|
||||
val fee = Fee.Common(
|
||||
amount = Amount(
|
||||
currencySymbol = "TST",
|
||||
value = BigDecimal("0.001"),
|
||||
decimals = 8,
|
||||
),
|
||||
)
|
||||
val transactionFee = TransactionFee.Single(fee)
|
||||
return FeeSelectorUMV2.Content(
|
||||
fees = transactionFee,
|
||||
feeItems = persistentListOf(
|
||||
FeeItem.Market(fee),
|
||||
),
|
||||
selectedFeeItem = FeeItem.Market(fee),
|
||||
feeExtraInfo = FeeExtraInfo(
|
||||
isFeeApproximate = false,
|
||||
isFeeConvertibleToFiat = false,
|
||||
isTronToken = false,
|
||||
),
|
||||
feeFiatRateUM = FeeFiatRateUM(
|
||||
rate = BigDecimal("50000"),
|
||||
appCurrency = appCurrency,
|
||||
),
|
||||
feeNonce = FeeNonce.Nonce(
|
||||
nonce = BigInteger.ZERO,
|
||||
onNonceChange = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createFeeTooLowUM(): FeeUM.Content {
|
||||
val fee = Fee.Common(
|
||||
amount = Amount(
|
||||
currencySymbol = "TST",
|
||||
value = BigDecimal("0.0001"),
|
||||
decimals = 8,
|
||||
),
|
||||
)
|
||||
val minimumFee = Fee.Common(
|
||||
amount = Amount(
|
||||
currencySymbol = "TST",
|
||||
value = BigDecimal("0.001"),
|
||||
decimals = 8,
|
||||
),
|
||||
)
|
||||
val transactionFee = TransactionFee.Choosable(
|
||||
minimum = minimumFee,
|
||||
normal = fee,
|
||||
priority = fee,
|
||||
)
|
||||
return FeeUM.Content(
|
||||
feeSelectorUM = FeeSelectorUM.Content(
|
||||
fees = transactionFee,
|
||||
selectedType = FeeType.Custom,
|
||||
selectedFee = fee,
|
||||
customValues = persistentListOf(
|
||||
CustomFeeFieldUM(
|
||||
value = "0.0001",
|
||||
onValueChange = {},
|
||||
keyboardOptions = mockk(relaxed = true),
|
||||
keyboardActions = mockk(relaxed = true),
|
||||
symbol = "TST",
|
||||
decimals = 8,
|
||||
title = mockk(relaxed = true),
|
||||
footer = mockk(relaxed = true),
|
||||
),
|
||||
),
|
||||
nonce = BigInteger.ZERO,
|
||||
),
|
||||
rate = BigDecimal("50000"),
|
||||
isFeeConvertibleToFiat = true,
|
||||
isFeeApproximate = false,
|
||||
isTronToken = false,
|
||||
isEditingDisabled = false,
|
||||
isPrimaryButtonEnabled = true,
|
||||
appCurrency = AppCurrency.Default,
|
||||
isCustomSelected = true,
|
||||
notifications = persistentListOf(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createFeeTooLowUMV2(): FeeSelectorUMV2.Content {
|
||||
val fee = Fee.Common(
|
||||
amount = Amount(
|
||||
currencySymbol = "TST",
|
||||
value = BigDecimal("0.0001"),
|
||||
decimals = 8,
|
||||
),
|
||||
)
|
||||
val minimumFee = Fee.Common(
|
||||
amount = Amount(
|
||||
currencySymbol = "TST",
|
||||
value = BigDecimal("0.001"),
|
||||
decimals = 8,
|
||||
),
|
||||
)
|
||||
val transactionFee = TransactionFee.Choosable(
|
||||
minimum = minimumFee,
|
||||
normal = fee,
|
||||
priority = fee,
|
||||
)
|
||||
return FeeSelectorUMV2.Content(
|
||||
fees = transactionFee,
|
||||
feeItems = persistentListOf(
|
||||
FeeItem.Custom(
|
||||
fee = fee,
|
||||
customValues = persistentListOf(
|
||||
CustomFeeFieldUM(
|
||||
value = "0.0001",
|
||||
onValueChange = {},
|
||||
keyboardOptions = mockk(relaxed = true),
|
||||
keyboardActions = mockk(relaxed = true),
|
||||
symbol = "TST",
|
||||
decimals = 8,
|
||||
title = mockk(relaxed = true),
|
||||
footer = mockk(relaxed = true),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
selectedFeeItem = FeeItem.Custom(
|
||||
fee = fee,
|
||||
customValues = persistentListOf(
|
||||
CustomFeeFieldUM(
|
||||
value = "0.0001",
|
||||
onValueChange = {},
|
||||
keyboardOptions = mockk(relaxed = true),
|
||||
keyboardActions = mockk(relaxed = true),
|
||||
symbol = "TST",
|
||||
decimals = 8,
|
||||
title = mockk(relaxed = true),
|
||||
footer = mockk(relaxed = true),
|
||||
),
|
||||
),
|
||||
),
|
||||
feeExtraInfo = FeeExtraInfo(
|
||||
isFeeApproximate = false,
|
||||
isFeeConvertibleToFiat = false,
|
||||
isTronToken = false,
|
||||
),
|
||||
feeFiatRateUM = FeeFiatRateUM(
|
||||
rate = BigDecimal("50000"),
|
||||
appCurrency = appCurrency,
|
||||
),
|
||||
feeNonce = FeeNonce.Nonce(
|
||||
nonce = BigInteger.ZERO,
|
||||
onNonceChange = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createFeeTooHighUM(): FeeUM.Content {
|
||||
val priorityFee = Fee.Common(
|
||||
amount = Amount(
|
||||
currencySymbol = "TST",
|
||||
value = BigDecimal("0.001"),
|
||||
decimals = 8,
|
||||
),
|
||||
)
|
||||
val minimumFee = Fee.Common(
|
||||
amount = Amount(
|
||||
currencySymbol = "TST",
|
||||
value = BigDecimal("0.001"),
|
||||
decimals = 8,
|
||||
),
|
||||
)
|
||||
val customFee = Fee.Common(
|
||||
amount = Amount(
|
||||
currencySymbol = "TST",
|
||||
value = BigDecimal("0.01"),
|
||||
decimals = 8,
|
||||
),
|
||||
)
|
||||
val transactionFee = TransactionFee.Choosable(
|
||||
minimum = minimumFee,
|
||||
normal = minimumFee,
|
||||
priority = priorityFee,
|
||||
)
|
||||
return FeeUM.Content(
|
||||
feeSelectorUM = FeeSelectorUM.Content(
|
||||
fees = transactionFee,
|
||||
selectedType = FeeType.Custom,
|
||||
selectedFee = customFee,
|
||||
customValues = persistentListOf(
|
||||
CustomFeeFieldUM(
|
||||
value = "0.01",
|
||||
onValueChange = {},
|
||||
keyboardOptions = mockk(relaxed = true),
|
||||
keyboardActions = mockk(relaxed = true),
|
||||
symbol = "TST",
|
||||
decimals = 8,
|
||||
title = mockk(relaxed = true),
|
||||
footer = mockk(relaxed = true),
|
||||
),
|
||||
),
|
||||
nonce = BigInteger.ZERO,
|
||||
),
|
||||
rate = BigDecimal("50000"),
|
||||
isFeeConvertibleToFiat = true,
|
||||
isFeeApproximate = false,
|
||||
isTronToken = false,
|
||||
isEditingDisabled = false,
|
||||
isPrimaryButtonEnabled = true,
|
||||
appCurrency = AppCurrency.Default,
|
||||
isCustomSelected = true,
|
||||
notifications = persistentListOf(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createFeeTooHighUMV2(): FeeSelectorUMV2.Content {
|
||||
val priorityFee = Fee.Common(
|
||||
amount = Amount(
|
||||
currencySymbol = "TST",
|
||||
value = BigDecimal("0.001"),
|
||||
decimals = 8,
|
||||
),
|
||||
)
|
||||
val minimumFee = Fee.Common(
|
||||
amount = Amount(
|
||||
currencySymbol = "TST",
|
||||
value = BigDecimal("0.001"),
|
||||
decimals = 8,
|
||||
),
|
||||
)
|
||||
val customFee = Fee.Common(
|
||||
amount = Amount(
|
||||
currencySymbol = "TST",
|
||||
value = BigDecimal("0.01"),
|
||||
decimals = 8,
|
||||
),
|
||||
)
|
||||
val transactionFee = TransactionFee.Choosable(
|
||||
minimum = minimumFee,
|
||||
normal = minimumFee,
|
||||
priority = priorityFee,
|
||||
)
|
||||
return FeeSelectorUMV2.Content(
|
||||
fees = transactionFee,
|
||||
feeItems = persistentListOf(
|
||||
FeeItem.Custom(
|
||||
fee = customFee,
|
||||
customValues = persistentListOf(
|
||||
CustomFeeFieldUM(
|
||||
value = "0.01",
|
||||
onValueChange = {},
|
||||
keyboardOptions = mockk(relaxed = true),
|
||||
keyboardActions = mockk(relaxed = true),
|
||||
symbol = "TST",
|
||||
decimals = 8,
|
||||
title = mockk(relaxed = true),
|
||||
footer = mockk(relaxed = true),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
selectedFeeItem = FeeItem.Custom(
|
||||
fee = customFee,
|
||||
customValues = persistentListOf(
|
||||
CustomFeeFieldUM(
|
||||
value = "0.01",
|
||||
onValueChange = {},
|
||||
keyboardOptions = mockk(relaxed = true),
|
||||
keyboardActions = mockk(relaxed = true),
|
||||
symbol = "TST",
|
||||
decimals = 8,
|
||||
title = mockk(relaxed = true),
|
||||
footer = mockk(relaxed = true),
|
||||
),
|
||||
),
|
||||
),
|
||||
feeExtraInfo = FeeExtraInfo(
|
||||
isFeeApproximate = false,
|
||||
isFeeConvertibleToFiat = false,
|
||||
isTronToken = false,
|
||||
),
|
||||
feeFiatRateUM = FeeFiatRateUM(
|
||||
rate = BigDecimal("50000"),
|
||||
appCurrency = appCurrency,
|
||||
),
|
||||
feeNonce = FeeNonce.Nonce(
|
||||
nonce = BigInteger.ZERO,
|
||||
onNonceChange = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createFeeTooHighAndTooLowUM(): FeeUM.Content {
|
||||
val priorityFee = Fee.Common(
|
||||
amount = Amount(
|
||||
currencySymbol = "TST",
|
||||
value = BigDecimal("0.001"),
|
||||
decimals = 8,
|
||||
),
|
||||
)
|
||||
val minimumFee = Fee.Common(
|
||||
amount = Amount(
|
||||
currencySymbol = "TST",
|
||||
value = BigDecimal("0.01"),
|
||||
decimals = 8,
|
||||
),
|
||||
)
|
||||
val customFee = Fee.Common(
|
||||
amount = Amount(
|
||||
currencySymbol = "TST",
|
||||
value = BigDecimal("0.008"),
|
||||
decimals = 8,
|
||||
),
|
||||
)
|
||||
val transactionFee = TransactionFee.Choosable(
|
||||
minimum = minimumFee,
|
||||
normal = minimumFee,
|
||||
priority = priorityFee,
|
||||
)
|
||||
return FeeUM.Content(
|
||||
feeSelectorUM = FeeSelectorUM.Content(
|
||||
fees = transactionFee,
|
||||
selectedType = FeeType.Custom,
|
||||
selectedFee = customFee,
|
||||
customValues = persistentListOf(
|
||||
CustomFeeFieldUM(
|
||||
value = "0.008",
|
||||
onValueChange = {},
|
||||
keyboardOptions = mockk(relaxed = true),
|
||||
keyboardActions = mockk(relaxed = true),
|
||||
symbol = "TST",
|
||||
decimals = 8,
|
||||
title = mockk(relaxed = true),
|
||||
footer = mockk(relaxed = true),
|
||||
),
|
||||
),
|
||||
nonce = BigInteger.ZERO,
|
||||
),
|
||||
rate = BigDecimal("50000"),
|
||||
isFeeConvertibleToFiat = true,
|
||||
isFeeApproximate = false,
|
||||
isTronToken = false,
|
||||
isEditingDisabled = false,
|
||||
isPrimaryButtonEnabled = true,
|
||||
appCurrency = AppCurrency.Default,
|
||||
isCustomSelected = true,
|
||||
notifications = persistentListOf(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createFeeTooHighAndTooLowUMV2(): FeeSelectorUMV2.Content {
|
||||
val priorityFee = Fee.Common(
|
||||
amount = Amount(
|
||||
currencySymbol = "TST",
|
||||
value = BigDecimal("0.001"),
|
||||
decimals = 8,
|
||||
),
|
||||
)
|
||||
val minimumFee = Fee.Common(
|
||||
amount = Amount(
|
||||
currencySymbol = "TST",
|
||||
value = BigDecimal("0.01"),
|
||||
decimals = 8,
|
||||
),
|
||||
)
|
||||
val customFee = Fee.Common(
|
||||
amount = Amount(
|
||||
currencySymbol = "TST",
|
||||
value = BigDecimal("0.008"),
|
||||
decimals = 8,
|
||||
),
|
||||
)
|
||||
val transactionFee = TransactionFee.Choosable(
|
||||
minimum = minimumFee,
|
||||
normal = minimumFee,
|
||||
priority = priorityFee,
|
||||
)
|
||||
return FeeSelectorUMV2.Content(
|
||||
fees = transactionFee,
|
||||
feeItems = persistentListOf(
|
||||
FeeItem.Custom(
|
||||
fee = customFee,
|
||||
customValues = persistentListOf(
|
||||
CustomFeeFieldUM(
|
||||
value = "0.008",
|
||||
onValueChange = {},
|
||||
keyboardOptions = mockk(relaxed = true),
|
||||
keyboardActions = mockk(relaxed = true),
|
||||
symbol = "TST",
|
||||
decimals = 8,
|
||||
title = mockk(relaxed = true),
|
||||
footer = mockk(relaxed = true),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
selectedFeeItem = FeeItem.Custom(
|
||||
fee = customFee,
|
||||
customValues = persistentListOf(
|
||||
CustomFeeFieldUM(
|
||||
value = "0.008",
|
||||
onValueChange = {},
|
||||
keyboardOptions = mockk(relaxed = true),
|
||||
keyboardActions = mockk(relaxed = true),
|
||||
symbol = "TST",
|
||||
decimals = 8,
|
||||
title = mockk(relaxed = true),
|
||||
footer = mockk(relaxed = true),
|
||||
),
|
||||
),
|
||||
),
|
||||
feeExtraInfo = FeeExtraInfo(
|
||||
isFeeApproximate = false,
|
||||
isFeeConvertibleToFiat = false,
|
||||
isTronToken = false,
|
||||
),
|
||||
feeFiatRateUM = FeeFiatRateUM(
|
||||
rate = BigDecimal("50000"),
|
||||
appCurrency = appCurrency,
|
||||
),
|
||||
feeNonce = FeeNonce.Nonce(
|
||||
nonce = BigInteger.ZERO,
|
||||
onNonceChange = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createNormalFeeUM(): FeeUM.Content {
|
||||
val fee = Fee.Common(
|
||||
amount = Amount(
|
||||
currencySymbol = "TST",
|
||||
value = BigDecimal("0.001"),
|
||||
decimals = 8,
|
||||
),
|
||||
)
|
||||
val transactionFee = TransactionFee.Single(fee)
|
||||
return FeeUM.Content(
|
||||
feeSelectorUM = FeeSelectorUM.Content(
|
||||
fees = transactionFee,
|
||||
selectedType = FeeType.Market,
|
||||
selectedFee = fee,
|
||||
customValues = persistentListOf(
|
||||
CustomFeeFieldUM(
|
||||
value = "0.001",
|
||||
onValueChange = {},
|
||||
keyboardOptions = mockk(relaxed = true),
|
||||
keyboardActions = mockk(relaxed = true),
|
||||
symbol = "TST",
|
||||
decimals = 8,
|
||||
title = mockk(relaxed = true),
|
||||
footer = mockk(relaxed = true),
|
||||
),
|
||||
),
|
||||
nonce = BigInteger.ZERO,
|
||||
),
|
||||
rate = BigDecimal("50000"),
|
||||
isFeeConvertibleToFiat = true,
|
||||
isFeeApproximate = false,
|
||||
isTronToken = false,
|
||||
isEditingDisabled = false,
|
||||
isPrimaryButtonEnabled = true,
|
||||
appCurrency = AppCurrency.Default,
|
||||
isCustomSelected = false,
|
||||
notifications = persistentListOf(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createNormalFeeSelectorUMV2(): FeeSelectorUMV2.Content {
|
||||
val fee = Fee.Common(
|
||||
amount = Amount(
|
||||
currencySymbol = "TST",
|
||||
value = BigDecimal("0.001"),
|
||||
decimals = 8,
|
||||
),
|
||||
)
|
||||
val transactionFee = TransactionFee.Single(fee)
|
||||
return FeeSelectorUMV2.Content(
|
||||
fees = transactionFee,
|
||||
feeItems = persistentListOf(
|
||||
FeeItem.Market(fee),
|
||||
),
|
||||
selectedFeeItem = FeeItem.Market(fee),
|
||||
feeExtraInfo = FeeExtraInfo(
|
||||
isFeeApproximate = false,
|
||||
isFeeConvertibleToFiat = false,
|
||||
isTronToken = false,
|
||||
),
|
||||
feeFiatRateUM = FeeFiatRateUM(
|
||||
rate = BigDecimal("50000"),
|
||||
appCurrency = appCurrency,
|
||||
),
|
||||
feeNonce = FeeNonce.Nonce(
|
||||
nonce = BigInteger.ZERO,
|
||||
onNonceChange = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@BeforeAll
|
||||
fun setUpLocale() {
|
||||
Locale.setDefault(Locale.US)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue