diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0c9d9ac315..c48f118fe4 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -177,8 +177,6 @@ dependencies { implementation(projects.features.tokendetails.impl) implementation(projects.features.manageTokens.api) implementation(projects.features.manageTokens.impl) - implementation(projects.features.send.api) - implementation(projects.features.send.impl) implementation(projects.features.sendV2.api) implementation(projects.features.sendV2.impl) implementation(projects.features.qrScanning.api) diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index f1cd71bbe3..238574faa4 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -16,14 +16,14 @@ import com.tangem.features.nft.component.NFTComponent import com.tangem.features.onboarding.v2.entry.OnboardingEntryComponent import com.tangem.features.onramp.component.* import com.tangem.features.pushnotifications.api.PushNotificationsComponent -import com.tangem.features.send.api.SendComponent import com.tangem.features.send.v2.api.NFTSendComponent -import com.tangem.features.send.v2.api.SendFeatureToggles +import com.tangem.features.send.v2.api.SendComponent import com.tangem.features.staking.api.StakingComponent import com.tangem.features.swap.SwapComponent import com.tangem.features.tester.api.TesterRouter import com.tangem.features.tokendetails.TokenDetailsComponent import com.tangem.features.wallet.WalletEntryComponent +import com.tangem.features.walletconnect.components.WalletConnectEntryComponent import com.tangem.features.walletconnect.components.WalletConnectFeatureToggles import com.tangem.tap.features.details.ui.appcurrency.api.AppCurrencySelectorComponent import com.tangem.tap.features.details.ui.appsettings.api.AppSettingsComponent @@ -55,7 +55,6 @@ internal class ChildFactory @Inject constructor( private val onboardingEntryComponentFactory: OnboardingEntryComponent.Factory, private val welcomeComponentFactory: WelcomeComponent.Factory, private val storiesComponentFactory: StoriesComponent.Factory, - private val sendComponentFactory: SendComponent.Factory, private val stakingComponentFactory: StakingComponent.Factory, private val swapComponentFactory: SwapComponent.Factory, private val homeComponentFactory: HomeComponent.Factory, @@ -71,9 +70,8 @@ internal class ChildFactory @Inject constructor( private val referralComponentFactory: ReferralComponent.Factory, private val pushNotificationsComponentFactory: PushNotificationsComponent.Factory, private val walletComponentFactory: WalletEntryComponent.Factory, - private val sendComponentFactoryV2: com.tangem.features.send.v2.api.SendComponent.Factory, - private val sendFeatureToggles: SendFeatureToggles, - private val redesignedWalletConnectComponentFactory: RedesignedWalletConnectComponent.Factory, + private val sendComponentFactoryV2: SendComponent.Factory, + private val redesignedWalletConnectComponentFactory: WalletConnectEntryComponent.Factory, private val nftComponentFactory: NFTComponent.Factory, private val nftSendComponentFactory: NFTSendComponent.Factory, private val testerRouter: TesterRouter, @@ -252,33 +250,18 @@ internal class ChildFactory @Inject constructor( ) } is AppRoute.Send -> { - if (sendFeatureToggles.isSendV2Enabled) { - createComponentChild( - context = context, - params = com.tangem.features.send.v2.api.SendComponent.Params( - userWalletId = route.userWalletId, - currency = route.currency, - transactionId = route.transactionId, - amount = route.amount, - tag = route.tag, - destinationAddress = route.destinationAddress, - ), - componentFactory = sendComponentFactoryV2, - ) - } else { - createComponentChild( - context = context, - params = SendComponent.Params( - userWalletId = route.userWalletId, - currency = route.currency, - transactionId = route.transactionId, - amount = route.amount, - tag = route.tag, - destinationAddress = route.destinationAddress, - ), - componentFactory = sendComponentFactory, - ) - } + createComponentChild( + context = context, + params = SendComponent.Params( + userWalletId = route.userWalletId, + currency = route.currency, + transactionId = route.transactionId, + amount = route.amount, + tag = route.tag, + destinationAddress = route.destinationAddress, + ), + componentFactory = sendComponentFactoryV2, + ) } is AppRoute.Home -> { createComponentChild( diff --git a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json index 71ae6b7be1..7f4fdfcc91 100644 --- a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json @@ -43,10 +43,6 @@ "name": "NEW_ATTESTATION_ENABLED", "version": "5.24.0" }, - { - "name": "SEND_V2_ENABLED", - "version": "5.23.0" - }, { "name": "WALLET_CONNECT_REDESIGN_ENABLED", "version": "undefined" diff --git a/domain/tokens/build.gradle.kts b/domain/tokens/build.gradle.kts index f24fb5baf6..072542bfb0 100644 --- a/domain/tokens/build.gradle.kts +++ b/domain/tokens/build.gradle.kts @@ -31,7 +31,6 @@ dependencies { implementation(projects.domain.quotes) /** Project - Api */ - implementation(projects.features.send.api) implementation(projects.features.staking.api) implementation(projects.features.markets.api) implementation(projects.features.swap.api) diff --git a/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/SendFeatureToggles.kt b/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/SendFeatureToggles.kt index 14b404270d..4dbf452662 100644 --- a/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/SendFeatureToggles.kt +++ b/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/SendFeatureToggles.kt @@ -1,5 +1,3 @@ package com.tangem.features.send.v2.api -interface SendFeatureToggles { - val isSendV2Enabled: Boolean -} \ No newline at end of file +interface SendFeatureToggles \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/DefaultSendFeatureToggles.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/DefaultSendFeatureToggles.kt index a36e1cfa6a..326f72db7e 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/DefaultSendFeatureToggles.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/DefaultSendFeatureToggles.kt @@ -4,8 +4,5 @@ import com.tangem.core.configtoggle.feature.FeatureTogglesManager import com.tangem.features.send.v2.api.SendFeatureToggles internal class DefaultSendFeatureToggles( - private val featureToggles: FeatureTogglesManager, -) : SendFeatureToggles { - override val isSendV2Enabled: Boolean - get() = featureToggles.isFeatureEnabled("SEND_V2_ENABLED") -} \ No newline at end of file + @Suppress("UnusedPrivateMember") private val featureToggles: FeatureTogglesManager, +) : SendFeatureToggles \ No newline at end of file diff --git a/features/send/api/.gitignore b/features/send/api/.gitignore deleted file mode 100644 index 796b96d1c4..0000000000 --- a/features/send/api/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/features/send/api/build.gradle.kts b/features/send/api/build.gradle.kts deleted file mode 100644 index e5ec92a053..0000000000 --- a/features/send/api/build.gradle.kts +++ /dev/null @@ -1,24 +0,0 @@ -plugins { - alias(deps.plugins.android.library) - alias(deps.plugins.kotlin.android) - id("kotlin-parcelize") - id("configuration") -} - -android { - namespace = "com.tangem.features.send.api" -} - -dependencies { - /** Core */ - implementation(projects.core.decompose) - implementation(projects.core.ui) - - /** Domain models */ - api(projects.domain.models) - implementation(projects.domain.wallets.models) - implementation(projects.domain.tokens.models) - - /** AndroidX */ - implementation(deps.androidx.fragment.ktx) -} \ No newline at end of file diff --git a/features/send/api/src/main/kotlin/com/tangem/features/send/api/SendComponent.kt b/features/send/api/src/main/kotlin/com/tangem/features/send/api/SendComponent.kt deleted file mode 100644 index 0c1a2e271d..0000000000 --- a/features/send/api/src/main/kotlin/com/tangem/features/send/api/SendComponent.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.tangem.features.send.api - -import com.tangem.core.decompose.factory.ComponentFactory -import com.tangem.core.ui.decompose.ComposableContentComponent -import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.domain.wallets.models.UserWalletId - -interface SendComponent : ComposableContentComponent { - - data class Params( - val userWalletId: UserWalletId, - val currency: CryptoCurrency, - val transactionId: String? = null, - val amount: String? = null, - val tag: String? = null, - val destinationAddress: String? = null, - ) - - interface Factory : ComponentFactory -} \ No newline at end of file diff --git a/features/send/impl/.gitignore b/features/send/impl/.gitignore deleted file mode 100644 index 42afabfd2a..0000000000 --- a/features/send/impl/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/features/send/impl/build.gradle.kts b/features/send/impl/build.gradle.kts deleted file mode 100644 index 077fefbe77..0000000000 --- a/features/send/impl/build.gradle.kts +++ /dev/null @@ -1,94 +0,0 @@ -plugins { - alias(deps.plugins.android.library) - alias(deps.plugins.kotlin.android) - alias(deps.plugins.kotlin.kapt) - alias(deps.plugins.hilt.android) - id("configuration") -} - -android { - namespace = "com.tangem.features.send.impl" -} - -dependencies { - /** AndroidX */ - implementation(deps.androidx.fragment.ktx) - implementation(deps.androidx.appCompat) - implementation(deps.androidx.paging.runtime) - - /** Other dependencies */ - implementation(deps.kotlin.immutable.collections) - implementation(deps.material) - implementation(deps.arrow.core) - implementation(deps.lifecycle.compose) - implementation(deps.jodatime) - implementation(deps.timber) - implementation(deps.reKotlin) - implementation(deps.kotlin.serialization) - - /** Compose */ - implementation(deps.compose.accompanist.systemUiController) - implementation(deps.compose.material3) - implementation(deps.compose.material) - implementation(deps.compose.foundation) - implementation(deps.compose.ui) - implementation(deps.compose.ui.tooling) - implementation(deps.compose.navigation) - implementation(deps.compose.navigation.hilt) - implementation(deps.compose.paging) - implementation(deps.compose.constraintLayout) - - /** Tangem SDKs */ - implementation(tangemDeps.card.core) - implementation(tangemDeps.blockchain) - - /** Core modules */ - implementation(projects.core.configToggles) - implementation(projects.core.ui) - implementation(projects.core.utils) - implementation(projects.core.navigation) - implementation(projects.core.analytics) - implementation(projects.core.analytics.models) - implementation(projects.core.datasource) - implementation(projects.core.decompose) - - /** Common */ - implementation(projects.common.ui) - implementation(projects.common.routing) - - /** Libs */ - implementation(projects.libs.crypto) - - /** Domain modules */ - implementation(projects.domain.models) - implementation(projects.domain.legacy) - implementation(projects.libs.blockchainSdk) - implementation(projects.domain.tokens) - implementation(projects.domain.tokens.models) - implementation(projects.domain.wallets) - implementation(projects.domain.wallets.models) - implementation(projects.domain.appCurrency) - implementation(projects.domain.appCurrency.models) - implementation(projects.domain.txhistory) - implementation(projects.domain.txhistory.models) - implementation(projects.domain.transaction) - implementation(projects.domain.transaction.models) - implementation(projects.domain.card) - implementation(projects.domain.balanceHiding) - implementation(projects.domain.balanceHiding.models) - implementation(projects.domain.feedback) - implementation(projects.domain.qrScanning) - implementation(projects.domain.qrScanning.models) - implementation(projects.domain.settings) - implementation(projects.domain.notifications) - - /** Feature modules */ - implementation(projects.features.send.api) - implementation(projects.features.tokendetails.api) - implementation(projects.features.txhistory.api) - implementation(projects.features.qrScanning.api) - - /** DI */ - implementation(deps.hilt.android) - kapt(deps.hilt.kapt) -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/DefaultSendComponent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/DefaultSendComponent.kt deleted file mode 100644 index e08ff5f208..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/DefaultSendComponent.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.tangem.features.send.impl - -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.model.getOrCreateModel -import com.tangem.features.send.api.SendComponent -import com.tangem.features.send.impl.presentation.model.SendModel -import com.tangem.features.send.impl.presentation.ui.SendScreen -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject - -internal class DefaultSendComponent @AssistedInject constructor( - @Assisted appComponentContext: AppComponentContext, - @Assisted params: SendComponent.Params, -) : SendComponent, AppComponentContext by appComponentContext { - - private val model: SendModel = getOrCreateModel(params) - - @Composable - override fun Content(modifier: Modifier) { - val currentState = model.stateRouter.currentState.collectAsStateWithLifecycle() - val uiState by model.uiState.collectAsStateWithLifecycle() - - SendScreen(uiState, currentState.value) - } - - @AssistedFactory - interface Factory : SendComponent.Factory { - override fun create(context: AppComponentContext, params: SendComponent.Params): DefaultSendComponent - } -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/di/SendModule.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/di/SendModule.kt deleted file mode 100644 index 86c1ce5b9f..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/di/SendModule.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.tangem.features.send.impl.di - -import com.tangem.core.decompose.di.ModelScoped -import com.tangem.core.decompose.di.ModelComponent -import com.tangem.core.decompose.model.Model -import com.tangem.features.send.api.SendComponent -import com.tangem.features.send.impl.DefaultSendComponent -import com.tangem.features.send.impl.navigation.DefaultSendRouter -import com.tangem.features.send.impl.navigation.InnerSendRouter -import com.tangem.features.send.impl.presentation.model.SendModel -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import dagger.multibindings.ClassKey -import dagger.multibindings.IntoMap - -@Module -@InstallIn(SingletonComponent::class) -internal interface SendModule { - - @Binds - fun bindComponentFactory(factory: DefaultSendComponent.Factory): SendComponent.Factory - - @Binds - @IntoMap - @ClassKey(SendModel::class) - fun bindModel(model: SendModel): Model -} - -@Module -@InstallIn(ModelComponent::class) -internal interface SendModelModule { - - @Binds - @ModelScoped - fun bindRouter(router: DefaultSendRouter): InnerSendRouter -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/navigation/DefaultSendRouter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/navigation/DefaultSendRouter.kt deleted file mode 100644 index 888e2fc158..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/navigation/DefaultSendRouter.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.tangem.features.send.impl.navigation - -import com.tangem.common.routing.AppRoute -import com.tangem.common.routing.AppRouter -import com.tangem.core.decompose.di.ModelScoped -import com.tangem.core.navigation.url.UrlOpener -import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.domain.wallets.models.UserWalletId -import javax.inject.Inject - -@ModelScoped -internal class DefaultSendRouter @Inject constructor( - private val router: AppRouter, - private val urlOpener: UrlOpener, -) : InnerSendRouter { - - override fun openUrl(url: String) { - urlOpener.openUrl(url) - } - - override fun openTokenDetails(userWalletId: UserWalletId, currency: CryptoCurrency) { - router.pop { isSuccess -> - if (isSuccess) { - router.push( - AppRoute.CurrencyDetails( - userWalletId = userWalletId, - currency = currency, - ), - ) - } - } - } - - override fun openQrCodeScanner(network: String) { - router.push( - AppRoute.QrScanning(source = AppRoute.QrScanning.Source.Send(network)), - ) - } -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/navigation/InnerSendRouter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/navigation/InnerSendRouter.kt deleted file mode 100644 index a966c138e3..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/navigation/InnerSendRouter.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.tangem.features.send.impl.navigation - -import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.domain.wallets.models.UserWalletId - -interface InnerSendRouter { - - /** Open website by [url] */ - fun openUrl(url: String) - - /** Open token details screen by [userWalletId] and [currency] */ - fun openTokenDetails(userWalletId: UserWalletId, currency: CryptoCurrency) - - /** Open QR code scanner screen */ - fun openQrCodeScanner(network: String) -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/SendAnalyticEvents.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/SendAnalyticEvents.kt deleted file mode 100644 index 16070eefb1..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/SendAnalyticEvents.kt +++ /dev/null @@ -1,160 +0,0 @@ -package com.tangem.features.send.impl.presentation.analytics - -import com.tangem.core.analytics.models.AnalyticsEvent -import com.tangem.core.analytics.models.AnalyticsParam -import com.tangem.core.analytics.models.AnalyticsParam.Key.BLOCKCHAIN -import com.tangem.core.analytics.models.AnalyticsParam.Key.FEE_TYPE -import com.tangem.core.analytics.models.AnalyticsParam.Key.SOURCE -import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM -import com.tangem.core.analytics.models.AnalyticsParam.Key.TYPE -import com.tangem.core.analytics.models.AnalyticsParam.Key.VALIDATION -import com.tangem.core.analytics.models.AnalyticsParam.OnOffState - -/** - * Send screen analytics - */ -internal sealed class SendAnalyticEvents( - event: String, - params: Map = mapOf(), -) : AnalyticsEvent(category = "Token / Send", event = event, params = params) { - - /** Close button clicked */ - data class CloseButtonClicked( - val source: SendScreenSource, - val isFromSummary: Boolean, - val isValid: Boolean, - ) : SendAnalyticEvents( - event = "Button - Close", - params = mapOf( - SOURCE to source.name, - "FromSummary" to if (isFromSummary) "Yes" else "No", - "isValid" to if (isValid) "Yes" else "No", - ), - ) - - // region Address - /** Recipient address screen opened */ - data object AddressScreenOpened : SendAnalyticEvents(event = "Address Screen Opened") - - /** Address to send entered */ - data class AddressEntered(val source: EnterAddressSource, val isValid: Boolean) : SendAnalyticEvents( - event = "Address Entered", - params = mapOf( - SOURCE to source.name, - VALIDATION to if (isValid) "Success" else "Fail", - ), - ) - - /** Qr Code button clicked */ - data object QrCodeButtonClicked : SendAnalyticEvents(event = "Button - QR Code") - // endregion - - // region Amount - /** Amount screen opened */ - data object AmountScreenOpened : SendAnalyticEvents(event = "Amount Screen Opened") - - /** Selected currency */ - data class SelectedCurrency(val type: SelectedCurrencyType) : SendAnalyticEvents( - event = "Selected Currency", - params = mapOf(TYPE to type.value), - ) - - /** Max amount button clicked */ - data object MaxAmountButtonClicked : SendAnalyticEvents(event = "Max Amount Taped") - // endregion - - // region Fee - /** Fee screen opened */ - data object FeeScreenOpened : SendAnalyticEvents(event = "Fee Screen Opened") - - /** Selected fee (send after next screen opened) */ - data class SelectedFee(val feeType: AnalyticsParam.FeeType) : SendAnalyticEvents( - event = "Fee Selected", - params = mapOf("Fee Type" to feeType.value), - ) - - /** Custom fee selected */ - data object CustomFeeButtonClicked : SendAnalyticEvents(event = "Custom Fee Clicked") - - /** Custom fee edited */ - data object GasPriceInserter : SendAnalyticEvents(event = "Gas Price Inserted") - - /** Subtract from amount selector switched (send after next screen opened) */ - data class SubtractFromAmount(val status: Boolean) : SendAnalyticEvents( - event = "Subtract from Amount", - params = mapOf("Status" to if (status) OnOffState.On.value else OnOffState.Off.value), - ) - // endregion - - // region Confirmation - /** Confirmation screen opened */ - data object ConfirmationScreenOpened : SendAnalyticEvents(event = "Confirm Screen Opened") - - /** Screen reopened from confirmation screen */ - data class ScreenReopened(val source: SendScreenSource) : SendAnalyticEvents( - event = "Screen Reopened", - params = mapOf(SOURCE to source.name), - ) - // endregion - - // region Transaction Result - /** Transaction send screen opened */ - data class TransactionScreenOpened( - val token: String, - val feeType: AnalyticsParam.FeeType, - ) : SendAnalyticEvents( - event = "Transaction Sent Screen Opened", - params = mapOf( - TOKEN_PARAM to token, - FEE_TYPE to feeType.value, - ), - ) - - /** Share button clicked */ - data object ShareButtonClicked : SendAnalyticEvents(event = "Button - Share") - - /** Expore button clicked */ - data object ExploreButtonClicked : SendAnalyticEvents(event = "Button - Explore") - - /** If not enough fee notification is present */ - data class NoticeNotEnoughFee(val token: String, val blockchain: String) : SendAnalyticEvents( - event = "Notice - Not Enough Fee", - params = mapOf(TOKEN_PARAM to token, BLOCKCHAIN to blockchain), - ) - - /** If transaction delays notification is present */ - data class NoticeTransactionDelays(val token: String) : SendAnalyticEvents( - event = "Notice - Transaction Delays Are Possible", - params = mapOf(TOKEN_PARAM to token), - ) - - data object NoticeFeeCoverage : SendAnalyticEvents( - event = "Notice - Network Fee Coverage", - ) - - /** If error occurs during send transactions */ - data class TransactionError(val token: String) : SendAnalyticEvents( - event = "Error - Transaction Rejected", - params = mapOf(TOKEN_PARAM to token), - ) - // endregion -} - -internal enum class SendScreenSource { - Address, - Amount, - Fee, - Confirm, -} - -internal enum class EnterAddressSource { - QRCode, - PasteButton, - RecentAddress, - MyWallet, -} - -internal enum class SelectedCurrencyType(val value: String) { - Token("Token"), - AppCurrency("App Currency"), -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendScreenAnalyticSender.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendScreenAnalyticSender.kt deleted file mode 100644 index f949326670..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendScreenAnalyticSender.kt +++ /dev/null @@ -1,134 +0,0 @@ -package com.tangem.features.send.impl.presentation.analytics.utils - -import com.tangem.blockchain.common.transaction.TransactionFee -import com.tangem.common.ui.amountScreen.models.AmountState -import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.core.analytics.models.AnalyticsParam -import com.tangem.core.analytics.models.Basic -import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.features.send.impl.presentation.analytics.SelectedCurrencyType -import com.tangem.features.send.impl.presentation.analytics.SendAnalyticEvents -import com.tangem.features.send.impl.presentation.analytics.SendScreenSource -import com.tangem.features.send.impl.presentation.state.SendUiState -import com.tangem.features.send.impl.presentation.state.SendUiStateType -import com.tangem.features.send.impl.presentation.state.StateRouter -import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState -import com.tangem.features.send.impl.presentation.state.fee.FeeType -import com.tangem.features.send.impl.presentation.state.fields.SendTextField -import com.tangem.utils.Provider - -internal class SendScreenAnalyticSender( - private val stateRouterProvider: Provider, - private val currentStateProvider: Provider, - private val cryptoCurrencyProvider: Provider, - private val analyticsEventHandler: AnalyticsEventHandler, -) { - fun send(prevScreen: SendUiStateType, state: SendUiState) { - when (prevScreen) { - SendUiStateType.Fee -> { - val feeState = state.getFeeState(stateRouterProvider().isEditState) ?: return - val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content - feeSelectorState?.selectedFee?.let { selectedFee -> - val isCustomFeeEdited = feeState.fee?.amount?.value != feeSelectorState.fees.normal.amount.value - if (selectedFee == FeeType.Custom && isCustomFeeEdited) { - analyticsEventHandler.send(SendAnalyticEvents.GasPriceInserter) - } - sendSelectedFeeAnalytics(feeSelectorState) - } - } - SendUiStateType.Amount -> { - val amountState = state.getAmountState(stateRouterProvider().isEditState) as? AmountState.Data ?: return - val isFiatSelected = amountState.amountTextField.isFiatValue - val selectedCurrency = if (!isFiatSelected) { - SelectedCurrencyType.Token - } else { - SelectedCurrencyType.AppCurrency - } - analyticsEventHandler.send( - SendAnalyticEvents.SelectedCurrency(selectedCurrency), - ) - } - else -> Unit - } - } - - fun sendOnClose() { - val routerState = stateRouterProvider().currentState.value - val state = currentStateProvider() - - val (source, isValid) = when (routerState.type) { - SendUiStateType.Recipient, - SendUiStateType.EditRecipient, - -> SendScreenSource.Address to (state.editRecipientState?.isPrimaryButtonEnabled ?: false) - SendUiStateType.Amount, - SendUiStateType.EditAmount, - -> SendScreenSource.Amount to state.editAmountState.isPrimaryButtonEnabled - SendUiStateType.Fee, - SendUiStateType.EditFee, - -> SendScreenSource.Fee to (state.editFeeState?.isPrimaryButtonEnabled ?: false) - else -> SendScreenSource.Confirm to true - } - - analyticsEventHandler.send( - SendAnalyticEvents.CloseButtonClicked( - source = source, - isFromSummary = routerState.isFromConfirmation, - isValid = isValid, - ), - ) - } - - fun sendTransaction() { - val state = currentStateProvider() - val isEditState = stateRouterProvider().isEditState - val cryptoCurrency = cryptoCurrencyProvider() - val feeState = state.getFeeState(isEditState) ?: return - val recipientState = state.getRecipientState(isEditState) ?: return - - val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return - val feeType = getSendTransactionFeeType(feeSelectorState) - analyticsEventHandler.send( - SendAnalyticEvents.TransactionScreenOpened( - token = cryptoCurrency.symbol, - feeType = feeType, - ), - ) - analyticsEventHandler.send( - Basic.TransactionSent( - sentFrom = AnalyticsParam.TxSentFrom.Send( - blockchain = cryptoCurrency.network.name, - token = cryptoCurrency.symbol, - feeType = feeType, - ), - memoType = getSendTransactionMemoType(recipientState.memoTextField), - ), - ) - } - - private fun sendSelectedFeeAnalytics(feeSelectorState: FeeSelectorState.Content) { - val type = getSendTransactionFeeType(feeSelectorState) - analyticsEventHandler.send(SendAnalyticEvents.SelectedFee(type)) - } - - private fun getSendTransactionFeeType(feeSelectorState: FeeSelectorState.Content): AnalyticsParam.FeeType = - when (feeSelectorState.fees) { - is TransactionFee.Single -> AnalyticsParam.FeeType.Fixed - is TransactionFee.Choosable -> when (feeSelectorState.selectedFee) { - FeeType.Slow -> AnalyticsParam.FeeType.Min - FeeType.Market -> AnalyticsParam.FeeType.Normal - FeeType.Fast -> AnalyticsParam.FeeType.Max - FeeType.Custom -> AnalyticsParam.FeeType.Custom - } - } - - private fun getSendTransactionMemoType( - recipientMemo: SendTextField.RecipientMemo?, - ): Basic.TransactionSent.MemoType { - val memo = recipientMemo?.value - return when { - memo?.isBlank() == true -> Basic.TransactionSent.MemoType.Empty - memo?.isNotBlank() == true -> Basic.TransactionSent.MemoType.Full - else -> Basic.TransactionSent.MemoType.Null - } - } -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/domain/AvailableWallet.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/domain/AvailableWallet.kt deleted file mode 100644 index ff920781f0..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/domain/AvailableWallet.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.tangem.features.send.impl.presentation.domain - -import androidx.compose.runtime.Immutable -import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.domain.wallets.models.UserWalletId - -/** - * Available wallet to send - * - * @property name wallet name - * @property userWalletId wallet id - * @property address blockchain address - */ -@Immutable -data class AvailableWallet( - val name: String, - val userWalletId: UserWalletId, - val address: String, - val cryptoCurrency: CryptoCurrency, -) \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/domain/SendRecipientListContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/domain/SendRecipientListContent.kt deleted file mode 100644 index 03e9414c0a..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/domain/SendRecipientListContent.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.tangem.features.send.impl.presentation.domain - -import androidx.annotation.DrawableRes -import com.tangem.core.ui.extensions.TextReference - -data class SendRecipientListContent( - val id: String, - val title: TextReference = TextReference.EMPTY, - val subtitle: TextReference = TextReference.EMPTY, - val timestamp: TextReference? = null, - val subtitleEndOffset: Int = 0, - @DrawableRes val subtitleIconRes: Int? = null, - val isVisible: Boolean = true, - val isLoading: Boolean = false, -) \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/model/SendClickIntents.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/model/SendClickIntents.kt deleted file mode 100644 index 30d0e6a4af..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/model/SendClickIntents.kt +++ /dev/null @@ -1,70 +0,0 @@ -package com.tangem.features.send.impl.presentation.model - -import com.tangem.common.ui.amountScreen.AmountScreenClickIntents -import com.tangem.common.ui.notifications.NotificationUM -import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.features.send.impl.presentation.analytics.EnterAddressSource -import com.tangem.features.send.impl.presentation.state.fee.FeeType -import java.math.BigDecimal - -@Suppress("TooManyFunctions") -internal interface SendClickIntents : AmountScreenClickIntents { - - fun popBackStack() - - fun onBackClick() - - fun onCloseClick() - - fun onNextClick(isFromEdit: Boolean = false) - - fun onPrevClick() - - fun onQrCodeScanClick() - - fun onFailedTxEmailClick(errorMessage: String) - - fun onTokenDetailsClick(currency: CryptoCurrency) - - // region Recipient - fun onRecipientAddressValueChange(value: String, type: EnterAddressSource? = null) - - fun onRecipientMemoValueChange(value: String, isValuePasted: Boolean = false) - // endregion - - // region Fee - fun feeReload() - - fun onFeeSelectorClick(feeType: FeeType) - - fun onCustomFeeValueChange(index: Int, value: String) - - fun onReadMoreClick() - // endregion - - // region Send - fun onSendClick() - - fun showAmount() - - fun showRecipient() - - fun showFee() - - fun showSend() - - fun onExploreClick() - - fun onShareClick(txUrl: String) - - fun onAmountReduceByClick( - reduceAmountBy: BigDecimal, - reduceAmountByDiff: BigDecimal, - notification: Class, - ) - - fun onAmountReduceToClick(reduceAmountTo: BigDecimal, notification: Class) - - fun onNotificationCancel(clazz: Class) - // endregion -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/model/SendModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/model/SendModel.kt deleted file mode 100644 index a331811946..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/model/SendModel.kt +++ /dev/null @@ -1,1143 +0,0 @@ -package com.tangem.features.send.impl.presentation.model - -import android.os.SystemClock -import androidx.compose.runtime.Stable -import arrow.core.Either -import arrow.core.getOrElse -import arrow.core.left -import com.tangem.blockchain.common.AmountType -import com.tangem.blockchain.common.TransactionData -import com.tangem.blockchain.common.transaction.TransactionFee -import com.tangem.common.routing.AppRouter -import com.tangem.common.ui.amountScreen.models.AmountState -import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary -import com.tangem.common.ui.notifications.NotificationUM -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.ui.utils.parseBigDecimal -import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase -import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase -import com.tangem.domain.common.util.cardTypesResolver -import com.tangem.domain.feedback.GetCardInfoUseCase -import com.tangem.domain.feedback.SaveBlockchainErrorUseCase -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.notifications.GetTronFeeNotificationShowCountUseCase -import com.tangem.domain.notifications.IncrementNotificationsShowCountUseCase -import com.tangem.domain.qrscanning.models.SourceType -import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase -import com.tangem.domain.qrscanning.usecases.ParseQrCodeUseCase -import com.tangem.domain.settings.IsSendTapHelpEnabledUseCase -import com.tangem.domain.settings.NeverShowTapHelpUseCase -import com.tangem.domain.tokens.* -import com.tangem.domain.tokens.error.CurrencyStatusError -import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.domain.transaction.error.AddressValidation -import com.tangem.domain.transaction.error.AddressValidationResult -import com.tangem.domain.transaction.error.GetFeeError -import com.tangem.domain.transaction.usecase.* -import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase -import com.tangem.domain.txhistory.usecase.GetFixedTxHistoryItemsUseCase -import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase -import com.tangem.domain.utils.convertToSdkAmount -import com.tangem.domain.wallets.models.UserWallet -import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.domain.wallets.models.isLocked -import com.tangem.domain.wallets.models.isMultiCurrency -import com.tangem.domain.wallets.models.requireColdWallet -import com.tangem.domain.wallets.usecase.GetUserWalletUseCase -import com.tangem.domain.wallets.usecase.GetWalletsUseCase -import com.tangem.features.send.api.SendComponent -import com.tangem.features.send.impl.navigation.InnerSendRouter -import com.tangem.features.send.impl.presentation.analytics.EnterAddressSource -import com.tangem.features.send.impl.presentation.analytics.SendAnalyticEvents -import com.tangem.features.send.impl.presentation.analytics.SendScreenSource -import com.tangem.features.send.impl.presentation.analytics.utils.SendScreenAnalyticSender -import com.tangem.features.send.impl.presentation.domain.AvailableWallet -import com.tangem.features.send.impl.presentation.state.* -import com.tangem.features.send.impl.presentation.state.amount.AmountStateFactory -import com.tangem.features.send.impl.presentation.state.confirm.SendNotificationFactory -import com.tangem.features.send.impl.presentation.state.fee.* -import com.tangem.features.send.impl.presentation.state.recipient.RecipientSendFactory -import com.tangem.features.txhistory.entity.TxHistoryContentUpdateEmitter -import com.tangem.lib.crypto.BlockchainUtils -import com.tangem.utils.Provider -import com.tangem.utils.coroutines.* -import com.tangem.utils.extensions.orZero -import com.tangem.utils.extensions.stripZeroPlainString -import kotlinx.coroutines.* -import kotlinx.coroutines.flow.* -import timber.log.Timber -import java.math.BigDecimal -import java.util.Locale -import javax.inject.Inject -import kotlin.properties.Delegates - -@Suppress("LongParameterList", "TooManyFunctions", "LargeClass") -@Stable -@ModelScoped -internal class SendModel @Inject constructor( - override val dispatchers: CoroutineDispatcherProvider, - private val getUserWalletUseCase: GetUserWalletUseCase, - private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase, - private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, - private val getWalletsUseCase: GetWalletsUseCase, - private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase, - private val getMinimumTransactionAmountSyncUseCase: GetMinimumTransactionAmountSyncUseCase, - private val getCryptoCurrencyUseCase: GetCryptoCurrencyUseCase, - private val getNetworkAddressesUseCase: GetNetworkAddressesUseCase, - private val getTxHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase, - private val getFixedTxHistoryItemsUseCase: GetFixedTxHistoryItemsUseCase, - private val sendTransactionUseCase: SendTransactionUseCase, - private val createTransferTransactionUseCase: CreateTransferTransactionUseCase, - private val getFeeUseCase: GetFeeUseCase, - private val validateWalletAddressUseCase: ValidateWalletAddressUseCase, - private val isAmountSubtractAvailableUseCase: IsAmountSubtractAvailableUseCase, - private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, - private val analyticsEventHandler: AnalyticsEventHandler, - private val parseQrCodeUseCase: ParseQrCodeUseCase, - private val isSendTapHelpEnabledUseCase: IsSendTapHelpEnabledUseCase, - private val neverShowTapHelpUseCase: NeverShowTapHelpUseCase, - private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase, - private val listenToQrScanningUseCase: ListenToQrScanningUseCase, - private val addCryptoCurrenciesUseCase: AddCryptoCurrenciesUseCase, - private val updateDelayedCurrencyStatusUseCase: UpdateDelayedNetworkStatusUseCase, - private val fetchPendingTransactionsUseCase: FetchPendingTransactionsUseCase, - private val isUtxoConsolidationAvailableUseCase: IsUtxoConsolidationAvailableUseCase, - private val validateWalletMemoUseCase: ValidateWalletMemoUseCase, - private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase, - private val getCardInfoUseCase: GetCardInfoUseCase, - private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase, - private val shareManager: ShareManager, - private val txHistoryContentUpdateEmitter: TxHistoryContentUpdateEmitter, - private val incrementNotificationsShowCountUseCase: IncrementNotificationsShowCountUseCase, - @DelayedWork private val coroutineScope: CoroutineScope, - private val innerRouter: InnerSendRouter, - appRouter: AppRouter, - paramsContainer: ParamsContainer, - validateTransactionUseCase: ValidateTransactionUseCase, - getCurrencyCheckUseCase: GetCurrencyCheckUseCase, - isFeeApproximateUseCase: IsFeeApproximateUseCase, - getBalanceNotEnoughForFeeWarningUseCase: GetBalanceNotEnoughForFeeWarningUseCase, - getTronFeeNotificationShowCountUseCase: GetTronFeeNotificationShowCountUseCase, -) : Model(), SendClickIntents { - - private val params = paramsContainer.require() - - private val userWalletId: UserWalletId = params.userWalletId - private val cryptoCurrency: CryptoCurrency = params.currency - private val transactionId: String? = params.transactionId - private val amount: String? = params.amount - private val destinationAddress: String? = params.destinationAddress - private val memo: String? = params.tag - - private val selectedAppCurrencyFlow: StateFlow = createSelectedAppCurrencyFlow() - - val stateRouter = StateRouter( - appRouter = appRouter, - isEditingDisabled = transactionId != null, - analyticsEventsHandler = analyticsEventHandler, - ) - - private val stateFactory = SendStateFactory( - clickIntents = this, - stateRouterProvider = Provider { stateRouter }, - currentStateProvider = Provider { uiState.value }, - userWalletProvider = Provider { userWallet }, - appCurrencyProvider = Provider(selectedAppCurrencyFlow::value), - cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus }, - feeCryptoCurrencyStatusProvider = Provider { feeCryptoCurrencyStatus }, - isTapHelpPreviewEnabledProvider = Provider { isTapHelpPreviewEnabled }, - ) - - private val recipientStateFactory = RecipientSendFactory( - stateRouterProvider = Provider { stateRouter }, - currentStateProvider = Provider { uiState.value }, - cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus }, - isUtxoConsolidationAvailableProvider = Provider { isUtxoConsolidationAvailable }, - validateWalletMemoUseCase = validateWalletMemoUseCase, - ) - - private val amountStateFactory = AmountStateFactory( - stateRouterProvider = Provider { stateRouter }, - currentStateProvider = Provider { uiState.value }, - cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus }, - minimumTransactionAmountProvider = Provider { minimumTransactionAmount }, - ) - - private val feeStateFactory = FeeStateFactory( - clickIntents = this, - stateRouterProvider = Provider { stateRouter }, - currentStateProvider = Provider { uiState.value }, - feeCryptoCurrencyStatusProvider = Provider { feeCryptoCurrencyStatus }, - appCurrencyProvider = Provider(selectedAppCurrencyFlow::value), - isFeeApproximateUseCase = isFeeApproximateUseCase, - ) - - private val eventStateFactory = SendEventStateFactory( - clickIntents = this, - stateRouterProvider = Provider { stateRouter }, - currentStateProvider = Provider { uiState.value }, - feeStateFactory = feeStateFactory, - ) - - private val feeNotificationFactory = FeeNotificationFactory( - currentStateProvider = Provider { uiState.value }, - stateRouterProvider = Provider { stateRouter }, - clickIntents = this, - ) - - private val sendNotificationFactory = SendNotificationFactory( - analyticsEventHandler = analyticsEventHandler, - validateTransactionUseCase = validateTransactionUseCase, - getCurrencyCheckUseCase = getCurrencyCheckUseCase, - getBalanceNotEnoughForFeeWarningUseCase = getBalanceNotEnoughForFeeWarningUseCase, - getTronFeeNotificationShowCountUseCase = getTronFeeNotificationShowCountUseCase, - cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus }, - feeCryptoCurrencyStatusProvider = Provider { feeCryptoCurrencyStatus }, - currentStateProvider = Provider { uiState.value }, - stateRouterProvider = Provider { stateRouter }, - isSubtractAvailableProvider = Provider { isAmountSubtractAvailable }, - appCurrencyProvider = Provider(selectedAppCurrencyFlow::value), - clickIntents = this, - userWalletId = userWalletId, - ) - - private val sendScreenAnalyticSender by lazy(LazyThreadSafetyMode.NONE) { - SendScreenAnalyticSender( - stateRouterProvider = Provider { stateRouter }, - currentStateProvider = Provider { uiState.value }, - analyticsEventHandler = analyticsEventHandler, - cryptoCurrencyProvider = Provider { cryptoCurrency }, - ) - } - - val uiState: MutableStateFlow = MutableStateFlow( - value = stateFactory.getInitialState(), - ) - - private var userWallet: UserWallet by Delegates.notNull() - private var userWallets: List = emptyList() - private var isAmountSubtractAvailable: Boolean = false - private var isUtxoConsolidationAvailable: Boolean = false - private var isTapHelpPreviewEnabled: Boolean = false - private var cryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull() - private var feeCryptoCurrencyStatus: CryptoCurrencyStatus? = null - private var minimumTransactionAmount: EnterAmountBoundary? = null - - private var balanceJobHolder = JobHolder() - private var balanceHidingJobHolder = JobHolder() - private var feeJobHolder = JobHolder() - private var addressValidationJobHolder = JobHolder() - private var memoValidationJobHolder = JobHolder() - private var sendNotificationsJobHolder = JobHolder() - private var feeNotificationsJobHolder = JobHolder() - - private var sendIdleTimer = 0L - - init { - subscribeOnCurrencyStatusUpdates() - subscribeOnBalanceHidden() - getTapHelpPreviewAvailability() - - onStateActive() - } - - override fun onDestroy() { - super.onDestroy() - balanceHidingJobHolder.cancel() - balanceJobHolder.cancel() - stateRouter.clear() - } - - private fun subscribeOnQRScannerResult() { - listenToQrScanningUseCase(SourceType.SEND) - .getOrElse { emptyFlow() } - .onEach(::onQrCodeScanned) - .launchIn(modelScope) - } - - private fun subscribeOnCurrencyStatusUpdates() { - modelScope.launch { - getUserWalletUseCase(userWalletId).fold( - ifRight = { wallet -> - userWallet = wallet - checkIfSubtractAvailable() - checkIfUtxoConsolidationAvailable() - - val isSingleWalletWithToken = wallet is UserWallet.Cold && - wallet.scanResponse.cardTypesResolver.isSingleWalletWithToken() - val isMultiCurrency = wallet.isMultiCurrency - getCurrenciesStatusUpdates( - isSingleWalletWithToken = isSingleWalletWithToken, - isMultiCurrency = isMultiCurrency, - ) - }, - ifLeft = { - showErrorAlert() - return@launch - }, - ) - } - } - - private fun subscribeOnBalanceHidden() { - getBalanceHidingSettingsUseCase() - .conflate() - .distinctUntilChanged() - .onEach { - uiState.value = stateFactory.getOnHideBalanceState(isBalanceHidden = it.isBalanceHidden) - } - .launchIn(modelScope) - .saveIn(balanceHidingJobHolder) - } - - private suspend fun getCurrenciesStatusUpdates(isSingleWalletWithToken: Boolean, isMultiCurrency: Boolean) { - getCurrencyStatus( - isSingleWalletWithToken = isSingleWalletWithToken, - isMultiCurrency = isMultiCurrency, - ).fold( - ifRight = { cryptoCurrencyStatus -> - onDataLoaded( - currencyStatus = cryptoCurrencyStatus, - feeCurrencyStatus = getFeeCurrencyStatusSync(cryptoCurrencyStatus, isMultiCurrency), - minTransactionAmount = getMinimumTransactionAmount(cryptoCurrencyStatus), - ) - }, - ifLeft = { showErrorAlert() }, - ) - } - - private fun getTapHelpPreviewAvailability() { - modelScope.launch { - isTapHelpPreviewEnabled = isSendTapHelpEnabledUseCase().getOrElse { false } - } - } - - private suspend fun getCurrencyStatus( - isSingleWalletWithToken: Boolean, - isMultiCurrency: Boolean, - ): Either { - return if (isMultiCurrency) { - getSingleCryptoCurrencyStatusUseCase.invokeMultiWalletSync( - userWalletId = userWalletId, - cryptoCurrencyId = cryptoCurrency.id, - isSingleWalletWithTokens = isSingleWalletWithToken, - ) - } else { - getSingleCryptoCurrencyStatusUseCase.invokeSingleWalletSync(userWalletId = userWalletId) - } - } - - private suspend fun getFeeCurrencyStatusSync( - cryptoCurrencyStatus: CryptoCurrencyStatus, - isMultiCurrency: Boolean, - ): CryptoCurrencyStatus? { - return if (isMultiCurrency) { - getFeePaidCryptoCurrencyStatusSyncUseCase( - userWalletId = userWalletId, - cryptoCurrencyStatus = cryptoCurrencyStatus, - ).getOrNull() - } else { - cryptoCurrencyStatus - } - } - - private suspend fun getMinimumTransactionAmount(cryptoCurrencyStatus: CryptoCurrencyStatus): EnterAmountBoundary? { - return getMinimumTransactionAmountSyncUseCase( - userWalletId = userWalletId, - cryptoCurrencyStatus = cryptoCurrencyStatus, - ).getOrNull()?.let { - EnterAmountBoundary( - amount = it, - fiatRate = cryptoCurrencyStatus.value.fiatRate.orZero(), - ) - } - } - - private fun createSelectedAppCurrencyFlow(): StateFlow { - return getSelectedAppCurrencyUseCase() - .map { maybeAppCurrency -> - maybeAppCurrency.getOrElse { AppCurrency.Default } - } - .stateIn( - scope = modelScope, - started = SharingStarted.Eagerly, - initialValue = AppCurrency.Default, - ) - } - - private fun onDataLoaded( - currencyStatus: CryptoCurrencyStatus, - feeCurrencyStatus: CryptoCurrencyStatus?, - minTransactionAmount: EnterAmountBoundary?, - ) { - cryptoCurrencyStatus = currencyStatus - feeCryptoCurrencyStatus = feeCurrencyStatus - minimumTransactionAmount = minTransactionAmount - subscribeOnQRScannerResult() - when { - uiState.value.sendState?.isSuccess == true -> return - transactionId != null && amount != null && destinationAddress != null -> { - uiState.value = stateFactory.getReadyState(amount, destinationAddress, memo) - stateRouter.showSend() - loadFee() - } - else -> { - uiState.value = stateFactory.getReadyState() - getWalletsAndRecent() - stateRouter.showRecipient() - updateNotifications() - } - } - } - - private fun getWalletsAndRecent() { - getUserWallets() - modelScope.launch { - getTxHistory() - } - } - - private fun getUserWallets() { - modelScope.launch { - runCatching { - waitForDelay(delay = RECENT_LOAD_DELAY) { - getWalletsUseCase.invokeSync() - .toAvailableWallets() - } - }.onSuccess { result -> - userWallets = result - uiState.value = recipientStateFactory.onLoadedWalletsList(wallets = userWallets) - }.onFailure { - uiState.value = recipientStateFactory.onLoadedWalletsList(wallets = emptyList()) - } - } - } - - private suspend fun List.toAvailableWallets(): List { - return filterNot { it.isLocked } - .mapNotNull { wallet -> - val addresses = if (!wallet.isMultiCurrency) { - getCryptoCurrencyUseCase( - userWallet = wallet, - cryptoCurrencyId = cryptoCurrency.id.value, - ).getOrNull()?.let { - if (it.network.id == cryptoCurrency.network.id) { - getNetworkAddressesUseCase.invokeSync( - userWalletId = wallet.walletId, - networkRawId = it.network.id.rawId, - ) - } else { - null - } - } - } else { - getNetworkAddressesUseCase.invokeSync( - userWalletId = wallet.walletId, - networkRawId = cryptoCurrency.network.id.rawId, - ) - } - addresses?.map { (cryptoCurrency, address) -> - AvailableWallet( - name = wallet.name, - address = address, - cryptoCurrency = cryptoCurrency, - userWalletId = wallet.walletId, - ) - } - }.flatten() - } - - private suspend fun getTxHistory() { - val txHistoryList = waitForDelay(delay = RECENT_LOAD_DELAY) { - getFixedTxHistoryItemsUseCase.getSync( - userWalletId = userWalletId, - currency = cryptoCurrency, - pageSize = RECENT_TX_SIZE, - ).getOrElse { emptyList() } - } - uiState.value = recipientStateFactory.onLoadedHistoryList(txHistory = txHistoryList) - } - - private fun onStateActive() { - stateRouter.currentState - .onEach { - when (it.type) { - SendUiStateType.Fee, - SendUiStateType.EditFee, - -> loadFee() - SendUiStateType.Send -> { - uiState.value = stateFactory.getIsAmountSubtractedState(isAmountSubtractAvailable) - sendIdleTimer = SystemClock.elapsedRealtime() - } - else -> Unit - } - } - .launchIn(modelScope) - } - - private fun updateNotifications() { - sendNotificationFactory.create() - .conflate() - .distinctUntilChanged() - .onEach { uiState.value = stateFactory.getSendNotificationState(notifications = it) } - .flowOn(dispatchers.main) - .launchIn(modelScope) - .saveIn(sendNotificationsJobHolder) - } - - private fun updateFeeNotifications() { - feeNotificationFactory.create() - .conflate() - .distinctUntilChanged() - .onEach { uiState.value = feeStateFactory.getFeeNotificationState(notifications = it) } - .flowOn(dispatchers.main) - .launchIn(modelScope) - .saveIn(feeNotificationsJobHolder) - } - - // region screen state navigation - override fun popBackStack() = stateRouter.popBackStack() - override fun onBackClick() { - cancelFeeRequest() - stateRouter.onBackClick(isSuccess = uiState.value.sendState?.isSuccess == true) - } - - override fun onCloseClick() { - sendScreenAnalyticSender.sendOnClose() - when (stateRouter.currentState.value.type) { - SendUiStateType.EditAmount, - SendUiStateType.EditFee, - SendUiStateType.EditRecipient, - -> onBackClick() - else -> popBackStack() - } - } - - override fun onNextClick(isFromEdit: Boolean) { - val currentState = stateRouter.currentState.value - when (currentState.type) { - SendUiStateType.Fee, - SendUiStateType.EditFee, - -> if (uiState.value.getFeeState(isFromEdit)?.isPrimaryButtonEnabled == false) return - SendUiStateType.Amount, - SendUiStateType.EditAmount, - -> if (!uiState.value.getAmountState(isFromEdit).isPrimaryButtonEnabled) return - SendUiStateType.Recipient, - SendUiStateType.EditRecipient, - -> if (uiState.value.getRecipientState(isFromEdit)?.isPrimaryButtonEnabled == false) return - SendUiStateType.Send -> if (uiState.value.sendState?.isPrimaryButtonEnabled == false) return - SendUiStateType.None -> return - } - - uiState.value = stateFactory.syncEditStates(isFromEdit = isFromEdit) - sendScreenAnalyticSender.send(currentState.type, uiState.value) - prepareNextState(currentState.type, isFromEdit) - stateRouter.onNextClick() - } - - private fun prepareNextState(currentStateType: SendUiStateType, isFromEdit: Boolean) { - when (currentStateType) { - SendUiStateType.Fee, - SendUiStateType.EditFee, - -> if (onFeeNextIntercept(isFromEdit)) return - SendUiStateType.Amount -> { - loadFee() - incrementNotificationsShowCounter() - } - SendUiStateType.EditAmount, - -> loadFee() - else -> Unit - } - } - - private fun incrementNotificationsShowCounter() { - modelScope.launch { - incrementNotificationsShowCountUseCase(cryptoCurrency) - } - } - - override fun onAmountNext() = onNextClick(stateRouter.isEditState) - - override fun onPrevClick() { - cancelFeeRequest() - stateRouter.onPrevClick() - } - - override fun onQrCodeScanClick() { - analyticsEventHandler.send(SendAnalyticEvents.QrCodeButtonClicked) - innerRouter.openQrCodeScanner(cryptoCurrency.network.name) - } - - override fun onFailedTxEmailClick(errorMessage: String) { - val recipient = uiState.value.recipientState?.addressTextField?.value - val feeValue = uiState.value.feeState?.fee?.amount?.value - val amountValue = (uiState.value.amountState as? AmountState.Data)?.amountTextField?.cryptoAmount?.value - - val receivingAmount = if (amountValue != null && feeValue != null) { - checkAndCalculateSubtractedAmount( - isAmountSubtractAvailable = isAmountSubtractAvailable, - cryptoCurrencyStatus = cryptoCurrencyStatus, - amountValue = amountValue, - feeValue = feeValue, - reduceAmountBy = uiState.value.sendState?.reduceAmountBy ?: BigDecimal.ZERO, - ) - } else { - null - } - - val amount = receivingAmount?.convertToSdkAmount(cryptoCurrency) - - saveBlockchainErrorUseCase( - error = BlockchainErrorInfo( - errorMessage = errorMessage, - blockchainId = cryptoCurrency.network.rawId, - derivationPath = cryptoCurrency.network.derivationPath.value, - destinationAddress = recipient.orEmpty(), - tokenSymbol = if (amount?.type is AmountType.Token) { - amount.currencySymbol - } else { - "" - }, - amount = amount?.value?.stripZeroPlainString() ?: "unknown", - fee = feeValue?.convertToSdkAmount(cryptoCurrency) - ?.value?.stripZeroPlainString() ?: "unknown", - ), - ) - - val userWallet = userWallet.requireColdWallet() // TODO [REDACTED_TASK_KEY] - val cardInfo = getCardInfoUseCase(userWallet.scanResponse).getOrNull() ?: return - - modelScope.launch { - sendFeedbackEmailUseCase(type = FeedbackEmailType.TransactionSendingProblem(cardInfo = cardInfo)) - } - } - - override fun onTokenDetailsClick(currency: CryptoCurrency) = innerRouter.openTokenDetails(userWalletId, currency) - - private fun onFeeNextIntercept(isFromEdit: Boolean): Boolean { - val feeState = uiState.value.getFeeState(stateRouter.isEditState) - val feeSelectorState = feeState?.feeSelectorState as? FeeSelectorState.Content ?: return false - - if (isFromEdit) { - // in some cases, if it's possible to fix incorrect fee automatically, - // do it, update current state and go let continue the flow - val fixedState = feeStateFactory.tryAutoFixCustomFeeValue() - if (fixedState.editFeeState != feeState) { - uiState.value = fixedState - val currentState = stateRouter.currentState.value - uiState.value = stateFactory.syncEditStates(isFromEdit = isFromEdit) - sendScreenAnalyticSender.send(currentState.type, uiState.value) - return false - } - } - - if (checkIfFeeTooLow(feeSelectorState)) { - uiState.value = eventStateFactory.getFeeTooLowAlert( - onConsume = { uiState.value = eventStateFactory.onConsumeEventState() }, - ) - return true - } - return checkIfFeeTooHigh( - feeSelectorState = feeSelectorState, - onShow = { diff -> - uiState.value = eventStateFactory.getFeeTooHighAlert( - diff = diff, - onConsume = { uiState.value = eventStateFactory.onConsumeEventState() }, - ) - }, - ) - } - - private fun cancelFeeRequest() { - modelScope.launch { - feeJobHolder.cancel() - } - } - - private fun onQrCodeScanned(address: String) { - parseQrCodeUseCase(address, cryptoCurrency).fold( - ifRight = { parsedCode -> - onRecipientAddressValueChange(parsedCode.address, EnterAddressSource.QRCode) - parsedCode.amount?.let { - onAmountValueChange(it.parseBigDecimal(decimals = cryptoCurrency.decimals)) - } - parsedCode.memo?.let { onRecipientMemoValueChange(it) } - }, - ifLeft = { - onRecipientAddressValueChange(address, EnterAddressSource.QRCode) - Timber.w(it) - }, - ) - } - -// endregion - - // region amount state clicks - override fun onCurrencyChangeClick(isFiat: Boolean) { - uiState.value = amountStateFactory.getOnCurrencyChangedState(isFiat) - } - - override fun onAmountValueChange(value: String) { - uiState.value = amountStateFactory.getOnAmountValueChange(value) - } - - override fun onMaxValueClick() { - uiState.value = amountStateFactory.getOnMaxAmountClick() - analyticsEventHandler.send(SendAnalyticEvents.MaxAmountButtonClicked) - } - - override fun onAmountPasteTriggerDismiss() { - uiState.value = amountStateFactory.getOnAmountPastedTriggerDismiss() - } -// endregion - -// region recipient state clicks - - override fun onRecipientAddressValueChange(value: String, type: EnterAddressSource?) { - modelScope.launch { - if (!checkIfXrpAddressValue(value)) { - uiState.value = recipientStateFactory.onRecipientAddressValueChange(value, isValuePasted = type != null) - uiState.value = recipientStateFactory.getOnRecipientAddressValidationStarted() - val isValidAddress = validateAddress(value) - uiState.value = recipientStateFactory.getOnRecipientAddressValidState(value, isValidAddress) - type?.let { - analyticsEventHandler.send( - SendAnalyticEvents.AddressEntered( - it, - isValidAddress.isRight(), - ), - ) - } - autoNextFromRecipient(type, isValidAddress.isRight()) - } - }.saveIn(addressValidationJobHolder) - } - - override fun onRecipientMemoValueChange(value: String, isValuePasted: Boolean) { - modelScope.launch { - if (!checkIfXrpAddressValue(value)) { - uiState.value = recipientStateFactory.getOnRecipientMemoValueChange(value, isValuePasted) - uiState.value = recipientStateFactory.getOnRecipientAddressValidationStarted() - val recipientState = uiState.value.getRecipientState(stateRouter.isEditState) - val maybeValidAddress = validateAddress(recipientState?.addressTextField?.value.orEmpty()) - uiState.value = recipientStateFactory.getOnRecipientMemoValidState(value, maybeValidAddress.isRight()) - } - }.saveIn(memoValidationJobHolder) - } - - private suspend fun validateAddress(value: String): AddressValidationResult = runCatching { - val maybeValidAddress = validateWalletAddressUseCase( - userWalletId = userWalletId, - network = cryptoCurrency.network, - address = value, - currencyAddress = cryptoCurrencyStatus.value.networkAddress?.availableAddresses, - ) - onEnteredValidAddress(maybeValidAddress.isLeft()) - maybeValidAddress - }.getOrElse { AddressValidation.Error.DataError(it).left() } - - private fun validateMemo(value: String?): Boolean { - return value?.let { validateWalletMemoUseCase(cryptoCurrency.network, it).isRight() } != false - } - - private suspend fun checkIfXrpAddressValue(value: String): Boolean { - return BlockchainUtils.decodeRippleXAddress(value, cryptoCurrency.network.rawId)?.let { decodedAddress -> - uiState.value = - recipientStateFactory.onRecipientAddressValueChange(value, isXAddress = true, isValuePasted = true) - uiState.value = recipientStateFactory.getOnXAddressMemoState() - val isValidAddress = validateAddress(decodedAddress.address) - uiState.value = - recipientStateFactory.getOnRecipientAddressValidState(decodedAddress.address, isValidAddress) - true - } ?: false - } - - private fun onEnteredValidAddress(isNotValid: Boolean) { - uiState.value = recipientStateFactory.getHiddenRecentListState(isNotValid = isNotValid) - } - - private fun autoNextFromRecipient(type: EnterAddressSource?, isValidAddress: Boolean) { - val memo = uiState.value.getRecipientState(stateRouter.isEditState)?.memoTextField?.value - val isValidMemo = validateMemo(memo) - - val isRecent = type == EnterAddressSource.RecentAddress || type == EnterAddressSource.MyWallet - if (isRecent && isValidAddress && isValidMemo) onNextClick(stateRouter.isEditState) - } -// endregion - - // region fee - override fun feeReload() = loadFee() - - override fun onFeeSelectorClick(feeType: FeeType) { - uiState.value = feeStateFactory.onFeeSelectedState(feeType) - updateFeeNotifications() - if (feeType == FeeType.Custom) { - analyticsEventHandler.send(SendAnalyticEvents.CustomFeeButtonClicked) - } - } - - override fun onCustomFeeValueChange(index: Int, value: String) { - uiState.value = feeStateFactory.onCustomFeeValueChange(index, value) - updateFeeNotifications() - } - - override fun onReadMoreClick() { - val locale = if (Locale.getDefault().language == RU_LOCALE) RU_LOCALE else EN_LOCALE - val url = buildString { - append(FEE_READ_MORE_URL_FIRST_PART) - append(locale) - append(FEE_READ_MORE_URL_SECOND_PART) - } - innerRouter.openUrl(url) - } - - private fun loadFee() { - modelScope.launch { - val isShowStatus = uiState.value.feeState?.fee == null - if (isShowStatus) { - uiState.value = feeStateFactory.onFeeOnLoadingState() - updateNotifications() - } - val result = callFeeUseCase()?.fold( - ifRight = { - uiState.value = feeStateFactory.onFeeOnLoadedState(it) - sendIdleTimer = SystemClock.elapsedRealtime() - }, - ifLeft = { loadFeeError -> - onFeeLoadFailed(isShowStatus, loadFeeError) - }, - ) - if (result == null) { - onFeeLoadFailed(isShowStatus, null) - } - updateNotifications() - updateFeeNotifications() - }.saveIn(feeJobHolder) - } - - private fun onFeeLoadFailed(isShowStatus: Boolean, loadFeeError: GetFeeError?) { - if (isShowStatus) { - uiState.value = feeStateFactory.onFeeOnErrorState( - loadFeeError, - ) - } - } - - private suspend fun checkIfSubtractAvailable() { - isAmountSubtractAvailable = isAmountSubtractAvailableUseCase(userWalletId, cryptoCurrency).fold( - ifRight = { it }, - ifLeft = { false }, - ) - } - - private suspend fun checkIfUtxoConsolidationAvailable() { - isUtxoConsolidationAvailable = isUtxoConsolidationAvailableUseCase.invokeSync( - userWalletId = userWalletId, - network = cryptoCurrency.network, - ) - } - - private suspend fun callFeeUseCase(): Either? { - val isFromConfirmation = stateRouter.currentState.value.isFromConfirmation - val amountState = uiState.value.getAmountState(isFromConfirmation) as? AmountState.Data ?: return null - val recipientState = uiState.value.getRecipientState(isFromConfirmation) ?: return null - val amount = amountState.amountTextField.cryptoAmount.value ?: return null - val destinationAddress = recipientState.addressTextField.value - val memo = recipientState.memoTextField?.value - - val transferTransaction = createTransferTransactionUseCase( - amount = amount.convertToSdkAmount(cryptoCurrency), - memo = memo, - destination = destinationAddress, - userWalletId = userWallet.walletId, - network = cryptoCurrency.network, - ).getOrElse { - return GetFeeError.DataError(it).left() - } - - return getFeeUseCase( - transactionData = transferTransaction, - userWallet = userWallet, - network = params.currency.network, - ) - } -// endregion - - // region send state clicks - override fun onSendClick() { - val sendState = uiState.value.sendState ?: return - if (sendState.isSuccess) popBackStack() - - uiState.value = stateFactory.getSendingStateUpdate(isSending = true) - if (SystemClock.elapsedRealtime() - sendIdleTimer < CHECK_FEE_UPDATE_DELAY) { - verifyAndSendTransaction() - } else { - onCheckFeeUpdate() - sendIdleTimer = SystemClock.elapsedRealtime() - } - } - - override fun showAmount() { - uiState.value = stateFactory.syncEditStates(isFromEdit = false) - stateRouter.showAmount(isFromConfirmation = true) - setNeverToShowTapHelp() - analyticsEventHandler.send(SendAnalyticEvents.ScreenReopened(SendScreenSource.Amount)) - } - - override fun showRecipient() { - uiState.value = stateFactory.syncEditStates(isFromEdit = false) - stateRouter.showRecipient(isFromConfirmation = true) - uiState.value = stateFactory.getHiddenTapHelpState() - setNeverToShowTapHelp() - analyticsEventHandler.send(SendAnalyticEvents.ScreenReopened(SendScreenSource.Address)) - } - - override fun showFee() { - uiState.value = stateFactory.syncEditStates(isFromEdit = false) - stateRouter.showFee(isFromConfirmation = true) - setNeverToShowTapHelp() - analyticsEventHandler.send(SendAnalyticEvents.ScreenReopened(SendScreenSource.Fee)) - } - - override fun showSend() { - stateRouter.showSend() - } - - override fun onExploreClick() { - val sendState = uiState.value.sendState ?: return - analyticsEventHandler.send(SendAnalyticEvents.ExploreButtonClicked) - innerRouter.openUrl(sendState.txUrl) - } - - override fun onShareClick(txUrl: String) { - analyticsEventHandler.send(SendAnalyticEvents.ShareButtonClicked) - shareManager.shareText(txUrl) - } - - override fun onAmountReduceToClick(reduceAmountTo: BigDecimal, notification: Class) { - uiState.value = amountStateFactory.getOnAmountReduceToState(reduceAmountTo) - uiState.value = sendNotificationFactory.dismissNotificationState(notification) - feeReload() - } - - override fun onAmountReduceByClick( - reduceAmountBy: BigDecimal, - reduceAmountByDiff: BigDecimal, - notification: Class, - ) { - uiState.value = amountStateFactory.getOnAmountReduceByState( - reduceAmountBy = reduceAmountBy, - reduceAmountByDiff = reduceAmountByDiff, - ) - - uiState.value = sendNotificationFactory.dismissNotificationState(notification) - feeReload() - } - - override fun onNotificationCancel(clazz: Class) { - uiState.value = sendNotificationFactory.dismissNotificationState(clazz = clazz, isIgnored = true) - } - - private fun verifyAndSendTransaction() { - val recipient = uiState.value.recipientState?.addressTextField?.value ?: return - val feeState = uiState.value.feeState ?: return - val fee = feeState.fee ?: return - val memo = uiState.value.recipientState?.memoTextField?.value - val amountValue = (uiState.value.amountState as? AmountState.Data) - ?.amountTextField?.cryptoAmount?.value - ?: return - val feeValue = fee.amount.value ?: return - - val receivingAmount = checkAndCalculateSubtractedAmount( - isAmountSubtractAvailable = isAmountSubtractAvailable, - cryptoCurrencyStatus = cryptoCurrencyStatus, - amountValue = amountValue, - feeValue = feeValue, - reduceAmountBy = uiState.value.sendState?.reduceAmountBy ?: BigDecimal.ZERO, - ) - - modelScope.launch { - createTransferTransactionUseCase( - amount = receivingAmount.convertToSdkAmount(cryptoCurrency), - fee = fee, - memo = memo, - destination = recipient, - userWalletId = userWalletId, - network = cryptoCurrency.network, - ).fold( - ifLeft = { - Timber.e(it) - uiState.value = stateFactory.getSendingStateUpdate(isSending = false) - uiState.value = eventStateFactory.getGenericErrorState( - error = it, - onConsume = { uiState.value = eventStateFactory.onConsumeEventState() }, - ) - }, - ifRight = { txData -> - sendTransaction(txData) - }, - ) - } - } - - private suspend fun sendTransaction(txData: TransactionData.Uncompiled) { - val result = sendTransactionUseCase( - txData = txData, - userWallet = userWallet, - network = cryptoCurrency.network, - ) - - uiState.value = stateFactory.getSendingStateUpdate(isSending = false) - - result.fold( - ifLeft = { error -> - uiState.value = eventStateFactory.getSendTransactionErrorState( - error = error, - onConsume = { uiState.value = eventStateFactory.onConsumeEventState() }, - ) - analyticsEventHandler.send(SendAnalyticEvents.TransactionError(cryptoCurrency.symbol)) - }, - ifRight = { - updateTransactionStatus(txData) - addTokenToWalletIfNeeded() - scheduleUpdates() - sendScreenAnalyticSender.sendTransaction() - }, - ) - } - - private fun addTokenToWalletIfNeeded() { - if (cryptoCurrency !is CryptoCurrency.Token) return - - val recipientState = uiState.value.getRecipientState(stateRouter.isEditState) ?: return - val destinationAddress = recipientState.addressTextField.value - - val receivingUserWallet = userWallets.firstOrNull { it.address == destinationAddress } ?: return - - modelScope.launch { - addCryptoCurrenciesUseCase( - userWalletId = receivingUserWallet.userWalletId, - cryptoCurrency = cryptoCurrency, - network = receivingUserWallet.cryptoCurrency.network, - ) - } - } - - private fun updateTransactionStatus(txData: TransactionData.Uncompiled) { - val txUrl = getExplorerTransactionUrlUseCase( - txHash = txData.hash.orEmpty(), - networkId = cryptoCurrency.network.id, - ).getOrElse { "" } - uiState.value = stateFactory.getTransactionSendState(txData, txUrl) - } - - private fun scheduleUpdates() { - coroutineScope.launch { - listOf( - // we should update network to find pending tx after 1 sec - async { - fetchPendingTransactionsUseCase( - userWalletId = userWallet.walletId, - network = cryptoCurrency.network, - ) - }, - // we should update tx history and network for new balance - async { - updateTxHistory() - }, - async { - updateDelayedCurrencyStatusUseCase( - userWalletId = userWallet.walletId, - network = cryptoCurrency.network, - delayMillis = BALANCE_UPDATE_DELAY, - ) - }, - ).awaitAll() - } - } - - private suspend fun updateTxHistory() { - delay(BALANCE_UPDATE_DELAY) - val txHistoryItemsCountEither = getTxHistoryItemsCountUseCase( - userWalletId = userWalletId, - currency = cryptoCurrency, - ) - - txHistoryItemsCountEither.onRight { - txHistoryContentUpdateEmitter.triggerUpdate() - } - } - - private fun onCheckFeeUpdate() { - val sendState = uiState.value.sendState ?: return - val isSuccess = sendState.isSuccess - val noErrorNotifications = sendState.notifications.none { it is NotificationUM.Error } - - if (!isSuccess && noErrorNotifications) { - modelScope.launch { - val feeUpdatedState = callFeeUseCase()?.fold( - ifRight = { - uiState.value = stateFactory.getSendingStateUpdate(isSending = false) - eventStateFactory.getFeeUpdatedAlert( - fee = it, - onConsume = { uiState.value = eventStateFactory.onConsumeEventState() }, - onFeeNotIncreased = { - uiState.value = stateFactory.getSendingStateUpdate(isSending = true) - verifyAndSendTransaction() - }, - ) - }, - ifLeft = { - uiState.value = stateFactory.getSendingStateUpdate(isSending = false) - eventStateFactory.getFeeUnreachableErrorState( - onConsume = { uiState.value = eventStateFactory.onConsumeEventState() }, - ) - }, - ) - - uiState.value = if (feeUpdatedState != null) { - feeUpdatedState - } else { - uiState.value = stateFactory.getSendingStateUpdate(isSending = false) - eventStateFactory.getFeeUnreachableErrorState( - onConsume = { uiState.value = eventStateFactory.onConsumeEventState() }, - ) - } - } - } - } - - private fun setNeverToShowTapHelp() { - modelScope.launch { - neverShowTapHelpUseCase() - } - uiState.value = stateFactory.getHiddenTapHelpState() - } - - private fun showErrorAlert() { - uiState.value = eventStateFactory.getGenericErrorState( - onConsume = { uiState.value = eventStateFactory.onConsumeEventState() }, - ) - } -// endregion - - private companion object { - const val CHECK_FEE_UPDATE_DELAY = 60_000L - const val BALANCE_UPDATE_DELAY = 11_000L - const val RECENT_LOAD_DELAY = 500L - const val RECENT_TX_SIZE = 100 - - const val RU_LOCALE = "ru" - const val EN_LOCALE = "en" - const val FEE_READ_MORE_URL_FIRST_PART = "https://tangem.com/" - const val FEE_READ_MORE_URL_SECOND_PART = "/blog/post/what-is-a-transaction-fee-and-why-do-we-need-it/" - } -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertUM.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertUM.kt deleted file mode 100644 index c4e0e780f8..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertUM.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.tangem.features.send.impl.presentation.state - -import androidx.compose.runtime.Immutable -import com.tangem.common.ui.alerts.models.AlertUM -import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.extensions.wrappedList -import com.tangem.features.send.impl.R - -@Immutable -internal sealed class SendAlertUM : AlertUM { - - data class GenericError( - override val title: TextReference? = resourceReference(id = R.string.send_alert_transaction_failed_title), - override val onConfirmClick: (() -> Unit), - ) : SendAlertUM() { - override val message: TextReference = resourceReference(R.string.common_unknown_error) - override val confirmButtonText: TextReference = - resourceReference(id = R.string.common_support) - } - - data class FeeIncreased( - override val onConfirmClick: () -> Unit, - ) : SendAlertUM() { - override val title: TextReference? = null - override val message: TextReference = resourceReference(id = R.string.send_notification_high_fee_title) - override val confirmButtonText: TextReference = resourceReference(id = R.string.common_ok) - } - - data class FeeTooLow( - override val onConfirmClick: () -> Unit, - ) : SendAlertUM() { - override val title: TextReference? = null - override val message: TextReference = resourceReference(id = R.string.send_alert_fee_too_low_text) - override val confirmButtonText: TextReference = resourceReference(R.string.common_continue) - } - - data class FeeTooHigh( - val times: String, - override val onConfirmClick: () -> Unit, - ) : SendAlertUM() { - override val title: TextReference? = null - override val message: TextReference = - resourceReference(id = R.string.send_alert_fee_too_high_text, wrappedList(times)) - override val confirmButtonText: TextReference = resourceReference(R.string.common_continue) - } - - data class FeeUnreachableError( - override val onConfirmClick: (() -> Unit), - ) : SendAlertUM() { - override val title: TextReference = resourceReference(R.string.send_fee_unreachable_error_title) - override val message: TextReference = resourceReference(R.string.send_fee_unreachable_error_text) - override val confirmButtonText = resourceReference(R.string.warning_button_refresh) - } -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEvent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEvent.kt deleted file mode 100644 index 4a3fad4207..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEvent.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.tangem.features.send.impl.presentation.state - -import androidx.compose.runtime.Immutable -import com.tangem.common.ui.alerts.models.AlertUM -import com.tangem.core.ui.extensions.TextReference - -@Immutable -internal sealed class SendEvent { - - data class ShowSnackBar(val text: TextReference) : SendEvent() - - data class ShowAlert(val alert: AlertUM) : SendEvent() -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt deleted file mode 100644 index 9d95fd1d61..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt +++ /dev/null @@ -1,136 +0,0 @@ -package com.tangem.features.send.impl.presentation.state - -import com.tangem.blockchain.common.transaction.TransactionFee -import com.tangem.common.ui.alerts.TransactionErrorAlertConverter -import com.tangem.core.ui.event.consumedEvent -import com.tangem.core.ui.event.triggeredEvent -import com.tangem.domain.transaction.error.SendTransactionError -import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState -import com.tangem.features.send.impl.presentation.state.fee.FeeStateFactory -import com.tangem.features.send.impl.presentation.state.fee.FeeType -import com.tangem.features.send.impl.presentation.model.SendClickIntents -import com.tangem.utils.Provider -import java.math.BigDecimal - -/** - * Factory to produce event state for [SendUiState] - * - * @param currentStateProvider [Provider] of [SendUiState] - * @param clickIntents [SendClickIntents] - * @param feeStateFactory [FeeStateFactory] - */ -internal class SendEventStateFactory( - private val stateRouterProvider: Provider, - private val currentStateProvider: Provider, - private val clickIntents: SendClickIntents, - private val feeStateFactory: FeeStateFactory, -) { - private val transactionErrorAlertConverter by lazy(LazyThreadSafetyMode.NONE) { - TransactionErrorAlertConverter( - popBackStack = clickIntents::popBackStack, - onFailedTxEmailClick = clickIntents::onFailedTxEmailClick, - ) - } - - fun onConsumeEventState(): SendUiState { - return currentStateProvider().copy(event = consumedEvent()) - } - - fun getSendTransactionErrorState(error: SendTransactionError?, onConsume: () -> Unit): SendUiState { - val state = currentStateProvider() - val event = error?.let { - transactionErrorAlertConverter.convert(error)?.let { - triggeredEvent(SendEvent.ShowAlert(it), onConsume) - } - } - return state.copy( - event = event ?: consumedEvent(), - ) - } - - fun getFeeUpdatedAlert(fee: TransactionFee, onConsume: () -> Unit, onFeeNotIncreased: () -> Unit): SendUiState { - val state = currentStateProvider() - val feeState = state.getFeeState(stateRouterProvider().isEditState) - val feeSelector = feeState?.feeSelectorState as? FeeSelectorState.Content ?: return state - val newFee = when (fee) { - is TransactionFee.Single -> fee.normal - is TransactionFee.Choosable -> { - when (feeSelector.selectedFee) { - FeeType.Slow -> fee.minimum - FeeType.Market -> fee.normal - FeeType.Fast -> fee.priority - FeeType.Custom -> return state - } - } - } - - val newFeeValue = newFee.amount.value ?: BigDecimal.ZERO - val oldFeeValue = feeStateFactory.feeConverter.convert(feeSelector).amount.value ?: BigDecimal.ZERO - val updateFeeState = feeStateFactory.onFeeOnLoadedState(fee) - return if (newFeeValue > oldFeeValue) { - updateFeeState.copy( - event = triggeredEvent( - data = SendEvent.ShowAlert(SendAlertUM.FeeIncreased(onConsume)), - onConsume = onConsume, - ), - ) - } else { - onFeeNotIncreased() - updateFeeState - } - } - - fun getFeeTooLowAlert(onConsume: () -> Unit): SendUiState { - val state = currentStateProvider() - return state.copy( - event = triggeredEvent( - data = SendEvent.ShowAlert( - SendAlertUM.FeeTooLow( - onConfirmClick = clickIntents::showSend, - ), - ), - onConsume = onConsume, - ), - ) - } - - fun getFeeTooHighAlert(diff: String, onConsume: () -> Unit): SendUiState { - return currentStateProvider().copy( - event = triggeredEvent( - data = SendEvent.ShowAlert( - SendAlertUM.FeeTooHigh( - onConfirmClick = clickIntents::showSend, - times = diff, - ), - ), - onConsume = onConsume, - ), - ) - } - - fun getGenericErrorState(error: Throwable? = null, onConsume: () -> Unit): SendUiState { - val state = currentStateProvider() - return state.copy( - event = triggeredEvent( - data = SendEvent.ShowAlert( - SendAlertUM.GenericError( - onConfirmClick = { clickIntents.onFailedTxEmailClick(error?.localizedMessage.orEmpty()) }, - ), - ), - onConsume = onConsume, - ), - ) - } - - fun getFeeUnreachableErrorState(onConsume: () -> Unit): SendUiState { - val state = currentStateProvider() - return state.copy( - event = triggeredEvent( - data = SendEvent.ShowAlert( - SendAlertUM.FeeUnreachableError(onConfirmClick = clickIntents::feeReload), - ), - onConsume = onConsume, - ), - ) - } -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt deleted file mode 100644 index 4502a628ec..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt +++ /dev/null @@ -1,224 +0,0 @@ -package com.tangem.features.send.impl.presentation.state - -import com.tangem.blockchain.common.TransactionData -import com.tangem.common.ui.amountScreen.converters.AmountStateConverter -import com.tangem.common.ui.amountScreen.converters.MaxEnterAmountConverter -import com.tangem.common.ui.amountScreen.models.AmountParameters -import com.tangem.common.ui.amountScreen.models.AmountState -import com.tangem.common.ui.notifications.NotificationUM -import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter -import com.tangem.core.ui.event.consumedEvent -import com.tangem.core.ui.extensions.stringReference -import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.domain.wallets.models.UserWallet -import com.tangem.features.send.impl.presentation.state.common.SendSyncEditConverter -import com.tangem.features.send.impl.presentation.state.confirm.SendConfirmStateConverter -import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState -import com.tangem.features.send.impl.presentation.state.fee.SendFeeStateConverter -import com.tangem.features.send.impl.presentation.state.fee.checkFeeCoverage -import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientStateConverter -import com.tangem.features.send.impl.presentation.model.SendClickIntents -import com.tangem.utils.Provider -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf -import java.math.BigDecimal - -@Suppress("LongParameterList") -internal class SendStateFactory( - private val clickIntents: SendClickIntents, - private val stateRouterProvider: Provider, - private val currentStateProvider: Provider, - private val userWalletProvider: Provider, - private val appCurrencyProvider: Provider, - private val cryptoCurrencyStatusProvider: Provider, - private val feeCryptoCurrencyStatusProvider: Provider, - private val isTapHelpPreviewEnabledProvider: Provider, -) { - private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter) - private val maxEnterAmountConverter = MaxEnterAmountConverter() - - private val amountStateConverter by lazy(LazyThreadSafetyMode.NONE) { - AmountStateConverter( - clickIntents = clickIntents, - appCurrencyProvider = appCurrencyProvider, - iconStateConverter = iconStateConverter, - cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, - maxEnterAmount = maxEnterAmountConverter.convert(cryptoCurrencyStatusProvider()), - ) - } - private val recipientStateConverter by lazy(LazyThreadSafetyMode.NONE) { - SendRecipientStateConverter( - clickIntents = clickIntents, - cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, - ) - } - private val feeStateConverter by lazy(LazyThreadSafetyMode.NONE) { - SendFeeStateConverter( - appCurrencyProvider = appCurrencyProvider, - feeCryptoCurrencyStatusProvider = feeCryptoCurrencyStatusProvider, - cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, - ) - } - private val confirmStateConverter by lazy(LazyThreadSafetyMode.NONE) { - SendConfirmStateConverter( - isTapHelpPreviewEnabledProvider = isTapHelpPreviewEnabledProvider, - ) - } - private val sendSyncEditConverter by lazy(LazyThreadSafetyMode.NONE) { - SendSyncEditConverter(currentStateProvider = currentStateProvider) - } - // region UI states - fun getInitialState(): SendUiState = SendUiState( - clickIntents = clickIntents, - event = consumedEvent(), - isEditingDisabled = false, - isBalanceHidden = false, - cryptoCurrencyName = "", - isSubtracted = false, - amountState = AmountState.Empty(false), - editAmountState = AmountState.Empty(false), - ) - - fun getReadyState(): SendUiState { - val state = currentStateProvider() - val amountState = if (state.amountState is AmountState.Empty) { - amountStateConverter.convert( - AmountParameters( - title = stringReference(userWalletProvider().name), - value = "", - ), - ) - } else { - state.amountState - } - return state.copy( - amountState = amountState, - recipientState = state.recipientState - ?: recipientStateConverter.convert(SendRecipientStateConverter.Data("", null)), - feeState = state.feeState ?: feeStateConverter.convert(Unit), - sendState = confirmStateConverter.convert(Unit), - cryptoCurrencyName = cryptoCurrencyStatusProvider().currency.name, - ) - } - - fun getReadyState(amount: String, destinationAddress: String, memo: String?): SendUiState { - val state = currentStateProvider() - val amountState = if (state.amountState is AmountState.Empty) { - amountStateConverter.convert( - AmountParameters( - title = stringReference(userWalletProvider().name), - value = amount, - ), - ) - } else { - state.amountState - } - return state.copy( - amountState = amountState, - recipientState = state.recipientState - ?: recipientStateConverter.convert(SendRecipientStateConverter.Data(destinationAddress, memo)), - feeState = state.feeState ?: feeStateConverter.convert(Unit), - sendState = confirmStateConverter.convert(Unit), - isEditingDisabled = true, - cryptoCurrencyName = cryptoCurrencyStatusProvider().currency.name, - ) - } - - fun syncEditStates(isFromEdit: Boolean) = sendSyncEditConverter.convert(isFromEdit) - - fun getOnHideBalanceState(isBalanceHidden: Boolean): SendUiState { - return currentStateProvider().copy(isBalanceHidden = isBalanceHidden) - } - //endregion - - //region send - fun getIsAmountSubtractedState(isAmountSubtractAvailable: Boolean): SendUiState { - val state = currentStateProvider() - val balance = cryptoCurrencyStatusProvider().value.amount ?: return state - val amountState = state.getAmountState(stateRouterProvider().isEditState) as? AmountState.Data ?: return state - val feeState = state.getFeeState(stateRouterProvider().isEditState) ?: return state - val amountValue = amountState.amountTextField.cryptoAmount.value ?: return state - val feeValue = feeState.fee?.amount?.value ?: BigDecimal.ZERO - return state.copy( - isSubtracted = checkFeeCoverage( - isSubtractAvailable = isAmountSubtractAvailable, - balance = balance, - amountValue = amountValue, - feeValue = feeValue, - reduceAmountBy = state.sendState?.reduceAmountBy, - ), - ) - } - - fun getSendingStateUpdate(isSending: Boolean): SendUiState { - val state = currentStateProvider() - return state.copy( - sendState = state.sendState?.copy( - isSending = isSending, - isPrimaryButtonEnabled = isPrimaryButtonEnabled( - state = state, - isSending = isSending, - notifications = state.sendState.notifications, - ), - ), - ) - } - - fun getTransactionSendState(txData: TransactionData.Uncompiled, txUrl: String): SendUiState { - val state = currentStateProvider() - val sendState = state.sendState ?: return state - return state.copy( - sendState = sendState.copy( - transactionDate = txData.date?.timeInMillis ?: System.currentTimeMillis(), - isSuccess = true, - showTapHelp = false, - txUrl = txUrl, - notifications = persistentListOf(), - ), - ) - } - - fun getSendNotificationState(notifications: ImmutableList): SendUiState { - val state = currentStateProvider() - val sendState = state.sendState ?: return state - val reducedBy = sendState.reduceAmountBy.takeIf { - notifications.none { - it is NotificationUM.Error.ExistentialDeposit || - it is NotificationUM.Error.TransactionLimitError || - it is NotificationUM.Warning.HighFeeError - } - } - return state.copy( - sendState = sendState.copy( - isPrimaryButtonEnabled = isPrimaryButtonEnabled( - state = state, - isSending = sendState.isSending, - notifications = notifications, - ), - reduceAmountBy = reducedBy, - notifications = notifications, - showTapHelp = sendState.showTapHelp && notifications.isEmpty(), - ), - ) - } - - fun getHiddenTapHelpState(): SendUiState { - val state = currentStateProvider() - val sendState = state.sendState ?: return state - return state.copy( - sendState = sendState.copy(showTapHelp = false), - ) - } - - private fun isPrimaryButtonEnabled( - state: SendUiState, - isSending: Boolean, - notifications: ImmutableList, - ): Boolean { - val feeState = state.getFeeState(stateRouterProvider().isEditState) ?: return false - val hasErrorNotifications = notifications.any { it is NotificationUM.Error } - return !hasErrorNotifications && !isSending && feeState.feeSelectorState is FeeSelectorState.Content - } - //endregion -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt deleted file mode 100644 index fd0769ac99..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt +++ /dev/null @@ -1,151 +0,0 @@ -package com.tangem.features.send.impl.presentation.state - -import androidx.compose.runtime.Immutable -import androidx.compose.runtime.Stable -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.ui.event.StateEvent -import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.features.send.impl.presentation.domain.SendRecipientListContent -import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState -import com.tangem.features.send.impl.presentation.state.fields.SendTextField -import com.tangem.features.send.impl.presentation.model.SendClickIntents -import kotlinx.collections.immutable.ImmutableList -import java.math.BigDecimal - -/** - * Ui states of the send screen - */ -@Immutable -internal data class SendUiState( - val clickIntents: SendClickIntents, - val isEditingDisabled: Boolean, - val cryptoCurrencyName: String, - val amountState: AmountState, - val recipientState: SendStates.RecipientState? = null, - val feeState: SendStates.FeeState? = null, - val sendState: SendStates.SendState? = null, - val editAmountState: AmountState, - val editRecipientState: SendStates.RecipientState? = null, - val editFeeState: SendStates.FeeState? = null, - val isBalanceHidden: Boolean, - val isSubtracted: Boolean, - val event: StateEvent, -) { - - fun getAmountState(isEditState: Boolean): AmountState { - return if (isEditState) { - editAmountState - } else { - amountState - } - } - - fun getRecipientState(isEditState: Boolean): SendStates.RecipientState? { - return if (isEditState) { - editRecipientState - } else { - recipientState - } - } - - fun getFeeState(isEditState: Boolean): SendStates.FeeState? { - return if (isEditState) { - editFeeState - } else { - feeState - } - } - - fun copyWrapped( - isEditState: Boolean, - amountState: AmountState = this.amountState, - feeState: SendStates.FeeState? = this.feeState, - recipientState: SendStates.RecipientState? = this.recipientState, - sendState: SendStates.SendState? = this.sendState, - ): SendUiState = if (isEditState) { - copy( - editAmountState = amountState, - editFeeState = feeState, - editRecipientState = recipientState, - sendState = sendState, - ) - } else { - copy( - amountState = amountState, - feeState = feeState, - recipientState = recipientState, - sendState = sendState, - ) - } -} - -@Stable -internal sealed class SendStates { - - abstract val type: SendUiStateType - - abstract val isPrimaryButtonEnabled: Boolean - - /** Recipient state */ - @Stable - data class RecipientState( - override val type: SendUiStateType = SendUiStateType.Recipient, - override val isPrimaryButtonEnabled: Boolean, - val addressTextField: SendTextField.RecipientAddress, - val memoTextField: SendTextField.RecipientMemo?, - val recent: ImmutableList, - val wallets: ImmutableList, - val network: String, - val isValidating: Boolean = false, - ) : SendStates() - - /** Fee and speed state */ - @Stable - data class FeeState( - override val type: SendUiStateType = SendUiStateType.Fee, - override val isPrimaryButtonEnabled: Boolean = false, - val feeSelectorState: FeeSelectorState, - val fee: Fee?, - val rate: BigDecimal?, - val isFeeConvertibleToFiat: Boolean, - val appCurrency: AppCurrency, - val isFeeApproximate: Boolean, - val isCustomSelected: Boolean, - val notifications: ImmutableList, - val isTronToken: Boolean, - ) : SendStates() - - /** Send state */ - @Stable - data class SendState( - override val type: SendUiStateType = SendUiStateType.Send, - override val isPrimaryButtonEnabled: Boolean = false, - val isSending: Boolean, - val isSuccess: Boolean, - val transactionDate: Long, - val txUrl: String, - val ignoreAmountReduce: Boolean, - val reduceAmountBy: BigDecimal?, - val isFromConfirmation: Boolean, - val showTapHelp: Boolean, - val notifications: ImmutableList, - ) : SendStates() -} - -data class SendUiCurrentScreen( - val type: SendUiStateType, - val isFromConfirmation: Boolean, -) - -enum class SendUiStateType { - None, - Recipient, - Amount, - Fee, - Send, - EditAmount, - EditRecipient, - EditFee, -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt deleted file mode 100644 index 809f8ada16..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt +++ /dev/null @@ -1,127 +0,0 @@ -package com.tangem.features.send.impl.presentation.state - -import com.tangem.common.routing.AppRouter -import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.features.send.impl.presentation.analytics.SendAnalyticEvents -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.update - -internal class StateRouter( - private val appRouter: AppRouter, - private val analyticsEventsHandler: AnalyticsEventHandler, - private val isEditingDisabled: Boolean, -) { - private val mutableCurrentState: MutableStateFlow = MutableStateFlow(getInitState()) - - val currentState: StateFlow - get() = mutableCurrentState - - val isEditState: Boolean - get() = currentState.value.isFromConfirmation - - fun clear() { - mutableCurrentState.update { getInitState() } - } - - fun popBackStack() { - appRouter.pop() - } - - fun onBackClick(isSuccess: Boolean = false) { - val type = currentState.value.type - when { - isSuccess -> popBackStack() - isEditingDisabled -> when (type) { - SendUiStateType.EditFee -> showSend() - else -> popBackStack() - } - else -> when (type) { - SendUiStateType.Amount -> showRecipient() - SendUiStateType.Fee -> showSend() - SendUiStateType.Send -> showAmount() - SendUiStateType.Recipient -> popBackStack() - SendUiStateType.EditAmount -> showSend() - SendUiStateType.EditRecipient -> showSend() - SendUiStateType.EditFee -> showSend() - else -> popBackStack() - } - } - } - - fun onNextClick() { - when (currentState.value.type) { - SendUiStateType.Recipient -> showAmount() - SendUiStateType.Amount, - SendUiStateType.Fee, - SendUiStateType.EditAmount, - SendUiStateType.EditRecipient, - SendUiStateType.EditFee, - -> showSend() - SendUiStateType.Send -> onBackClick() - - else -> popBackStack() - } - } - - fun onPrevClick() { - if (isEditingDisabled) { - popBackStack() - } else { - when (currentState.value.type) { - SendUiStateType.Amount -> showRecipient() - else -> popBackStack() - } - } - } - - fun showAmount(isFromConfirmation: Boolean = false) { - analyticsEventsHandler.send(SendAnalyticEvents.AmountScreenOpened) - mutableCurrentState.update { - if (isFromConfirmation) { - SendUiCurrentScreen(SendUiStateType.EditAmount, true) - } else { - SendUiCurrentScreen(SendUiStateType.Amount, false) - } - } - } - - fun showRecipient(isFromConfirmation: Boolean = false) { - analyticsEventsHandler.send(SendAnalyticEvents.AddressScreenOpened) - mutableCurrentState.update { - if (isFromConfirmation) { - SendUiCurrentScreen(SendUiStateType.EditRecipient, true) - } else { - SendUiCurrentScreen(SendUiStateType.Recipient, false) - } - } - } - - fun showFee(isFromConfirmation: Boolean = false) { - analyticsEventsHandler.send(SendAnalyticEvents.FeeScreenOpened) - mutableCurrentState.update { - if (isFromConfirmation) { - SendUiCurrentScreen(SendUiStateType.EditFee, true) - } else { - SendUiCurrentScreen(SendUiStateType.Fee, false) - } - } - } - - fun showSend() { - analyticsEventsHandler.send(SendAnalyticEvents.ConfirmationScreenOpened) - mutableCurrentState.update { SendUiCurrentScreen(SendUiStateType.Send, isFromConfirmation = false) } - } - - private fun getInitState() = if (isEditingDisabled) { - SendUiCurrentScreen( - type = SendUiStateType.None, - isFromConfirmation = false, - ) - } else { - SendUiCurrentScreen( - type = SendUiStateType.Recipient, - isFromConfirmation = false, - ) - } -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt deleted file mode 100644 index 9b8d11fb41..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt +++ /dev/null @@ -1,89 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.amount - -import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer -import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary -import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.features.send.impl.presentation.state.SendUiState -import com.tangem.features.send.impl.presentation.state.StateRouter -import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldChangeConverter -import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldMaxAmountConverter -import com.tangem.utils.Provider -import java.math.BigDecimal - -/** - * Factory to produce amount state for [SendUiState] - */ -internal class AmountStateFactory( - private val stateRouterProvider: Provider, - private val currentStateProvider: Provider, - private val cryptoCurrencyStatusProvider: Provider, - private val minimumTransactionAmountProvider: Provider, -) { - - private val amountFieldChangeConverter by lazy(LazyThreadSafetyMode.NONE) { - SendAmountFieldChangeConverter( - stateRouterProvider = stateRouterProvider, - currentStateProvider = currentStateProvider, - cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, - minimumTransactionAmountProvider = minimumTransactionAmountProvider, - ) - } - private val amountFieldMaxAmountConverter by lazy(LazyThreadSafetyMode.NONE) { - SendAmountFieldMaxAmountConverter( - stateRouterProvider = stateRouterProvider, - currentStateProvider = currentStateProvider, - cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, - minimumTransactionAmountProvider = minimumTransactionAmountProvider, - ) - } - - private val amountCurrencyConverter by lazy(LazyThreadSafetyMode.NONE) { - SendAmountCurrencyConverter( - stateRouterProvider = stateRouterProvider, - currentStateProvider = currentStateProvider, - cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, - ) - } - private val amountPasteConverter by lazy(LazyThreadSafetyMode.NONE) { - SendAmountPastedTriggerDismissConverter( - stateRouterProvider = stateRouterProvider, - currentStateProvider = currentStateProvider, - ) - } - private val amountReduceByConverter by lazy { - SendAmountReduceByConverter( - stateRouterProvider = stateRouterProvider, - currentStateProvider = currentStateProvider, - cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, - minimumTransactionAmountProvider = minimumTransactionAmountProvider, - ) - } - private val amountReduceToConverter by lazy { - SendAmountReduceToConverter( - stateRouterProvider = stateRouterProvider, - currentStateProvider = currentStateProvider, - cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, - minimumTransactionAmountProvider = minimumTransactionAmountProvider, - ) - } - - fun getOnAmountValueChange(value: String) = amountFieldChangeConverter.convert(value) - - fun getOnAmountReduceByState(reduceAmountBy: BigDecimal, reduceAmountByDiff: BigDecimal) = - amountReduceByConverter.convert( - AmountReduceByTransformer.ReduceByData( - reduceAmountBy = reduceAmountBy, - reduceAmountByDiff = reduceAmountByDiff, - ), - ) - - fun getOnAmountReduceToState(reduceAmountTo: BigDecimal) = amountReduceToConverter.convert(reduceAmountTo) - - fun getOnMaxAmountClick(): SendUiState { - return amountFieldMaxAmountConverter.convert(Unit) - } - - fun getOnCurrencyChangedState(isFiat: Boolean) = amountCurrencyConverter.convert(isFiat) - - fun getOnAmountPastedTriggerDismiss() = amountPasteConverter.convert(false) -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountCurrencyConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountCurrencyConverter.kt deleted file mode 100644 index c59bf9780c..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountCurrencyConverter.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.amount - -import com.tangem.common.ui.amountScreen.converters.AmountCurrencyTransformer -import com.tangem.common.ui.amountScreen.models.AmountState -import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.features.send.impl.presentation.state.SendUiState -import com.tangem.features.send.impl.presentation.state.StateRouter -import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter - -internal class SendAmountCurrencyConverter( - private val stateRouterProvider: Provider, - private val currentStateProvider: Provider, - private val cryptoCurrencyStatusProvider: Provider, -) : Converter { - - override fun convert(value: Boolean): SendUiState { - val state = currentStateProvider() - val isEditState = stateRouterProvider().isEditState - val amountState = state.getAmountState(isEditState) as? AmountState.Data ?: return state - - return state.copyWrapped( - isEditState = isEditState, - amountState = AmountCurrencyTransformer(cryptoCurrencyStatusProvider(), value).transform(amountState), - ) - } -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountPastedTriggerDismissConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountPastedTriggerDismissConverter.kt deleted file mode 100644 index a1d6b22fb4..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountPastedTriggerDismissConverter.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.amount - -import com.tangem.common.ui.amountScreen.converters.AmountPastedTriggerDismissTransformer -import com.tangem.common.ui.amountScreen.models.AmountState -import com.tangem.features.send.impl.presentation.state.SendUiState -import com.tangem.features.send.impl.presentation.state.StateRouter -import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter - -internal class SendAmountPastedTriggerDismissConverter( - private val stateRouterProvider: Provider, - private val currentStateProvider: Provider, -) : Converter { - - override fun convert(value: Boolean): SendUiState { - val state = currentStateProvider() - val isEditState = stateRouterProvider().isEditState - val amountState = state.getAmountState(isEditState) as? AmountState.Data ?: return state - - return state.copyWrapped( - isEditState = isEditState, - amountState = AmountPastedTriggerDismissTransformer.transform(amountState), - ) - } -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReduceByConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReduceByConverter.kt deleted file mode 100644 index c0e28102e7..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReduceByConverter.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.amount - -import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer -import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary -import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.features.send.impl.presentation.state.SendUiState -import com.tangem.features.send.impl.presentation.state.StateRouter -import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter - -internal class SendAmountReduceByConverter( - private val stateRouterProvider: Provider, - private val currentStateProvider: Provider, - private val cryptoCurrencyStatusProvider: Provider, - private val minimumTransactionAmountProvider: Provider, -) : Converter { - - override fun convert(value: AmountReduceByTransformer.ReduceByData): SendUiState { - val state = currentStateProvider() - val isEditState = stateRouterProvider().isEditState - val amountState = state.getAmountState(isEditState) ?: return state - - return state.copyWrapped( - isEditState = isEditState, - sendState = state.sendState?.copy( - reduceAmountBy = value.reduceAmountBy, - ), - amountState = AmountReduceByTransformer( - cryptoCurrencyStatus = cryptoCurrencyStatusProvider(), - minimumTransactionAmount = minimumTransactionAmountProvider(), - value = value, - ).transform(amountState), - ) - } -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReduceToConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReduceToConverter.kt deleted file mode 100644 index ea1caaf180..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReduceToConverter.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.amount - -import com.tangem.common.ui.amountScreen.converters.AmountReduceToTransformer -import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary -import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.features.send.impl.presentation.state.SendUiState -import com.tangem.features.send.impl.presentation.state.StateRouter -import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter -import java.math.BigDecimal - -internal class SendAmountReduceToConverter( - private val stateRouterProvider: Provider, - private val currentStateProvider: Provider, - private val cryptoCurrencyStatusProvider: Provider, - private val minimumTransactionAmountProvider: Provider, -) : Converter { - - override fun convert(value: BigDecimal): SendUiState { - val state = currentStateProvider() - val isEditState = stateRouterProvider().isEditState - val amountState = state.getAmountState(isEditState) ?: return state - - return state.copyWrapped( - isEditState = isEditState, - amountState = AmountReduceToTransformer( - cryptoCurrencyStatus = cryptoCurrencyStatusProvider(), - minimumTransactionAmount = minimumTransactionAmountProvider(), - value = value, - ).transform(amountState), - ) - } -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/common/SendSyncEditConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/common/SendSyncEditConverter.kt deleted file mode 100644 index f0abbba036..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/common/SendSyncEditConverter.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.common - -import com.tangem.features.send.impl.presentation.state.SendUiState -import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter - -internal class SendSyncEditConverter( - private val currentStateProvider: Provider, -) : Converter { - override fun convert(value: Boolean): SendUiState { - val state = currentStateProvider() - return if (value) { - state.copy( - amountState = state.editAmountState, - feeState = state.editFeeState, - recipientState = state.editRecipientState, - ) - } else { - state.copy( - editAmountState = state.amountState, - editRecipientState = state.recipientState, - editFeeState = state.feeState, - ) - } - } -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendConfirmStateConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendConfirmStateConverter.kt deleted file mode 100644 index 254a979807..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendConfirmStateConverter.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.confirm - -import com.tangem.features.send.impl.presentation.state.SendStates -import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter -import kotlinx.collections.immutable.persistentListOf - -internal class SendConfirmStateConverter( - private val isTapHelpPreviewEnabledProvider: Provider, -) : Converter { - override fun convert(value: Unit): SendStates.SendState { - return SendStates.SendState( - isPrimaryButtonEnabled = false, - isSending = false, - isSuccess = false, - transactionDate = 0L, - txUrl = "", - ignoreAmountReduce = false, - reduceAmountBy = null, - isFromConfirmation = true, - showTapHelp = isTapHelpPreviewEnabledProvider(), - notifications = persistentListOf(), - ) - } -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt deleted file mode 100644 index 1fd74a9383..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt +++ /dev/null @@ -1,374 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.confirm - -import com.tangem.blockchain.common.transaction.Fee -import com.tangem.blockchain.common.transaction.TransactionFee -import com.tangem.common.ui.R -import com.tangem.common.ui.amountScreen.models.AmountState -import com.tangem.common.ui.notifications.NotificationUM -import com.tangem.common.ui.notifications.NotificationsFactory.addDustWarningNotification -import com.tangem.common.ui.notifications.NotificationsFactory.addExceedBalanceNotification -import com.tangem.common.ui.notifications.NotificationsFactory.addExceedsBalanceNotification -import com.tangem.common.ui.notifications.NotificationsFactory.addExistentialWarningNotification -import com.tangem.common.ui.notifications.NotificationsFactory.addFeeCoverageNotification -import com.tangem.common.ui.notifications.NotificationsFactory.addFeeUnreachableNotification -import com.tangem.common.ui.notifications.NotificationsFactory.addMinimumAmountErrorNotification -import com.tangem.common.ui.notifications.NotificationsFactory.addRentExemptionNotification -import com.tangem.common.ui.notifications.NotificationsFactory.addReserveAmountErrorNotification -import com.tangem.common.ui.notifications.NotificationsFactory.addTransactionLimitErrorNotification -import com.tangem.common.ui.notifications.NotificationsFactory.addValidateTransactionNotifications -import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.utils.parseToBigDecimal -import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.domain.notifications.GetTronFeeNotificationShowCountUseCase -import com.tangem.domain.tokens.GetBalanceNotEnoughForFeeWarningUseCase -import com.tangem.domain.tokens.GetCurrencyCheckUseCase -import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.domain.tokens.model.warnings.CryptoCurrencyCheck -import com.tangem.domain.transaction.error.GetFeeError -import com.tangem.domain.transaction.usecase.ValidateTransactionUseCase -import com.tangem.domain.utils.convertToSdkAmount -import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.features.send.impl.presentation.analytics.SendAnalyticEvents -import com.tangem.features.send.impl.presentation.model.SendClickIntents -import com.tangem.features.send.impl.presentation.state.SendStates -import com.tangem.features.send.impl.presentation.state.SendUiState -import com.tangem.features.send.impl.presentation.state.SendUiStateType -import com.tangem.features.send.impl.presentation.state.StateRouter -import com.tangem.features.send.impl.presentation.state.fee.* -import com.tangem.lib.crypto.BlockchainUtils -import com.tangem.lib.crypto.BlockchainUtils.getTezosThreshold -import com.tangem.lib.crypto.BlockchainUtils.isTezos -import com.tangem.lib.crypto.BlockchainUtils.isTron -import com.tangem.utils.Provider -import com.tangem.utils.extensions.orZero -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.map -import java.math.BigDecimal - -@Suppress("LongParameterList", "LargeClass") -internal class SendNotificationFactory( - private val analyticsEventHandler: AnalyticsEventHandler, - private val validateTransactionUseCase: ValidateTransactionUseCase, - private val getCurrencyCheckUseCase: GetCurrencyCheckUseCase, - private val getBalanceNotEnoughForFeeWarningUseCase: GetBalanceNotEnoughForFeeWarningUseCase, - private val getTronFeeNotificationShowCountUseCase: GetTronFeeNotificationShowCountUseCase, - private val cryptoCurrencyStatusProvider: Provider, - private val feeCryptoCurrencyStatusProvider: Provider, - private val currentStateProvider: Provider, - private val stateRouterProvider: Provider, - private val isSubtractAvailableProvider: Provider, - private val appCurrencyProvider: Provider, - private val clickIntents: SendClickIntents, - private val userWalletId: UserWalletId, -) { - - fun create(): Flow> = stateRouterProvider().currentState - .filter { it.type == SendUiStateType.Send } - .map { - val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() - - val state = currentStateProvider() - val isEditState = stateRouterProvider().isEditState - val balance = cryptoCurrencyStatus.value.amount.orZero() - val sendState = state.sendState ?: return@map persistentListOf() - val feeState = state.getFeeState(isEditState) ?: return@map persistentListOf() - val amountState = state.getAmountState(isEditState) as? AmountState.Data ?: return@map persistentListOf() - val recipientState = state.getRecipientState(isEditState) - - val amountValue = amountState.amountTextField.cryptoAmount.value.orZero() - val feeValue = feeState.fee?.amount?.value.orZero() - val reduceAmountBy = sendState.reduceAmountBy.orZero() - val isFeeCoverage = checkFeeCoverage( - isSubtractAvailable = isSubtractAvailableProvider(), - balance = balance, - amountValue = amountValue, - feeValue = feeValue, - reduceAmountBy = reduceAmountBy, - ) - val sendingAmount = checkAndCalculateSubtractedAmount( - isAmountSubtractAvailable = isSubtractAvailableProvider(), - cryptoCurrencyStatus = cryptoCurrencyStatus, - amountValue = amountValue, - feeValue = feeValue, - reduceAmountBy = reduceAmountBy, - ) - val feeError = (feeState.feeSelectorState as? FeeSelectorState.Error)?.error - - val recipientAddress = state.recipientState?.addressTextField?.value - val feeCurrencyBalanceAfterTransaction = getFeeCurrencyBalanceAfterTx( - feeCurrencyStatus = feeCryptoCurrencyStatusProvider(), - sendingCurrencyStatus = cryptoCurrencyStatus, - sendingAmount = sendingAmount, - feeValue = feeValue, - ) - val currencyCheck = getCurrencyCheckUseCase( - userWalletId = userWalletId, - currencyStatus = cryptoCurrencyStatus, - amount = sendingAmount, - fee = feeValue, - recipientAddress = recipientAddress, - feeCurrencyBalanceAfterTransaction = feeCurrencyBalanceAfterTransaction, - ) - buildList { - addErrorNotifications( - feeError = feeError, - sendingAmount = sendingAmount, - feeValue = feeValue, - currencyCheck = currencyCheck, - ) - addWarningNotifications( - amountState = amountState, - recipientState = recipientState, - feeState = feeState, - sendState = sendState, - sendingAmount = sendingAmount, - isFeeCoverage = isFeeCoverage, - currencyCheck = currencyCheck, - ) - addInfoNotifications() - }.toImmutableList() - } - - private suspend fun MutableList.addInfoNotifications() { - addTronNetworkFeesNotification() - } - - fun dismissNotificationState(clazz: Class, isIgnored: Boolean = false): SendUiState { - val state = currentStateProvider() - val sendState = state.sendState ?: return state - val notificationsToRemove = sendState.notifications.filterIsInstance(clazz) - val updatedNotifications = sendState.notifications.toMutableList() - updatedNotifications.removeAll(notificationsToRemove) - return state.copy( - sendState = sendState.copy( - ignoreAmountReduce = isIgnored, - reduceAmountBy = if (isIgnored) null else sendState.reduceAmountBy, - notifications = updatedNotifications.toImmutableList(), - ), - ) - } - - private fun getFeeCurrencyBalanceAfterTx( - feeCurrencyStatus: CryptoCurrencyStatus?, - sendingCurrencyStatus: CryptoCurrencyStatus, - sendingAmount: BigDecimal, - feeValue: BigDecimal, - ): BigDecimal? { - val sendingCurrencyBalance = sendingCurrencyStatus.value as? CryptoCurrencyStatus.Loaded - val feeCurrencyBalance = feeCurrencyStatus?.value as? CryptoCurrencyStatus.Loaded - if (feeCurrencyStatus?.value !is CryptoCurrencyStatus.Loaded) return null - return when { - feeCurrencyStatus == sendingCurrencyStatus -> sendingCurrencyBalance?.let { - it.amount - sendingAmount - feeValue - } - else -> feeCurrencyBalance?.let { it.amount - feeValue } - } - } - - private suspend fun MutableList.addErrorNotifications( - feeError: GetFeeError?, - sendingAmount: BigDecimal, - feeValue: BigDecimal, - currencyCheck: CryptoCurrencyCheck, - ) { - val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() - val currency = cryptoCurrencyStatusProvider().currency - val currencyWarning = getBalanceNotEnoughForFeeWarningUseCase( - fee = feeValue, - userWalletId = userWalletId, - tokenStatus = cryptoCurrencyStatus, - coinStatus = feeCryptoCurrencyStatusProvider() ?: cryptoCurrencyStatus, - ).getOrNull() - - addFeeUnreachableNotification( - tokenStatus = cryptoCurrencyStatus, - coinStatus = feeCryptoCurrencyStatusProvider() ?: cryptoCurrencyStatus, - feeError = feeError, - onReload = clickIntents::feeReload, - onClick = clickIntents::onTokenDetailsClick, - ) - addExceedBalanceNotification( - feeAmount = feeValue, - sendingAmount = sendingAmount, - isSubtractionAvailable = isSubtractAvailableProvider(), - cryptoCurrencyStatus = cryptoCurrencyStatus, - ) - addExceedsBalanceNotification( - cryptoCurrencyWarning = currencyWarning, - cryptoCurrencyStatus = cryptoCurrencyStatus, - shouldMergeFeeNetworkName = BlockchainUtils.isArbitrum(currency.network.backendId), - onClick = clickIntents::onTokenDetailsClick, - onAnalyticsEvent = { - analyticsEventHandler.send( - SendAnalyticEvents.NoticeNotEnoughFee( - token = cryptoCurrencyStatus.currency.symbol, - blockchain = cryptoCurrencyStatus.currency.network.name, - ), - ) - }, - ) - if (!BlockchainUtils.isCardano(currency.network.rawId)) { - addDustWarningNotification( - dustValue = currencyCheck.dustValue, - feeValue = feeValue, - sendingAmount = sendingAmount, - cryptoCurrencyStatus = cryptoCurrencyStatus, - feeCurrencyStatus = feeCryptoCurrencyStatusProvider(), - ) - } - addTransactionLimitErrorNotification( - currencyCheck = currencyCheck, - sendingAmount = sendingAmount, - cryptoCurrencyStatus = cryptoCurrencyStatus, - feeCurrencyStatus = feeCryptoCurrencyStatusProvider(), - feeValue = feeValue, - onReduceClick = clickIntents::onAmountReduceToClick, - ) - addReserveAmountErrorNotification( - reserveAmount = currencyCheck.reserveAmount, - sendingAmount = sendingAmount, - cryptoCurrency = currency, - isAccountFunded = currencyCheck.isAccountFunded, - ) - addMinimumAmountErrorNotification( - minimumSendAmount = currencyCheck.minimumSendAmount, - sendingAmount = sendingAmount, - cryptoCurrency = currency, - ) - } - - private suspend fun MutableList.addWarningNotifications( - amountState: AmountState.Data, - recipientState: SendStates.RecipientState?, - feeState: SendStates.FeeState, - sendState: SendStates.SendState, - sendingAmount: BigDecimal, - isFeeCoverage: Boolean, - currencyCheck: CryptoCurrencyCheck, - ) { - val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() - val currency = cryptoCurrencyStatus.currency - val amountValue = amountState.amountTextField.cryptoAmount.value - val validationError = amountValue?.let { - validateTransactionUseCase( - userWalletId = userWalletId, - amount = amountValue.convertToSdkAmount(cryptoCurrencyStatus.currency), - fee = feeState.fee, - memo = recipientState?.memoTextField?.value.orEmpty(), - destination = recipientState?.addressTextField?.value.orEmpty(), - network = cryptoCurrencyStatus.currency.network, - ).leftOrNull() - } - - addRentExemptionNotification( - rentWarning = currencyCheck.rentWarning, - ) - - addExistentialWarningNotification( - existentialDeposit = currencyCheck.existentialDeposit, - feeAmount = feeState.fee?.amount?.value.orZero(), - sendingAmount = sendingAmount, - cryptoCurrencyStatus = cryptoCurrencyStatus, - onReduceClick = clickIntents::onAmountReduceByClick, - ) - addFeeCoverageNotification( - isFeeCoverage = isFeeCoverage, - amountField = amountState.amountTextField, - sendingValue = sendingAmount, - appCurrency = appCurrencyProvider(), - cryptoCurrencyStatus = cryptoCurrencyStatus, - ) - addValidateTransactionNotifications( - dustValue = currencyCheck.dustValue.orZero(), - minAdaValue = (feeState.fee as? Fee.CardanoToken)?.minAdaValue, - validationError = validationError, - cryptoCurrency = currency, - onReduceClick = clickIntents::onAmountReduceToClick, - ) - - addHighFeeWarningNotification( - amountState.amountTextField.cryptoAmount.value.orZero(), - sendState.ignoreAmountReduce, - ) - addTooHighNotification(feeState.feeSelectorState) - addTooLowNotification(feeState) - } - - private fun MutableList.addHighFeeWarningNotification( - sendAmount: BigDecimal, - ignoreAmountReduce: Boolean, - ) { - val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() - val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO - val isTezos = isTezos(cryptoCurrencyStatus.currency.network.rawId) - val threshold = getTezosThreshold() - val isTotalBalance = sendAmount >= balance && balance > threshold - if (!ignoreAmountReduce && isTotalBalance && isTezos) { - add( - NotificationUM.Warning.HighFeeError( - currencyName = cryptoCurrencyStatus.currency.name, - amount = threshold.toPlainString(), - onConfirmClick = { - clickIntents.onAmountReduceByClick( - reduceAmountBy = threshold, - reduceAmountByDiff = threshold, - notification = NotificationUM.Warning.HighFeeError::class.java, - ) - }, - onCloseClick = { - clickIntents.onNotificationCancel(NotificationUM.Warning.HighFeeError::class.java) - }, - ), - ) - } - } - - private fun MutableList.addTooLowNotification(feeState: SendStates.FeeState) { - val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return - val multipleFees = feeSelectorState.fees as? TransactionFee.Choosable ?: return - val minimumValue = multipleFees.minimum.amount.value ?: return - val customAmount = feeSelectorState.customValues.firstOrNull() ?: return - val customValue = customAmount.value.parseToBigDecimal(customAmount.decimals) - if (feeSelectorState.selectedFee == FeeType.Custom && minimumValue > customValue) { - add(NotificationUM.Warning.FeeTooLow) - analyticsEventHandler.send( - SendAnalyticEvents.NoticeTransactionDelays( - cryptoCurrencyStatusProvider().currency.symbol, - ), - ) - } - } - - private fun MutableList.addTooHighNotification(feeSelectorState: FeeSelectorState) { - if (feeSelectorState !is FeeSelectorState.Content) return - - checkIfFeeTooHigh(feeSelectorState) { diff -> - add(NotificationUM.Warning.TooHigh(diff)) - } - } - - private suspend fun MutableList.addTronNetworkFeesNotification() { - val cryptoCurrency = cryptoCurrencyStatusProvider().currency - val isTronToken = cryptoCurrency is CryptoCurrency.Token && - isTron(cryptoCurrency.network.rawId) - - if (isTronToken && getTronFeeNotificationShowCountUseCase() <= TRON_FEE_NOTIFICATION_MAX_SHOW_COUNT) { - add( - NotificationUM.Info( - title = resourceReference(R.string.tron_will_be_send_token_fee_title), - subtitle = resourceReference(R.string.tron_will_be_send_token_fee_description), - ), - ) - } - } - - companion object { - const val TRON_FEE_NOTIFICATION_MAX_SHOW_COUNT = 3 - } -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt deleted file mode 100644 index a13f13cc19..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt +++ /dev/null @@ -1,89 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.fee - -import com.tangem.blockchain.common.transaction.TransactionFee -import com.tangem.common.extensions.isZero -import com.tangem.core.ui.utils.parseBigDecimal -import com.tangem.core.ui.utils.parseToBigDecimal -import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import java.math.BigDecimal -import java.math.RoundingMode - -/** - * Check and calculates subtracted amount - */ -internal fun checkAndCalculateSubtractedAmount( - isAmountSubtractAvailable: Boolean, - cryptoCurrencyStatus: CryptoCurrencyStatus, - amountValue: BigDecimal, - feeValue: BigDecimal, - reduceAmountBy: BigDecimal, -): BigDecimal { - val balance = cryptoCurrencyStatus.value.amount ?: return amountValue - val isFeeCoverage = checkFeeCoverage( - isSubtractAvailable = isAmountSubtractAvailable, - balance = balance, - amountValue = amountValue, - feeValue = feeValue, - reduceAmountBy = reduceAmountBy, - ) - return if (isFeeCoverage) { - balance.minus(reduceAmountBy).minus(feeValue) - } else { - amountValue - } -} - -/** - * Checks if sending amount with fee is greater than balance - */ -internal fun checkFeeCoverage( - isSubtractAvailable: Boolean, - balance: BigDecimal, - amountValue: BigDecimal, - feeValue: BigDecimal, - reduceAmountBy: BigDecimal?, -): Boolean { - if (!isSubtractAvailable) return false - val reducedBy = balance - (reduceAmountBy ?: BigDecimal.ZERO) - return reducedBy < amountValue + feeValue && reducedBy > feeValue && reducedBy >= amountValue -} - -/** - * Check if custom fee is too low - */ -internal fun checkIfFeeTooLow(feeSelectorState: FeeSelectorState.Content): Boolean { - val multipleFees = feeSelectorState.fees as? TransactionFee.Choosable ?: return false - val minimumValue = multipleFees.minimum.amount.value ?: return false - val customAmount = feeSelectorState.customValues.firstOrNull() ?: return false - val customValue = customAmount.value.parseToBigDecimal(customAmount.decimals) - - return feeSelectorState.selectedFee == FeeType.Custom && minimumValue > customValue -} - -/** - * Check if custom fee is too high - */ -internal fun checkIfFeeTooHigh(feeSelectorState: FeeSelectorState.Content, onShow: (String) -> Unit): Boolean { - val multipleFees = feeSelectorState.fees as? TransactionFee.Choosable ?: return false - val highValue = multipleFees.priority.amount.value ?: return false - val customAmount = feeSelectorState.customValues.firstOrNull() ?: return false - val customValue = customAmount.value.parseToBigDecimal(customAmount.decimals) - val diff = if (highValue > BigDecimal.ZERO) { - customValue / highValue - } else { - BigDecimal.ZERO - } - val isShow = feeSelectorState.selectedFee == FeeType.Custom && diff > FEE_MAX_DIFF - if (isShow) onShow(diff.parseBigDecimal(ZERO_DECIMALS, RoundingMode.HALF_UP)) - return isShow -} - -/** - * Checks if fee exceeds fee paid currency balance - */ -fun checkExceedBalance(feeBalance: BigDecimal?, feeAmount: BigDecimal?): Boolean { - return feeAmount == null || feeBalance == null || feeAmount.isZero() || feeAmount > feeBalance -} - -private val FEE_MAX_DIFF = BigDecimal("5") -private const val ZERO_DECIMALS = 0 \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeConverter.kt deleted file mode 100644 index 54424e3b07..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeConverter.kt +++ /dev/null @@ -1,89 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.fee - -import com.tangem.blockchain.common.transaction.Fee -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.impl.presentation.state.StateRouter -import com.tangem.features.send.impl.presentation.state.fee.custom.BitcoinCustomFeeConverter -import com.tangem.features.send.impl.presentation.state.fee.custom.EthereumCustomFeeConverter -import com.tangem.features.send.impl.presentation.state.fee.custom.KaspaCustomFeeConverter -import com.tangem.features.send.impl.presentation.model.SendClickIntents -import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter - -internal class FeeConverter( - private val clickIntents: SendClickIntents, - private val stateRouterProvider: Provider, - private val appCurrencyProvider: Provider, - private val feeCryptoCurrencyStatusProvider: Provider, -) : Converter { - - private val ethereumCustomFeeConverter by lazy(LazyThreadSafetyMode.NONE) { - EthereumCustomFeeConverter( - clickIntents = clickIntents, - stateRouterProvider = stateRouterProvider, - appCurrencyProvider = appCurrencyProvider, - feeCryptoCurrencyStatusProvider = feeCryptoCurrencyStatusProvider, - ) - } - - private val bitcoinCustomFeeConverter by lazy(LazyThreadSafetyMode.NONE) { - BitcoinCustomFeeConverter( - clickIntents = clickIntents, - stateRouterProvider = stateRouterProvider, - appCurrencyProvider = appCurrencyProvider, - feeCryptoCurrencyStatusProvider = feeCryptoCurrencyStatusProvider, - ) - } - - private val kaspaCustomFeeConverter by lazy(LazyThreadSafetyMode.NONE) { - KaspaCustomFeeConverter( - clickIntents = clickIntents, - appCurrencyProvider = appCurrencyProvider, - feeCryptoCurrencyStatusProvider = feeCryptoCurrencyStatusProvider, - ) - } - - override fun convert(value: FeeSelectorState.Content): Fee { - return when (val fees = value.fees) { - is TransactionFee.Choosable -> { - when (value.selectedFee) { - FeeType.Slow -> fees.minimum - FeeType.Market -> fees.normal - FeeType.Fast -> fees.priority - FeeType.Custom -> convertCustom(value, fees) - } - } - is TransactionFee.Single -> - when (value.selectedFee) { - FeeType.Market -> fees.normal - FeeType.Custom -> convertCustom(value, fees) - else -> fees.normal - } - } - } - - private fun convertCustom(feeSelectorState: FeeSelectorState.Content, fees: TransactionFee): Fee { - val customValues = feeSelectorState.customValues - val normalFee = fees.normal - return if (customValues.isEmpty()) { - normalFee - } else { - when (normalFee) { - is Fee.Ethereum -> ethereumCustomFeeConverter.convertBack(normalFee = normalFee, value = customValues) - is Fee.Bitcoin -> bitcoinCustomFeeConverter.convertBack(normalFee = normalFee, value = customValues) - is Fee.Kaspa -> kaspaCustomFeeConverter.convertBack(normalFee = normalFee, value = customValues) - else -> { - val customFee = customValues.firstOrNull() - Fee.Common( - normalFee.amount.copy( - value = customFee?.value?.parseToBigDecimal(customFee.decimals), - ), - ) - } - } - } - } -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeNotificationFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeNotificationFactory.kt deleted file mode 100644 index 6eaa94816c..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeNotificationFactory.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.fee - -import com.tangem.common.ui.notifications.NotificationsFactory.addFeeUnreachableNotification -import com.tangem.features.send.impl.presentation.state.SendUiState -import com.tangem.features.send.impl.presentation.state.SendUiStateType -import com.tangem.features.send.impl.presentation.state.StateRouter -import com.tangem.features.send.impl.presentation.model.SendClickIntents -import com.tangem.utils.Provider -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.map - -@Suppress("LongParameterList") -internal class FeeNotificationFactory( - private val currentStateProvider: Provider, - private val stateRouterProvider: Provider, - private val clickIntents: SendClickIntents, -) { - - fun create() = stateRouterProvider().currentState - .filter { it.type == SendUiStateType.Fee || it.type == SendUiStateType.EditFee } - .map { - val state = currentStateProvider() - val feeState = state.getFeeState(stateRouterProvider().isEditState) ?: return@map persistentListOf() - buildList { - addFeeUnreachableNotification( - feeError = (feeState.feeSelectorState as? FeeSelectorState.Error)?.error, - tokenName = state.cryptoCurrencyName, - onReload = clickIntents::feeReload, - ) - }.toImmutableList() - } -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeSelectorState.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeSelectorState.kt deleted file mode 100644 index 20c93c2458..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeSelectorState.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.fee - -import androidx.compose.runtime.Immutable -import com.tangem.blockchain.common.transaction.TransactionFee -import com.tangem.domain.transaction.error.GetFeeError -import com.tangem.features.send.impl.presentation.state.fields.SendTextField -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf - -@Immutable -internal sealed class FeeSelectorState { - - data class Content( - val fees: TransactionFee, - val selectedFee: FeeType = FeeType.Market, - val customValues: ImmutableList = persistentListOf(), - ) : FeeSelectorState() - - data object Loading : FeeSelectorState() - - data class Error( - val error: GetFeeError?, - ) : FeeSelectorState() -} - -enum class FeeType { - Slow, - Market, - Fast, - Custom, -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt deleted file mode 100644 index 0ff24473ac..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt +++ /dev/null @@ -1,236 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.fee - -import com.tangem.blockchain.common.Token -import com.tangem.blockchain.common.transaction.TransactionFee -import com.tangem.common.extensions.isZero -import com.tangem.common.ui.amountScreen.models.AmountState -import com.tangem.common.ui.notifications.NotificationUM -import com.tangem.core.ui.utils.parseToBigDecimal -import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.tokens.model.AmountType -import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.domain.transaction.error.GetFeeError -import com.tangem.domain.transaction.usecase.IsFeeApproximateUseCase -import com.tangem.features.send.impl.presentation.state.SendStates -import com.tangem.features.send.impl.presentation.state.SendUiState -import com.tangem.features.send.impl.presentation.state.StateRouter -import com.tangem.features.send.impl.presentation.model.SendClickIntents -import com.tangem.utils.Provider -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf -import com.tangem.blockchain.common.AmountType as SdkAmountType - -/** - * Factory to produce fee state for [SendUiState] - */ -internal class FeeStateFactory( - private val clickIntents: SendClickIntents, - private val stateRouterProvider: Provider, - private val currentStateProvider: Provider, - private val feeCryptoCurrencyStatusProvider: Provider, - private val appCurrencyProvider: Provider, - private val isFeeApproximateUseCase: IsFeeApproximateUseCase, -) { - private val customFeeFieldConverter by lazy(LazyThreadSafetyMode.NONE) { - SendFeeCustomFieldConverter( - clickIntents = clickIntents, - stateRouterProvider = stateRouterProvider, - appCurrencyProvider = appCurrencyProvider, - feeCryptoCurrencyStatusProvider = feeCryptoCurrencyStatusProvider, - ) - } - - val feeConverter by lazy(LazyThreadSafetyMode.NONE) { - FeeConverter( - clickIntents = clickIntents, - stateRouterProvider = stateRouterProvider, - appCurrencyProvider = appCurrencyProvider, - feeCryptoCurrencyStatusProvider = feeCryptoCurrencyStatusProvider, - ) - } - - fun onFeeOnLoadingState(): SendUiState { - val state = currentStateProvider() - val isEditState = stateRouterProvider().isEditState - val feeState = state.getFeeState(isEditState) ?: return state - return state.copyWrapped( - isEditState = isEditState, - sendState = state.sendState?.copy( - isPrimaryButtonEnabled = false, - ), - feeState = feeState.copy( - feeSelectorState = if (feeState.feeSelectorState is FeeSelectorState.Content) { - feeState.feeSelectorState - } else { - FeeSelectorState.Loading - }, - isPrimaryButtonEnabled = false, - ), - ) - } - - fun onFeeOnLoadedState(fees: TransactionFee): SendUiState { - val state = currentStateProvider() - val isEditState = stateRouterProvider().isEditState - val feeState = state.getFeeState(isEditState) ?: return state - val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content - - val isCustomWasSelected = if (feeState.isCustomSelected) { - feeSelectorState?.customValues ?: persistentListOf() - } else { - customFeeFieldConverter.convert(fees.normal) - } - val updatedFeeSelectorState = feeSelectorState?.copy( - fees = fees, - customValues = isCustomWasSelected, - ) ?: FeeSelectorState.Content( - fees = fees, - customValues = customFeeFieldConverter.convert(fees.normal), - ) - - val fee = feeConverter.convert(updatedFeeSelectorState) - return state.copyWrapped( - isEditState = isEditState, - sendState = state.sendState?.copy( - isPrimaryButtonEnabled = true, - ), - feeState = feeState.copy( - feeSelectorState = updatedFeeSelectorState, - fee = fee, - isFeeApproximate = isFeeApproximate(state.amountState), - ), - ) - } - - fun onFeeOnErrorState(feeError: GetFeeError?): SendUiState { - val state = currentStateProvider() - val isEditState = stateRouterProvider().isEditState - return state.copyWrapped( - isEditState = isEditState, - feeState = state.getFeeState(isEditState)?.copy( - feeSelectorState = FeeSelectorState.Error(feeError), - ), - sendState = state.sendState?.copy( - isPrimaryButtonEnabled = false, - ), - ) - } - - fun onFeeSelectedState(feeType: FeeType): SendUiState { - val state = currentStateProvider() - val isEditState = stateRouterProvider().isEditState - val feeState = state.getFeeState(isEditState) ?: return state - val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return state - - val updatedFeeSelectorState = feeSelectorState.copy(selectedFee = feeType) - val fee = feeConverter.convert(updatedFeeSelectorState) - val isCustomFeeWasSelected = feeState.isCustomSelected || updatedFeeSelectorState.selectedFee == FeeType.Custom - return state.copyWrapped( - isEditState = isEditState, - feeState = feeState.copy( - fee = fee, - isCustomSelected = isCustomFeeWasSelected, - feeSelectorState = updatedFeeSelectorState, - ), - ) - } - - fun onCustomFeeValueChange(index: Int, value: String): SendUiState { - val state = currentStateProvider() - val isEditState = stateRouterProvider().isEditState - val feeState = state.getFeeState(isEditState) ?: return state - val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return state - val updatedFeeSelectorState = customFeeFieldConverter.onValueChange(feeSelectorState, index, value) - - val fee = feeConverter.convert(updatedFeeSelectorState) - return state.copyWrapped( - isEditState = isEditState, - feeState = feeState.copy( - feeSelectorState = updatedFeeSelectorState, - fee = fee, - ), - ) - } - - fun getFeeNotificationState(notifications: ImmutableList): SendUiState { - val state = currentStateProvider() - val isEditState = stateRouterProvider().isEditState - val feeState = state.getFeeState(isEditState) ?: return state - return state.copyWrapped( - isEditState = isEditState, - feeState = feeState.copy( - notifications = notifications, - isPrimaryButtonEnabled = isPrimaryButtonEnabled(feeState, notifications), - ), - ) - } - - fun tryAutoFixCustomFeeValue(): SendUiState { - val state = currentStateProvider() - val isEditState = stateRouterProvider().isEditState - val feeState = state.getFeeState(isEditState) ?: return state - val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return state - return when (feeSelectorState.selectedFee) { - FeeType.Slow, - FeeType.Market, - FeeType.Fast, - -> state - FeeType.Custom -> { - val updatedFeeSelectorState = customFeeFieldConverter.tryAutoFixValue(feeSelectorState) - - val fee = feeConverter.convert(updatedFeeSelectorState) - return state.copyWrapped( - isEditState = isEditState, - feeState = feeState.copy( - feeSelectorState = updatedFeeSelectorState, - fee = fee, - ), - ) - } - } - } - - private fun isPrimaryButtonEnabled( - feeState: SendStates.FeeState, - notifications: ImmutableList, - ): Boolean { - val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return false - val customValue = feeSelectorState.customValues.firstOrNull() - - val isNotCustom = feeSelectorState.selectedFee != FeeType.Custom - val isNotEmptyCustom = if (customValue != null) { - !customValue.value.parseToBigDecimal(customValue.decimals).isZero() && !isNotCustom - } else { - false - } - val noErrors = notifications.none { it is NotificationUM.Error } - - return noErrors && (isNotEmptyCustom || isNotCustom) - } - - private fun isFeeApproximate(state: AmountState): Boolean { - val cryptoCurrencyStatus = feeCryptoCurrencyStatusProvider() ?: return false - val amount = (state as? AmountState.Data)?.amountTextField?.cryptoAmount ?: return false - return isFeeApproximateUseCase( - networkId = cryptoCurrencyStatus.currency.network.id, - amountType = amount.type.toSdkAmountType(), - ) - } - - private fun AmountType.toSdkAmountType(): SdkAmountType { - return when (this) { - AmountType.CoinType -> SdkAmountType.Coin - is AmountType.FiatType -> error("unsupported type FiatType") - AmountType.ReserveType -> SdkAmountType.Reserve - is AmountType.TokenType -> SdkAmountType.Token( - Token( - name = this.token.name, - symbol = this.token.symbol, - contractAddress = this.token.contractAddress, - decimals = this.token.decimals, - id = this.token.id.rawCurrencyId?.value, - ), - ) - } - } -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeCustomFieldConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeCustomFieldConverter.kt deleted file mode 100644 index 71f672a60a..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeCustomFieldConverter.kt +++ /dev/null @@ -1,97 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.fee - -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.impl.presentation.state.StateRouter -import com.tangem.features.send.impl.presentation.state.fee.custom.BitcoinCustomFeeConverter -import com.tangem.features.send.impl.presentation.state.fee.custom.EthereumCustomFeeConverter -import com.tangem.features.send.impl.presentation.state.fee.custom.KaspaCustomFeeConverter -import com.tangem.features.send.impl.presentation.state.fields.SendTextField -import com.tangem.features.send.impl.presentation.model.SendClickIntents -import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf - -internal class SendFeeCustomFieldConverter( - private val clickIntents: SendClickIntents, - private val stateRouterProvider: Provider, - private val appCurrencyProvider: Provider, - private val feeCryptoCurrencyStatusProvider: Provider, -) : Converter> { - - private val ethereumCustomFeeConverter by lazy(LazyThreadSafetyMode.NONE) { - EthereumCustomFeeConverter( - clickIntents = clickIntents, - stateRouterProvider = stateRouterProvider, - appCurrencyProvider = appCurrencyProvider, - feeCryptoCurrencyStatusProvider = feeCryptoCurrencyStatusProvider, - ) - } - - private val bitcoinCustomFeeConverter by lazy(LazyThreadSafetyMode.NONE) { - BitcoinCustomFeeConverter( - clickIntents = clickIntents, - stateRouterProvider = stateRouterProvider, - appCurrencyProvider = appCurrencyProvider, - feeCryptoCurrencyStatusProvider = feeCryptoCurrencyStatusProvider, - ) - } - - private val kaspaCustomFeeConverter by lazy(LazyThreadSafetyMode.NONE) { - KaspaCustomFeeConverter( - clickIntents = clickIntents, - appCurrencyProvider = appCurrencyProvider, - feeCryptoCurrencyStatusProvider = feeCryptoCurrencyStatusProvider, - ) - } - - override fun convert(value: Fee): ImmutableList { - return when (value) { - is Fee.Ethereum -> ethereumCustomFeeConverter.convert(value) - is Fee.Bitcoin -> bitcoinCustomFeeConverter.convert(value) - is Fee.Kaspa -> kaspaCustomFeeConverter.convert(value) - else -> persistentListOf() - } - } - - fun onValueChange(feeSelectorState: FeeSelectorState.Content, index: Int, value: String) = feeSelectorState.copy( - customValues = 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: FeeSelectorState.Content) = feeSelectorState.copy( - customValues = 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 - } - }, - ) -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeStateConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeStateConverter.kt deleted file mode 100644 index 6bc7b4c193..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeStateConverter.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.fee - -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.impl.presentation.state.SendStates -import com.tangem.lib.crypto.BlockchainUtils.isTron -import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter -import kotlinx.collections.immutable.persistentListOf - -internal class SendFeeStateConverter( - private val appCurrencyProvider: Provider, - private val feeCryptoCurrencyStatusProvider: Provider, - private val cryptoCurrencyStatusProvider: Provider, -) : Converter { - - override fun convert(value: Unit): SendStates.FeeState { - return SendStates.FeeState( - feeSelectorState = FeeSelectorState.Loading, - fee = null, - notifications = persistentListOf(), - rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate, - appCurrency = appCurrencyProvider(), - isFeeApproximate = false, - isCustomSelected = false, - isFeeConvertibleToFiat = cryptoCurrencyStatusProvider().currency.network.hasFiatFeeRate, - isTronToken = cryptoCurrencyStatusProvider().currency is CryptoCurrency.Token && - isTron(cryptoCurrencyStatusProvider().currency.network.rawId), - ) - } -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BaseEthereumCustomFeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BaseEthereumCustomFeeConverter.kt deleted file mode 100644 index 3956b35bbd..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BaseEthereumCustomFeeConverter.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.fee.custom - -import com.tangem.blockchain.common.transaction.Fee -import com.tangem.features.send.impl.presentation.state.fields.SendTextField -import kotlinx.collections.immutable.ImmutableList - -/** - * Base ethereum custom fee converter - * - * @param T subtype of [Fee.Ethereum] - * -[REDACTED_AUTHOR] - */ -internal interface BaseEthereumCustomFeeConverter : CustomFeeConverter { - - fun getGasLimitIndex(feeValue: T): Int - - fun onValueChange( - feeValue: T, - customValues: ImmutableList, - index: Int, - value: String, - ): ImmutableList -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BitcoinCustomFeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BitcoinCustomFeeConverter.kt deleted file mode 100644 index 1550f48e4a..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BitcoinCustomFeeConverter.kt +++ /dev/null @@ -1,144 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.fee.custom - -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.text.input.KeyboardType -import com.tangem.blockchain.common.transaction.Fee -import com.tangem.common.ui.amountScreen.utils.getFiatReference -import com.tangem.core.ui.extensions.resourceReference -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.impl.R -import com.tangem.features.send.impl.presentation.model.SendClickIntents -import com.tangem.features.send.impl.presentation.state.StateRouter -import com.tangem.features.send.impl.presentation.state.fee.checkExceedBalance -import com.tangem.features.send.impl.presentation.state.fields.SendTextField -import com.tangem.lib.crypto.BlockchainUtils.isUseBitcoinFeeConverter -import com.tangem.utils.Provider -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList -import java.math.BigDecimal -import java.math.RoundingMode - -internal class BitcoinCustomFeeConverter( - private val clickIntents: SendClickIntents, - private val stateRouterProvider: Provider, - private val appCurrencyProvider: Provider, - private val feeCryptoCurrencyStatusProvider: Provider, -) : CustomFeeConverter { - - override fun convert(value: Fee.Bitcoin): ImmutableList { - val feeValue = value.amount.value - val feeCurrency = feeCryptoCurrencyStatusProvider()?.value - val network = feeCryptoCurrencyStatusProvider()?.currency?.network?.rawId - return if (network != null && isUseBitcoinFeeConverter(network)) { - persistentListOf( - SendTextField.CustomFee( - value = feeValue?.parseBigDecimal(value.amount.decimals).orEmpty(), - decimals = value.amount.decimals, - symbol = value.amount.currencySymbol, - onValueChange = { clickIntents.onCustomFeeValueChange(FEE_AMOUNT_INDEX, it) }, - keyboardOptions = KeyboardOptions( - imeAction = ImeAction.Next, - keyboardType = KeyboardType.Number, - ), - title = resourceReference(R.string.send_max_fee), - footer = resourceReference(R.string.send_bitcoin_custom_fee_footer), - label = getFiatReference( - rate = feeCurrency?.fiatRate, - value = feeValue, - appCurrency = appCurrencyProvider(), - ), - keyboardActions = KeyboardActions(), - isReadonly = true, - ), - SendTextField.CustomFee( - value = toSatoshiPerByte( - amount = feeValue, - decimals = value.amount.decimals, - txSize = value.txSize, - ).toString(), - decimals = SATOSHI_DECIMALS, - symbol = "", - title = resourceReference(R.string.send_satoshi_per_byte_title), - footer = resourceReference(R.string.send_satoshi_per_byte_text), - onValueChange = { clickIntents.onCustomFeeValueChange(FEE_SATOSHI_INDEX, it) }, - keyboardOptions = KeyboardOptions( - imeAction = if (checkExceedBalance( - feeBalance = feeCurrency?.amount, - feeAmount = feeValue, - ) - ) { - ImeAction.None - } else { - ImeAction.Done - }, - keyboardType = KeyboardType.Number, - ), - keyboardActions = KeyboardActions( - onDone = { clickIntents.onNextClick(stateRouterProvider().isEditState) }, - ), - ), - ) - } else { - persistentListOf() - } - } - - override fun convertBack(normalFee: Fee.Bitcoin, value: ImmutableList): Fee.Bitcoin { - val feeAmount = value[FEE_AMOUNT_INDEX].value.parseToBigDecimal(value[FEE_AMOUNT_INDEX].decimals) - val satoshiPerByte = value[FEE_SATOSHI_INDEX].value.parseToBigDecimal(value[FEE_SATOSHI_INDEX].decimals) - return normalFee.copy( - amount = normalFee.amount.copy(value = feeAmount), - satoshiPerByte = satoshiPerByte, - ) - } - - fun onValueChange( - customValues: ImmutableList, - index: Int, - value: String, - txSize: BigDecimal, - ): ImmutableList { - val mutableCustomValues = customValues.toMutableList() - return mutableCustomValues.apply { - if (index == FEE_SATOSHI_INDEX) { - val newSatoshiPerKb = value.parseToBigDecimal(this[FEE_SATOSHI_INDEX].decimals) - val newFeeAmount = newSatoshiPerKb.multiply(txSize) - .movePointLeft(this[FEE_AMOUNT_INDEX].decimals) - .setScale(this[FEE_AMOUNT_INDEX].decimals, RoundingMode.DOWN) - set( - FEE_AMOUNT_INDEX, - this[FEE_AMOUNT_INDEX].copy( - value = newFeeAmount.parseBigDecimal(this[FEE_AMOUNT_INDEX].decimals), - label = getFiatReference( - rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate, - value = newFeeAmount, - appCurrency = appCurrencyProvider(), - ), - ), - ) - set(index, this[index].copy(value = value)) - } - }.toImmutableList() - } - - private fun toSatoshiPerByte(amount: BigDecimal?, decimals: Int, txSize: BigDecimal): BigDecimal? { - val newFeeAmount = amount?.movePointRight(decimals) - return newFeeAmount?.divide( - txSize, - SATOSHI_DECIMALS, - RoundingMode.HALF_UP, - )?.setScale(SATOSHI_DECIMALS, RoundingMode.HALF_UP) - } - - private companion object { - private const val FEE_AMOUNT_INDEX = 0 - private const val FEE_SATOSHI_INDEX = 1 - private const val SATOSHI_DECIMALS = 0 - } -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/CustomFeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/CustomFeeConverter.kt deleted file mode 100644 index 25764269ec..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/CustomFeeConverter.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.fee.custom - -import com.tangem.blockchain.common.transaction.Fee -import com.tangem.features.send.impl.presentation.state.fields.SendTextField -import com.tangem.utils.converter.Converter -import kotlinx.collections.immutable.ImmutableList - -internal interface CustomFeeConverter : Converter> { - fun convertBack(normalFee: T, value: ImmutableList): T -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt deleted file mode 100644 index e8695d1e87..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt +++ /dev/null @@ -1,135 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.fee.custom - -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.text.input.KeyboardType -import com.tangem.blockchain.common.transaction.Fee -import com.tangem.common.ui.amountScreen.utils.getFiatReference -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.utils.parseBigDecimal -import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.features.send.impl.R -import com.tangem.features.send.impl.presentation.state.StateRouter -import com.tangem.features.send.impl.presentation.state.fee.checkExceedBalance -import com.tangem.features.send.impl.presentation.state.fields.SendTextField -import com.tangem.features.send.impl.presentation.model.SendClickIntents -import com.tangem.utils.Provider -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.toImmutableList - -internal class EthereumCustomFeeConverter( - private val clickIntents: SendClickIntents, - private val stateRouterProvider: Provider, - private val appCurrencyProvider: Provider, - private val feeCryptoCurrencyStatusProvider: Provider, -) : BaseEthereumCustomFeeConverter { - - private val feeCurrency: CryptoCurrencyStatus.Value? - get() = feeCryptoCurrencyStatusProvider()?.value - - private val legacyFeeConverter = EthereumLegacyCustomFeeConverter( - clickIntents = clickIntents, - appCurrencyProvider = appCurrencyProvider, - feeCryptoCurrencyStatusProvider = feeCryptoCurrencyStatusProvider, - ) - - private val eipFeeConverter = EthereumEIPCustomFeeConverter( - clickIntents = clickIntents, - appCurrencyProvider = appCurrencyProvider, - feeCryptoCurrencyStatusProvider = feeCryptoCurrencyStatusProvider, - ) - - override fun convert(value: Fee.Ethereum): ImmutableList { - return buildList { - convertFeeValue(value).let(::add) - - when (value) { - is Fee.Ethereum.EIP1559 -> eipFeeConverter.convert(value) - is Fee.Ethereum.Legacy -> legacyFeeConverter.convert(value) - } - .let(::addAll) - - convertGasLimitValue(value).let(::add) - } - .toImmutableList() - } - - override fun convertBack(normalFee: Fee.Ethereum, value: ImmutableList): Fee.Ethereum { - return when (normalFee) { - is Fee.Ethereum.EIP1559 -> eipFeeConverter.convertBack(normalFee = normalFee, value = value) - is Fee.Ethereum.Legacy -> legacyFeeConverter.convertBack(normalFee = normalFee, value = value) - } - } - - override fun getGasLimitIndex(feeValue: Fee.Ethereum): Int { - return when (feeValue) { - is Fee.Ethereum.EIP1559 -> eipFeeConverter.getGasLimitIndex(feeValue) - is Fee.Ethereum.Legacy -> legacyFeeConverter.getGasLimitIndex(feeValue) - } - } - - override fun onValueChange( - feeValue: Fee.Ethereum, - customValues: ImmutableList, - index: Int, - value: String, - ): ImmutableList { - return when (feeValue) { - is Fee.Ethereum.EIP1559 -> eipFeeConverter.onValueChange(feeValue, customValues, index, value) - is Fee.Ethereum.Legacy -> legacyFeeConverter.onValueChange(feeValue, customValues, index, value) - } - } - - private fun convertFeeValue(value: Fee.Ethereum): SendTextField.CustomFee { - val feeValue = value.amount.value - - return SendTextField.CustomFee( - value = feeValue?.parseBigDecimal(value.amount.decimals).orEmpty(), - decimals = value.amount.decimals, - symbol = value.amount.currencySymbol, - onValueChange = { clickIntents.onCustomFeeValueChange(FEE_AMOUNT, it) }, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next, keyboardType = KeyboardType.Number), - title = resourceReference(R.string.send_max_fee), - footer = resourceReference(R.string.send_custom_amount_fee_footer), - label = getFiatReference( - rate = feeCurrency?.fiatRate, - value = feeValue, - appCurrency = appCurrencyProvider(), - ), - keyboardActions = KeyboardActions(), - ) - } - - private fun convertGasLimitValue(value: Fee.Ethereum): SendTextField.CustomFee { - val isExceedBalance = checkExceedBalance(feeBalance = feeCurrency?.amount, feeAmount = value.amount.value) - - return SendTextField.CustomFee( - value = value.gasLimit.toString(), - decimals = GAS_DECIMALS, - symbol = "", - title = resourceReference(R.string.send_gas_limit), - footer = resourceReference(R.string.send_gas_limit_footer), - onValueChange = { clickIntents.onCustomFeeValueChange(getGasLimitIndex(value), it) }, - keyboardOptions = KeyboardOptions( - imeAction = if (isExceedBalance) ImeAction.None else ImeAction.Done, - keyboardType = KeyboardType.Number, - ), - keyboardActions = KeyboardActions( - onDone = { clickIntents.onNextClick(stateRouterProvider().isEditState) }, - ), - ) - } - - companion object { - const val ETHEREUM_GAS_UNIT = "GWEI" - const val GIGA_DECIMALS = 9 - const val GAS_DECIMALS = 0 - const val FEE_AMOUNT = 0 - } -} - -internal fun MutableList.setEmpty(index: Int) { - set(index, this[index].copy(value = "")) -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumEIPCustomFeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumEIPCustomFeeConverter.kt deleted file mode 100644 index f436dd3051..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumEIPCustomFeeConverter.kt +++ /dev/null @@ -1,202 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.fee.custom - -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.text.input.KeyboardType -import com.tangem.blockchain.common.transaction.Fee -import com.tangem.common.ui.amountScreen.utils.getFiatReference -import com.tangem.core.ui.extensions.resourceReference -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.impl.R -import com.tangem.features.send.impl.presentation.state.fee.checkExceedBalance -import com.tangem.features.send.impl.presentation.state.fee.custom.EthereumCustomFeeConverter.Companion.ETHEREUM_GAS_UNIT -import com.tangem.features.send.impl.presentation.state.fee.custom.EthereumCustomFeeConverter.Companion.FEE_AMOUNT -import com.tangem.features.send.impl.presentation.state.fee.custom.EthereumCustomFeeConverter.Companion.GAS_DECIMALS -import com.tangem.features.send.impl.presentation.state.fee.custom.EthereumCustomFeeConverter.Companion.GIGA_DECIMALS -import com.tangem.features.send.impl.presentation.state.fields.SendTextField -import com.tangem.features.send.impl.presentation.model.SendClickIntents -import com.tangem.utils.Provider -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList -import java.math.RoundingMode - -internal class EthereumEIPCustomFeeConverter( - private val clickIntents: SendClickIntents, - private val appCurrencyProvider: Provider, - private val feeCryptoCurrencyStatusProvider: Provider, -) : BaseEthereumCustomFeeConverter { - - override fun convert(value: Fee.Ethereum.EIP1559): ImmutableList { - return persistentListOf( - SendTextField.CustomFee( - value = value.maxFeePerGas.toBigDecimal().movePointLeft(GIGA_DECIMALS).parseBigDecimal(GIGA_DECIMALS), - decimals = GIGA_DECIMALS, - symbol = ETHEREUM_GAS_UNIT, - title = resourceReference(R.string.send_custom_evm_max_fee), - footer = resourceReference(R.string.send_custom_evm_max_fee_footer), - onValueChange = { clickIntents.onCustomFeeValueChange(MAX_FEE, it) }, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next, keyboardType = KeyboardType.Number), - keyboardActions = KeyboardActions(), - ), - SendTextField.CustomFee( - value = value.priorityFee.toBigDecimal().movePointLeft(GIGA_DECIMALS).parseBigDecimal(GIGA_DECIMALS), - decimals = GIGA_DECIMALS, - symbol = ETHEREUM_GAS_UNIT, - title = resourceReference(R.string.send_custom_evm_priority_fee), - footer = resourceReference(R.string.send_custom_evm_priority_fee_footer), - onValueChange = { clickIntents.onCustomFeeValueChange(PRIORITY_FEE, it) }, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next, keyboardType = KeyboardType.Number), - keyboardActions = KeyboardActions(), - ), - ) - } - - override fun convertBack( - normalFee: Fee.Ethereum.EIP1559, - value: ImmutableList, - ): Fee.Ethereum.EIP1559 { - val feeAmount = value[FEE_AMOUNT].value.parseToBigDecimal(value[FEE_AMOUNT].decimals) - val maxFeeDecimals = value[MAX_FEE].decimals - val maxFee = value[MAX_FEE].value.parseToBigDecimal(maxFeeDecimals) - .movePointRight(maxFeeDecimals) - .toBigInteger() - val priorityFeeDecimals = value[PRIORITY_FEE].decimals - val priorityFee = value[PRIORITY_FEE].value.parseToBigDecimal(priorityFeeDecimals) - .movePointRight(priorityFeeDecimals) - .toBigInteger() - val gasLimit = value[GAS_LIMIT].value.parseToBigDecimal(GAS_DECIMALS).toBigInteger() - - return normalFee.copy( - amount = normalFee.amount.copy(value = feeAmount), - maxFeePerGas = maxFee, - priorityFee = priorityFee, - gasLimit = gasLimit, - ) - } - - override fun getGasLimitIndex(feeValue: Fee.Ethereum.EIP1559): Int = GAS_LIMIT - - override fun onValueChange( - feeValue: Fee.Ethereum.EIP1559, - customValues: ImmutableList, - index: Int, - value: String, - ): ImmutableList { - val mutableCustomValues = customValues.toMutableList() - return mutableCustomValues.apply { - when (index) { - FEE_AMOUNT -> setOnAmountChange(value, index) - MAX_FEE -> setOnMaxFeeChange(value, index) - GAS_LIMIT -> setOnGasLimitChange(value, index) - else -> set(index, this[index].copy(value = value)) - } - }.toImmutableList() - } - - private fun MutableList.setOnAmountChange(value: String, index: Int) { - val gasLimit = this[GAS_LIMIT].value.parseToBigDecimal(this[GAS_LIMIT].decimals) - if (value.isBlank()) { - setEmpty(FEE_AMOUNT) - setEmpty(MAX_FEE) - } else { - val newFeeAmountDecimal = value.parseToBigDecimal(this[FEE_AMOUNT].decimals) - val newFeeAmount = newFeeAmountDecimal.movePointRight(GIGA_DECIMALS) // from ETH to GWEI - - val newMaxFee = newFeeAmount.divide(gasLimit, this[MAX_FEE].decimals, RoundingMode.HALF_UP) - - set( - index = MAX_FEE, - element = this[MAX_FEE].copy(value = newMaxFee.parseBigDecimal(this[MAX_FEE].decimals)), - ) - - set( - index = index, - element = this[index].copy( - value = value, - label = getFiatReference( - rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate, - value = newFeeAmountDecimal, - appCurrency = appCurrencyProvider(), - ), - ), - ) - } - } - - private fun MutableList.setOnMaxFeeChange(value: String, index: Int) { - val gasLimit = this[GAS_LIMIT].value.parseToBigDecimal(this[GAS_LIMIT].decimals) - - if (value.isBlank()) { - setEmpty(FEE_AMOUNT) - setEmpty(MAX_FEE) - } else { - val newMaxFee = value.parseToBigDecimal(this[MAX_FEE].decimals).movePointLeft(this[MAX_FEE].decimals) - val newFeeAmount = gasLimit * newMaxFee - set( - FEE_AMOUNT, - this[FEE_AMOUNT].copy( - value = newFeeAmount.parseBigDecimal(this[FEE_AMOUNT].decimals), - label = getFiatReference( - rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate, - value = newFeeAmount, - appCurrency = appCurrencyProvider(), - ), - ), - ) - set(index, this[index].copy(value = value)) - } - } - - private fun MutableList.setOnGasLimitChange(value: String, index: Int) { - if (value.isBlank()) { - setEmpty(FEE_AMOUNT) - setEmpty(GAS_LIMIT) - } else { - val newGasLimit = value.parseToBigDecimal(this[GAS_LIMIT].decimals) - - val maxFee = this[MAX_FEE].value.parseToBigDecimal(this[MAX_FEE].decimals) - .movePointLeft(this[MAX_FEE].decimals) // from GWEI to ETH - - val newFeeAmount = newGasLimit * maxFee - - set( - index = FEE_AMOUNT, - element = this[FEE_AMOUNT].copy( - value = newFeeAmount.parseBigDecimal(this[FEE_AMOUNT].decimals), - label = getFiatReference( - rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate, - value = newFeeAmount, - appCurrency = appCurrencyProvider(), - ), - ), - ) - - val isNotExceedBalance = checkExceedBalance( - feeBalance = feeCryptoCurrencyStatusProvider()?.value?.amount, - feeAmount = newFeeAmount, - ) - - set( - index = index, - element = this[index].copy( - value = value, - keyboardOptions = KeyboardOptions( - imeAction = if (!isNotExceedBalance) ImeAction.None else ImeAction.Done, - keyboardType = KeyboardType.Number, - ), - ), - ) - } - } - - private companion object { - const val MAX_FEE = 1 - const val PRIORITY_FEE = 2 - const val GAS_LIMIT = 3 - } -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumLegacyCustomFeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumLegacyCustomFeeConverter.kt deleted file mode 100644 index cf8e3881b7..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumLegacyCustomFeeConverter.kt +++ /dev/null @@ -1,179 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.fee.custom - -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.text.input.KeyboardType -import com.tangem.blockchain.common.transaction.Fee -import com.tangem.common.ui.amountScreen.utils.getFiatReference -import com.tangem.core.ui.extensions.resourceReference -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.impl.R -import com.tangem.features.send.impl.presentation.state.fee.checkExceedBalance -import com.tangem.features.send.impl.presentation.state.fee.custom.EthereumCustomFeeConverter.Companion.ETHEREUM_GAS_UNIT -import com.tangem.features.send.impl.presentation.state.fee.custom.EthereumCustomFeeConverter.Companion.FEE_AMOUNT -import com.tangem.features.send.impl.presentation.state.fee.custom.EthereumCustomFeeConverter.Companion.GAS_DECIMALS -import com.tangem.features.send.impl.presentation.state.fee.custom.EthereumCustomFeeConverter.Companion.GIGA_DECIMALS -import com.tangem.features.send.impl.presentation.state.fields.SendTextField -import com.tangem.features.send.impl.presentation.model.SendClickIntents -import com.tangem.utils.Provider -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList -import java.math.RoundingMode - -internal class EthereumLegacyCustomFeeConverter( - private val clickIntents: SendClickIntents, - private val appCurrencyProvider: Provider, - private val feeCryptoCurrencyStatusProvider: Provider, -) : BaseEthereumCustomFeeConverter { - - override fun convert(value: Fee.Ethereum.Legacy): ImmutableList { - return persistentListOf( - SendTextField.CustomFee( - value = value.gasPrice.toBigDecimal().movePointLeft(GIGA_DECIMALS).parseBigDecimal(GIGA_DECIMALS), - decimals = GIGA_DECIMALS, - symbol = ETHEREUM_GAS_UNIT, - title = resourceReference(R.string.send_gas_price), - footer = resourceReference(R.string.send_gas_price_footer), - onValueChange = { clickIntents.onCustomFeeValueChange(GAS_PRICE, it) }, - keyboardOptions = KeyboardOptions( - imeAction = ImeAction.Next, - keyboardType = KeyboardType.Number, - ), - keyboardActions = KeyboardActions(), - ), - ) - } - - override fun convertBack( - normalFee: Fee.Ethereum.Legacy, - value: ImmutableList, - ): Fee.Ethereum.Legacy { - val feeAmount = value[FEE_AMOUNT].value.parseToBigDecimal(value[FEE_AMOUNT].decimals) - val gasPrice = value[GAS_PRICE].value.parseToBigDecimal(GAS_DECIMALS).toBigInteger() - val gasLimit = value[GAS_LIMIT].value.parseToBigDecimal(GAS_DECIMALS).toBigInteger() - - return normalFee.copy( - amount = normalFee.amount.copy(value = feeAmount), - gasPrice = gasPrice, - gasLimit = gasLimit, - ) - } - - override fun getGasLimitIndex(feeValue: Fee.Ethereum.Legacy): Int = GAS_LIMIT - - override fun onValueChange( - feeValue: Fee.Ethereum.Legacy, - customValues: ImmutableList, - index: Int, - value: String, - ): ImmutableList { - val mutableCustomValues = customValues.toMutableList() - return mutableCustomValues.apply { - when (index) { - FEE_AMOUNT -> setOnAmountChange(value, index) - GAS_PRICE -> setOnGasPriceChange(value, index) - GAS_LIMIT -> setOnGasLimitChange(value, index) - else -> set(index, this[index].copy(value = value)) - } - }.toImmutableList() - } - - private fun MutableList.setOnAmountChange(value: String, index: Int) { - val gasLimit = this[GAS_LIMIT].value.parseToBigDecimal(this[GAS_LIMIT].decimals) - if (value.isBlank()) { - setEmpty(FEE_AMOUNT) - setEmpty(GAS_PRICE) - } else { - val newFeeAmountDecimal = value.parseToBigDecimal(this[FEE_AMOUNT].decimals) - val newFeeAmount = newFeeAmountDecimal.movePointRight(this[GAS_PRICE].decimals) // from ETH to GWEI - val newGasPrice = newFeeAmount.divide(gasLimit, this[GAS_PRICE].decimals, RoundingMode.HALF_UP) - set(GAS_PRICE, this[GAS_PRICE].copy(value = newGasPrice.parseBigDecimal(this[GAS_PRICE].decimals))) - set( - index, - this[index].copy( - value = value, - label = getFiatReference( - rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate, - value = newFeeAmountDecimal, - appCurrency = appCurrencyProvider(), - ), - ), - ) - } - } - - private fun MutableList.setOnGasPriceChange(value: String, index: Int) { - val gasLimit = this[GAS_LIMIT].value.parseToBigDecimal(this[GAS_LIMIT].decimals) - if (value.isBlank()) { - setEmpty(FEE_AMOUNT) - setEmpty(GAS_PRICE) - } else { - val newGasPrice = value.parseToBigDecimal(this[GAS_PRICE].decimals) - .movePointLeft(this[GAS_PRICE].decimals) // from GWEI to ETH - val newFeeAmount = gasLimit * newGasPrice - set( - FEE_AMOUNT, - this[FEE_AMOUNT].copy( - value = newFeeAmount.parseBigDecimal(this[FEE_AMOUNT].decimals), - label = getFiatReference( - rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate, - value = newFeeAmount, - appCurrency = appCurrencyProvider(), - ), - ), - ) - set(index, this[index].copy(value = value)) - } - } - - private fun MutableList.setOnGasLimitChange(value: String, index: Int) { - if (value.isBlank()) { - setEmpty(FEE_AMOUNT) - setEmpty(GAS_LIMIT) - } else { - val newGasLimit = value.parseToBigDecimal(this[GAS_LIMIT].decimals) - val gasPrice = this[GAS_PRICE].value.parseToBigDecimal(this[GAS_PRICE].decimals) - .movePointLeft(this[GAS_PRICE].decimals) // from GWEI to ETH - - val newFeeAmount = newGasLimit * gasPrice - - set( - index = FEE_AMOUNT, - element = this[FEE_AMOUNT].copy( - value = newFeeAmount.parseBigDecimal(this[FEE_AMOUNT].decimals), - label = getFiatReference( - rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate, - value = newFeeAmount, - appCurrency = appCurrencyProvider(), - ), - ), - ) - - val isNotExceedBalance = checkExceedBalance( - feeBalance = feeCryptoCurrencyStatusProvider()?.value?.amount, - feeAmount = newFeeAmount, - ) - - set( - index = index, - element = this[index].copy( - value = value, - keyboardOptions = KeyboardOptions( - imeAction = if (!isNotExceedBalance) ImeAction.None else ImeAction.Done, - keyboardType = KeyboardType.Number, - ), - ), - ) - } - } - - private companion object { - const val GAS_PRICE = 1 - const val GAS_LIMIT = 2 - } -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/KaspaCustomFeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/KaspaCustomFeeConverter.kt deleted file mode 100644 index 89cf36d500..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/KaspaCustomFeeConverter.kt +++ /dev/null @@ -1,135 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.fee.custom - -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.text.input.KeyboardType -import com.tangem.blockchain.common.transaction.Fee -import com.tangem.common.ui.amountScreen.utils.getFiatReference -import com.tangem.core.ui.extensions.resourceReference -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.impl.R -import com.tangem.features.send.impl.presentation.model.SendClickIntents -import com.tangem.features.send.impl.presentation.state.fields.SendTextField -import com.tangem.utils.Provider -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList -import java.math.RoundingMode - -internal class KaspaCustomFeeConverter( - private val clickIntents: SendClickIntents, - private val appCurrencyProvider: Provider, - private val feeCryptoCurrencyStatusProvider: Provider, -) : CustomFeeConverter { - - override fun convert(value: Fee.Kaspa): ImmutableList { - val feeValue = value.amount.value - val feeCurrency = feeCryptoCurrencyStatusProvider()?.value - val network = feeCryptoCurrencyStatusProvider()?.currency?.network?.rawId - return if (network != null) { - persistentListOf( - SendTextField.CustomFee( - value = feeValue?.parseBigDecimal(value.amount.decimals).orEmpty(), - decimals = value.amount.decimals, - symbol = value.amount.currencySymbol, - onValueChange = { clickIntents.onCustomFeeValueChange(FEE_AMOUNT_INDEX, it) }, - keyboardOptions = KeyboardOptions( - imeAction = ImeAction.Next, - keyboardType = KeyboardType.Number, - ), - title = resourceReference(R.string.send_max_fee), - footer = resourceReference(R.string.send_custom_amount_fee_footer), - label = getFiatReference( - rate = feeCurrency?.fiatRate, - value = feeValue, - appCurrency = appCurrencyProvider(), - ), - keyboardActions = KeyboardActions(), - ), - ) - } else { - persistentListOf() - } - } - - override fun convertBack(normalFee: Fee.Kaspa, value: ImmutableList): Fee.Kaspa { - val decimals = value[FEE_AMOUNT_INDEX].decimals - val feeAmount = value[FEE_AMOUNT_INDEX].value.parseToBigDecimal(decimals) - return normalFee.copy( - amount = normalFee.amount.copy(value = feeAmount), - mass = normalFee.mass, - feeRate = feeAmount - .divide(normalFee.mass.toBigDecimal(), decimals, RoundingMode.HALF_UP) - .movePointRight(decimals) - .toBigInteger(), - ) - } - - fun onValueChange( - customValues: ImmutableList, - index: Int, - value: String, - ): ImmutableList { - val mutableCustomValues = customValues.toMutableList() - return mutableCustomValues.apply { - when (index) { - FEE_AMOUNT_INDEX -> { - val valueDecimal = value.parseToBigDecimal(this[FEE_AMOUNT_INDEX].decimals) - set( - index, - this[index].copy( - value = value, - label = getFiatReference( - rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate, - value = valueDecimal, - appCurrency = appCurrencyProvider(), - ), - ), - ) - } - } - }.toImmutableList() - } - - fun tryAutoFixValue( - minimumFee: Fee.Kaspa, - customValues: ImmutableList, - ): ImmutableList { - val mutableCustomValues = customValues.toMutableList() - val minimumFeeAmountValue = minimumFee.amount.value - - return mutableCustomValues.apply { - // check that there is reveal transaction info (= krc-20 token transfer) - // return without changes otherwise - if (minimumFee.revealTransactionFee != null && minimumFeeAmountValue != null) { - getOrNull(FEE_AMOUNT_INDEX)?.let { - val valueDecimal = it.value.parseToBigDecimal(it.decimals) - // krc-20 transaction will be failed if custom fee value is less than minimum, - // so we set value to minimum in this case - if (valueDecimal < minimumFee.amount.value) { - val fixedValue = minimumFeeAmountValue.parseBigDecimal(it.decimals) - set( - FEE_AMOUNT_INDEX, - it.copy( - value = fixedValue, - label = getFiatReference( - rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate, - value = valueDecimal, - appCurrency = appCurrencyProvider(), - ), - ), - ) - } - } - } - }.toImmutableList() - } - - private companion object { - private const val FEE_AMOUNT_INDEX = 0 - } -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt deleted file mode 100644 index ea7e502aec..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt +++ /dev/null @@ -1,41 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.fields - -import com.tangem.common.ui.amountScreen.converters.MaxEnterAmountConverter -import com.tangem.common.ui.amountScreen.converters.field.AmountFieldChangeTransformer -import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary -import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.features.send.impl.presentation.state.SendUiState -import com.tangem.features.send.impl.presentation.state.StateRouter -import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter - -internal class SendAmountFieldChangeConverter( - private val stateRouterProvider: Provider, - private val currentStateProvider: Provider, - private val cryptoCurrencyStatusProvider: Provider, - private val minimumTransactionAmountProvider: Provider, -) : Converter { - - private val maxEnterAmountConverter = MaxEnterAmountConverter() - - override fun convert(value: String): SendUiState { - val state = currentStateProvider() - val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() - val isEditState = stateRouterProvider().isEditState - val amountState = state.getAmountState(isEditState) - - val maxEnterAmount = maxEnterAmountConverter.convert(cryptoCurrencyStatus) - val minimumTransactionAmount = minimumTransactionAmountProvider() - - return state.copyWrapped( - isEditState = isEditState, - sendState = state.sendState?.copy(reduceAmountBy = null), - amountState = AmountFieldChangeTransformer( - cryptoCurrencyStatus = cryptoCurrencyStatus, - maxEnterAmount = maxEnterAmount, - minimumTransactionAmount = minimumTransactionAmount, - value = value, - ).transform(amountState), - ) - } -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt deleted file mode 100644 index 882365d726..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.fields - -import com.tangem.common.ui.amountScreen.converters.MaxEnterAmountConverter -import com.tangem.common.ui.amountScreen.converters.field.AmountFieldSetMaxAmountTransformer -import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary -import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.features.send.impl.presentation.state.SendUiState -import com.tangem.features.send.impl.presentation.state.StateRouter -import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter -import com.tangem.utils.isNullOrZero - -internal class SendAmountFieldMaxAmountConverter( - private val stateRouterProvider: Provider, - private val currentStateProvider: Provider, - private val cryptoCurrencyStatusProvider: Provider, - private val minimumTransactionAmountProvider: Provider, -) : Converter { - - private val maxEnterAmountConverter = MaxEnterAmountConverter() - - override fun convert(value: Unit): SendUiState { - val state = currentStateProvider() - val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() - val isEditState = stateRouterProvider().isEditState - val amountState = state.getAmountState(isEditState) ?: return state - - val decimalCryptoValue = cryptoCurrencyStatus.value.amount - if (decimalCryptoValue.isNullOrZero()) return state - - val maxEnterAmount = maxEnterAmountConverter.convert(cryptoCurrencyStatus) - val minimumTransactionAmount = minimumTransactionAmountProvider() - - return state.copyWrapped( - isEditState = isEditState, - sendState = state.sendState?.copy(reduceAmountBy = null), - amountState = AmountFieldSetMaxAmountTransformer( - cryptoCurrencyStatus = cryptoCurrencyStatus, - maxAmount = maxEnterAmount, - minAmount = minimumTransactionAmount, - ).transform(amountState), - ) - } -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendTextField.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendTextField.kt deleted file mode 100644 index e289c7968a..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendTextField.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.fields - -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.runtime.Immutable -import com.tangem.core.ui.extensions.TextReference - -@Immutable -internal sealed class SendTextField { - - /** Current value */ - abstract val value: String - - /** Lambda be invoked when value is been changed */ - abstract val onValueChange: (String) -> Unit - - /** Keyboard options */ - abstract val keyboardOptions: KeyboardOptions - - data class RecipientAddress( - override val value: String, - override val onValueChange: (String) -> Unit, - override val keyboardOptions: KeyboardOptions, - val placeholder: TextReference, - val label: TextReference, - val isError: Boolean = false, - val error: TextReference? = null, - val isValuePasted: Boolean, - ) : SendTextField() - - data class RecipientMemo( - override val value: String, - override val onValueChange: (String) -> Unit, - override val keyboardOptions: KeyboardOptions, - val placeholder: TextReference, - val label: TextReference, - val isError: Boolean = false, - val error: TextReference? = null, - val disabledText: TextReference, - val isEnabled: Boolean, - val isValuePasted: Boolean, - ) : SendTextField() - - data class CustomFee( - override val value: String, - override val onValueChange: (String) -> Unit, - override val keyboardOptions: KeyboardOptions, - val keyboardActions: KeyboardActions, - val symbol: String?, - val decimals: Int, - val title: TextReference, - val footer: TextReference, - val label: TextReference? = null, - val isReadonly: Boolean = false, - ) : SendTextField() -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/ConfirmStatePreviewData.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/ConfirmStatePreviewData.kt deleted file mode 100644 index cc811abb61..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/ConfirmStatePreviewData.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.previewdata - -import com.tangem.features.send.impl.presentation.state.SendStates -import com.tangem.features.send.impl.presentation.state.SendUiStateType -import kotlinx.collections.immutable.persistentListOf - -internal object ConfirmStatePreviewData { - - val sendState = SendStates.SendState( - type = SendUiStateType.Send, - isSending = false, - isSuccess = false, - transactionDate = 0L, - txUrl = "", - ignoreAmountReduce = false, - reduceAmountBy = null, - isFromConfirmation = false, - showTapHelp = true, - notifications = persistentListOf(), - ) - - val sendDoneState = SendStates.SendState( - type = SendUiStateType.Send, - isSending = false, - isSuccess = true, - transactionDate = 1695199500000L, - txUrl = "url", - ignoreAmountReduce = false, - reduceAmountBy = null, - isFromConfirmation = false, - showTapHelp = false, - notifications = persistentListOf(), - ) -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/FeeStatePreviewData.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/FeeStatePreviewData.kt deleted file mode 100644 index 02cb054cd8..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/FeeStatePreviewData.kt +++ /dev/null @@ -1,108 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.previewdata - -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions -import com.tangem.blockchain.common.Amount -import com.tangem.blockchain.common.AmountType.Coin -import com.tangem.blockchain.common.transaction.Fee -import com.tangem.blockchain.common.transaction.TransactionFee -import com.tangem.core.ui.extensions.stringReference -import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.features.send.impl.presentation.state.SendStates -import com.tangem.features.send.impl.presentation.state.SendUiStateType -import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState -import com.tangem.features.send.impl.presentation.state.fee.FeeType -import com.tangem.features.send.impl.presentation.state.fields.SendTextField -import kotlinx.collections.immutable.persistentListOf -import java.math.BigDecimal - -internal object FeeStatePreviewData { - - private val fee = Fee.Common( - amount = Amount( - currencySymbol = "MATIC", - value = BigDecimal(0.159806), - decimals = 18, - type = Coin, - ), - ) - - private val singleFee = TransactionFee.Single(normal = fee) - - private val multipleFees = TransactionFee.Choosable( - normal = fee, - minimum = fee, - priority = fee.copy(fee.amount.copy(value = BigDecimal(0.159824))), - ) - - private val customValue = SendTextField.CustomFee( - value = "0.159834", - onValueChange = {}, - keyboardOptions = KeyboardOptions.Default, - keyboardActions = KeyboardActions.Default, - symbol = "MATIC", - decimals = 18, - title = stringReference("Fee up to"), - footer = stringReference("Maximum commission amount"), - label = stringReference("0.41 \$"), - isReadonly = false, - ) - - private val customValues = persistentListOf( - customValue, - customValue.copy( - value = "400", - symbol = "GWEI", - title = stringReference("Gas price"), - footer = stringReference("Gas Price impacts transaction speed; too low, it may not process"), - label = null, - ), - customValue.copy( - value = "31400", - symbol = "", - title = stringReference("Gas limit"), - footer = stringReference("Gas Limit is auto-calculated; raise it during network congestion"), - label = null, - ), - ) - - private val feeSelector = FeeSelectorState.Content( - fees = singleFee, - selectedFee = FeeType.Market, - customValues = persistentListOf(), - ) - - val feeState = SendStates.FeeState( - type = SendUiStateType.Fee, - isPrimaryButtonEnabled = false, - feeSelectorState = feeSelector, - fee = fee, - rate = BigDecimal.ONE, - appCurrency = AppCurrency.Default, - isFeeApproximate = false, - notifications = persistentListOf(), - isCustomSelected = false, - isFeeConvertibleToFiat = true, - isTronToken = false, - ) - - val feeChoosableState = feeState.copy( - feeSelectorState = feeSelector.copy( - fees = multipleFees, - customValues = customValues, - ), - ) - - val feeCustomState = feeState.copy( - feeSelectorState = feeSelector.copy( - fees = multipleFees, - customValues = customValues, - selectedFee = FeeType.Custom, - ), - isCustomSelected = true, - ) - - val errorFeeState = feeState.copy( - feeSelectorState = FeeSelectorState.Error(null), - ) -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/RecipientStatePreviewData.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/RecipientStatePreviewData.kt deleted file mode 100644 index f3cc308ca1..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/RecipientStatePreviewData.kt +++ /dev/null @@ -1,85 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.previewdata - -import androidx.compose.foundation.text.KeyboardOptions -import com.tangem.core.ui.extensions.stringReference -import com.tangem.features.send.impl.R -import com.tangem.features.send.impl.presentation.domain.SendRecipientListContent -import com.tangem.features.send.impl.presentation.state.SendStates -import com.tangem.features.send.impl.presentation.state.fields.SendTextField -import kotlinx.collections.immutable.persistentListOf - -internal object RecipientStatePreviewData { - - private val defaultRecentItem = SendRecipientListContent( - id = "sanctus", - title = stringReference("0x391316a070212312312378E88CAc8A0C250"), - subtitleEndOffset = 0, - subtitleIconRes = R.drawable.ic_arrow_down_24, - isVisible = true, - isLoading = false, - ) - - val recipientState = SendStates.RecipientState( - addressTextField = SendTextField.RecipientAddress( - value = "", - onValueChange = {}, - keyboardOptions = KeyboardOptions.Default, - placeholder = stringReference("Enter address"), - label = stringReference("Recipient"), - isError = false, - error = null, - isValuePasted = false, - ), - memoTextField = SendTextField.RecipientMemo( - value = "", - onValueChange = {}, - keyboardOptions = KeyboardOptions.Default, - placeholder = stringReference("Optional"), - label = stringReference("Memo"), - isError = false, - error = null, - disabledText = stringReference("Already included in the entered address"), - isEnabled = true, - isValuePasted = false, - ), - recent = persistentListOf(), - wallets = persistentListOf(), - network = "Ethereum", - isValidating = false, - isPrimaryButtonEnabled = true, - ) - - val recipientAddressState = recipientState.copy( - addressTextField = recipientState.addressTextField.copy( - value = "0x391316d97a07027a0702c8A002c8A0C25d8470", - ), - ) - - val recipientWithRecentState = recipientState.copy( - recent = persistentListOf( - defaultRecentItem.copy( - id = "1", - subtitle = stringReference("1 000 000 000.0004 USDT"), - timestamp = stringReference("today at 14:46"), - subtitleIconRes = R.drawable.ic_arrow_up_24, - ), - defaultRecentItem.copy( - id = "2", - subtitle = stringReference("20,09 USDT"), - timestamp = stringReference("24.05.2004 at 14:46"), - ), - defaultRecentItem.copy( - id = "3", - subtitle = stringReference("20,09 USDT"), - timestamp = stringReference("24.05.2004 at 14:46"), - ), - ), - wallets = persistentListOf( - defaultRecentItem.copy( - id = "4", - subtitle = stringReference("Main Wallet"), - subtitleIconRes = null, - ), - ), - ) -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt deleted file mode 100644 index 717f759814..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt +++ /dev/null @@ -1,74 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.previewdata - -import com.tangem.common.ui.notifications.NotificationUM -import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.features.send.impl.presentation.analytics.EnterAddressSource -import com.tangem.features.send.impl.presentation.model.SendClickIntents -import com.tangem.features.send.impl.presentation.state.fee.FeeType -import java.math.BigDecimal - -@Suppress("TooManyFunctions") -internal object SendClickIntentsStub : SendClickIntents { - override fun popBackStack() {} - - override fun onBackClick() {} - - override fun onCloseClick() {} - - override fun onNextClick(isFromEdit: Boolean) {} - - override fun onPrevClick() {} - - override fun onQrCodeScanClick() {} - - override fun onFailedTxEmailClick(errorMessage: String) {} - - override fun onTokenDetailsClick(currency: CryptoCurrency) {} - - override fun onAmountValueChange(value: String) {} - - override fun onCurrencyChangeClick(isFiat: Boolean) {} - - override fun onAmountNext() {} - - override fun onMaxValueClick() {} - - override fun onAmountPasteTriggerDismiss() {} - - override fun onRecipientAddressValueChange(value: String, type: EnterAddressSource?) {} - - override fun onRecipientMemoValueChange(value: String, isValuePasted: Boolean) {} - - override fun feeReload() {} - - override fun onFeeSelectorClick(feeType: FeeType) {} - - override fun onCustomFeeValueChange(index: Int, value: String) {} - - override fun onReadMoreClick() {} - - override fun onSendClick() {} - - override fun showAmount() {} - - override fun showRecipient() {} - - override fun showFee() {} - - override fun showSend() {} - - override fun onExploreClick() {} - - override fun onShareClick(txUrl: String) {} - - override fun onAmountReduceByClick( - reduceAmountBy: BigDecimal, - reduceAmountByDiff: BigDecimal, - notification: Class, - ) { - } - - override fun onAmountReduceToClick(reduceAmountTo: BigDecimal, notification: Class) {} - - override fun onNotificationCancel(clazz: Class) {} -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendStatesPreviewData.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendStatesPreviewData.kt deleted file mode 100644 index dd64a24aed..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendStatesPreviewData.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.previewdata - -import com.tangem.common.ui.amountScreen.preview.AmountStatePreviewData -import com.tangem.core.ui.event.consumedEvent -import com.tangem.features.send.impl.presentation.state.SendUiState - -internal object SendStatesPreviewData { - - val uiState = SendUiState( - clickIntents = SendClickIntentsStub, - isEditingDisabled = false, - cryptoCurrencyName = "", - amountState = AmountStatePreviewData.amountWithValueState, - recipientState = RecipientStatePreviewData.recipientAddressState, - feeState = FeeStatePreviewData.feeChoosableState, - sendState = ConfirmStatePreviewData.sendState, - editAmountState = AmountStatePreviewData.amountWithValueState, - editRecipientState = RecipientStatePreviewData.recipientAddressState, - editFeeState = FeeStatePreviewData.feeChoosableState, - isBalanceHidden = false, - isSubtracted = false, - event = consumedEvent(), - ) -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/RecipientSendFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/RecipientSendFactory.kt deleted file mode 100644 index c53f950398..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/RecipientSendFactory.kt +++ /dev/null @@ -1,184 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.recipient - -import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.domain.models.network.TxInfo -import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.domain.transaction.error.AddressValidation -import com.tangem.domain.transaction.error.AddressValidationResult -import com.tangem.domain.transaction.usecase.ValidateWalletMemoUseCase -import com.tangem.features.send.impl.R -import com.tangem.features.send.impl.presentation.domain.AvailableWallet -import com.tangem.features.send.impl.presentation.state.SendUiState -import com.tangem.features.send.impl.presentation.state.StateRouter -import com.tangem.utils.Provider -import kotlinx.collections.immutable.toPersistentList - -internal class RecipientSendFactory( - private val stateRouterProvider: Provider, - private val currentStateProvider: Provider, - private val cryptoCurrencyStatusProvider: Provider, - private val isUtxoConsolidationAvailableProvider: Provider, - private val validateWalletMemoUseCase: ValidateWalletMemoUseCase, -) { - private val recipientWalletListStateConverter by lazy(LazyThreadSafetyMode.NONE) { - SendRecipientWalletListConverter( - cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, - isUtxoConsolidationAvailableProvider = isUtxoConsolidationAvailableProvider, - ) - } - private val recipientHistoryListStateConverter by lazy(LazyThreadSafetyMode.NONE) { - SendRecipientHistoryListConverter( - cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, - ) - } - - fun onLoadedWalletsList(wallets: List): SendUiState { - val state = currentStateProvider() - return state.copy( - recipientState = state.recipientState?.copy( - wallets = recipientWalletListStateConverter.convert(wallets), - ), - ) - } - - fun onLoadedHistoryList(txHistory: List): SendUiState { - val state = currentStateProvider() - return state.copy( - recipientState = state.recipientState?.copy( - recent = recipientHistoryListStateConverter.convert(txHistory), - ), - ) - } - - fun onRecipientAddressValueChange(value: String, isXAddress: Boolean = false, isValuePasted: Boolean): SendUiState { - val state = currentStateProvider() - val isEditState = stateRouterProvider().isEditState - val recipientState = state.getRecipientState(isEditState) ?: return state - return state.copyWrapped( - isEditState = isEditState, - recipientState = recipientState.copy( - addressTextField = recipientState.addressTextField.copy(value = value, isValuePasted = isValuePasted), - memoTextField = recipientState.memoTextField?.copy(isEnabled = !isXAddress), - ), - ) - } - - fun getOnRecipientAddressValidState(value: String, maybeValidAddress: AddressValidationResult): SendUiState { - val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() - val state = currentStateProvider() - val isEditState = stateRouterProvider().isEditState - val recipientState = state.getRecipientState(isEditState) ?: return state - - val isValidMemo = validateWalletMemoUseCase( - memo = recipientState.memoTextField?.value.orEmpty(), - network = cryptoCurrencyStatus.currency.network, - ).isRight() - - return state.copyWrapped( - isEditState = isEditState, - recipientState = recipientState.copy( - isPrimaryButtonEnabled = isValidMemo && maybeValidAddress.isRight(), - isValidating = false, - addressTextField = recipientState.addressTextField.copy( - error = maybeValidAddress.fold( - ifLeft = { - when (it) { - AddressValidation.Error.InvalidAddress -> resourceReference( - R.string.send_recipient_address_error, - ) - AddressValidation.Error.AddressInWallet -> resourceReference( - R.string.send_error_address_same_as_wallet, - ) - else -> null - } - }, - ifRight = { null }, - ), - isError = value.isNotEmpty() && maybeValidAddress.isLeft(), - ), - ), - ) - } - - fun getOnRecipientAddressValidationStarted(): SendUiState { - val state = currentStateProvider() - val isEditState = stateRouterProvider().isEditState - val recipientState = state.getRecipientState(isEditState) ?: return state - return state.copyWrapped( - isEditState = isEditState, - recipientState = recipientState.copy(isValidating = true), - ) - } - - fun getOnRecipientMemoValueChange(value: String, isValuePasted: Boolean): SendUiState { - val state = currentStateProvider() - val isEditState = stateRouterProvider().isEditState - val recipientState = state.getRecipientState(isEditState) ?: return state - return state.copyWrapped( - isEditState = isEditState, - recipientState = recipientState.copy( - memoTextField = recipientState.memoTextField?.copy( - value = value, - isValuePasted = isValuePasted, - ), - ), - ) - } - - fun getOnRecipientMemoValidState(value: String, isValidAddress: Boolean): SendUiState { - val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() - val state = currentStateProvider() - val isEditState = stateRouterProvider().isEditState - val recipientState = state.getRecipientState(isEditState) ?: return state - - val isValidMemo = validateWalletMemoUseCase( - memo = value, - network = cryptoCurrencyStatus.currency.network, - ).isRight() - - return state.copyWrapped( - isEditState = isEditState, - recipientState = recipientState.copy( - isPrimaryButtonEnabled = isValidMemo && isValidAddress, - isValidating = false, - memoTextField = recipientState.memoTextField?.copy( - isError = value.isNotEmpty() && !isValidMemo, - isEnabled = true, - ), - ), - ) - } - - fun getOnXAddressMemoState(): SendUiState { - val state = currentStateProvider() - val isEditState = stateRouterProvider().isEditState - val recipientState = state.getRecipientState(isEditState) ?: return state - return state.copyWrapped( - isEditState = isEditState, - recipientState = recipientState.copy( - memoTextField = recipientState.memoTextField?.copy( - value = "", - isEnabled = false, - ), - ), - ) - } - - fun getHiddenRecentListState(isNotValid: Boolean): SendUiState { - val state = currentStateProvider() - val isEditState = stateRouterProvider().isEditState - val recipientState = state.getRecipientState(isEditState) ?: return state - return state.copyWrapped( - isEditState = isEditState, - recipientState = recipientState.copy( - recent = recipientState.recent.map { recent -> - recent.copy(isVisible = isNotValid && (recent.isLoading || recent.title != TextReference.EMPTY)) - }.toPersistentList(), - wallets = recipientState.wallets.map { wallet -> - wallet.copy(isVisible = isNotValid && (wallet.isLoading || wallet.title != TextReference.EMPTY)) - }.toPersistentList(), - ), - ) - } -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientAddressFieldConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientAddressFieldConverter.kt deleted file mode 100644 index a14a303ab2..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientAddressFieldConverter.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.recipient - -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.text.input.KeyboardType -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.features.send.impl.R -import com.tangem.features.send.impl.presentation.state.fields.SendTextField -import com.tangem.features.send.impl.presentation.model.SendClickIntents -import com.tangem.utils.converter.Converter - -internal class SendRecipientAddressFieldConverter( - private val clickIntents: SendClickIntents, -) : Converter { - - override fun convert(value: String): SendTextField.RecipientAddress { - return SendTextField.RecipientAddress( - value = value, - onValueChange = clickIntents::onRecipientAddressValueChange, - keyboardOptions = KeyboardOptions( - imeAction = ImeAction.Next, - keyboardType = KeyboardType.Text, - ), - error = resourceReference(R.string.send_recipient_address_error), - placeholder = resourceReference(R.string.send_enter_address_field), - label = resourceReference(R.string.send_recipient), - isValuePasted = false, - ) - } -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientHistoryListConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientHistoryListConverter.kt deleted file mode 100644 index 8a1c9ed5f2..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientHistoryListConverter.kt +++ /dev/null @@ -1,94 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.recipient - -import com.tangem.common.extensions.isZero -import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.stringReference -import com.tangem.core.ui.extensions.wrappedList -import com.tangem.core.ui.format.bigdecimal.crypto -import com.tangem.core.ui.format.bigdecimal.format -import com.tangem.core.ui.utils.DateTimeFormatters -import com.tangem.core.ui.utils.toDateFormatWithTodayYesterday -import com.tangem.core.ui.utils.toTimeFormat -import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.domain.models.network.TxInfo -import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.features.send.impl.R -import com.tangem.features.send.impl.presentation.domain.SendRecipientListContent -import com.tangem.features.send.impl.presentation.state.recipient.utils.RECENT_DEFAULT_COUNT -import com.tangem.features.send.impl.presentation.state.recipient.utils.RECENT_KEY_TAG -import com.tangem.features.send.impl.presentation.state.recipient.utils.emptyListState -import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.toPersistentList - -internal class SendRecipientHistoryListConverter( - private val cryptoCurrencyStatusProvider: Provider, -) : Converter, ImmutableList> { - - override fun convert(value: List): ImmutableList { - val cryptoCurrency = cryptoCurrencyStatusProvider().currency - return value.filterRecipients(cryptoCurrency).ifEmpty { - emptyListState(RECENT_KEY_TAG, RECENT_DEFAULT_COUNT) - } - } - - private fun List.filterRecipients(cryptoCurrency: CryptoCurrency) = this.filter { item -> - val isTransfer = item.type == TxInfo.TransactionType.Transfer - val isNotContract = item.interactionAddressType is TxInfo.InteractionAddressType.User - val isSingleAddress = if (item.isOutgoing) { - item.destinationType is TxInfo.DestinationType.Single - } else { - item.sourceType is TxInfo.SourceType.Single - } - val notZero = !item.amount.isZero() - isTransfer && isSingleAddress && isNotContract && item.isOutgoing && notZero - } - .take(RECENT_LIST_SIZE) - .mapIndexed { index, tx -> - SendRecipientListContent( - id = "$RECENT_KEY_TAG$index", - title = tx.extractAddress(), - subtitle = stringReference(tx.getAmount(cryptoCurrency).trim()), - timestamp = tx.extractTimestamp(), - subtitleEndOffset = cryptoCurrency.symbol.length, - subtitleIconRes = tx.extractIconRes(), - ) - }.toPersistentList() - - private fun TxInfo.extractAddress(): TextReference = if (isOutgoing) { - when (val destination = destinationType) { - is TxInfo.DestinationType.Multiple -> TextReference.Res( - R.string.transaction_history_multiple_addresses, - ) - is TxInfo.DestinationType.Single -> TextReference.Str(destination.addressType.address) - } - } else { - when (val source = sourceType) { - is TxInfo.SourceType.Multiple -> TextReference.Res(R.string.transaction_history_multiple_addresses) - is TxInfo.SourceType.Single -> TextReference.Str(source.address) - } - } - - private fun TxInfo.extractIconRes() = if (isOutgoing) { - R.drawable.ic_arrow_up_24 - } else { - R.drawable.ic_arrow_down_24 - } - - private fun TxInfo.getAmount(cryptoCurrency: CryptoCurrency): String { - return amount.format { crypto(cryptoCurrency) } - } - - private fun TxInfo.extractTimestamp(): TextReference { - val date = timestampInMillis.toDateFormatWithTodayYesterday( - formatter = DateTimeFormatters.dateDDMMYYYY, - ) - val time = timestampInMillis.toTimeFormat() - return TextReference.Res(R.string.send_date_format, wrappedList(date, time)) - } - - companion object { - private const val RECENT_LIST_SIZE = 10 - } -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientMemoFieldConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientMemoFieldConverter.kt deleted file mode 100644 index 5941575a37..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientMemoFieldConverter.kt +++ /dev/null @@ -1,60 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.recipient - -import androidx.annotation.StringRes -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.text.input.KeyboardType -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.domain.models.network.Network -import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.features.send.impl.R -import com.tangem.features.send.impl.presentation.model.SendClickIntents -import com.tangem.features.send.impl.presentation.state.fields.SendTextField -import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter - -internal class SendRecipientMemoFieldConverter( - private val clickIntents: SendClickIntents, - private val cryptoCurrencyStatus: Provider, -) : Converter { - - fun convertOrNull(memoValue: String?): SendTextField.RecipientMemo? { - val cryptoCurrency = cryptoCurrencyStatus().currency - val memo = memoValue ?: "" - - return when (cryptoCurrency.network.transactionExtrasType) { - Network.TransactionExtrasType.NONE -> null - Network.TransactionExtrasType.MEMO -> convert( - value = Data( - memo = memo, - label = R.string.send_extras_hint_memo, - ), - ) - Network.TransactionExtrasType.DESTINATION_TAG -> convert( - value = Data( - memo = memo, - label = R.string.send_destination_tag_field, - ), - ) - } - } - - override fun convert(value: Data): SendTextField.RecipientMemo { - return SendTextField.RecipientMemo( - value = value.memo, - onValueChange = clickIntents::onRecipientMemoValueChange, - keyboardOptions = KeyboardOptions( - imeAction = ImeAction.Done, - keyboardType = KeyboardType.Text, - ), - placeholder = resourceReference(R.string.send_optional_field), - label = resourceReference(value.label), - error = resourceReference(R.string.send_memo_destination_tag_error), - disabledText = resourceReference(R.string.send_additional_field_already_included), - isEnabled = true, - isValuePasted = false, - ) - } - - data class Data(val memo: String, @StringRes val label: Int) -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientStateConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientStateConverter.kt deleted file mode 100644 index 7829d19b85..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientStateConverter.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.recipient - -import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.features.send.impl.presentation.state.SendStates -import com.tangem.features.send.impl.presentation.state.recipient.utils.* -import com.tangem.features.send.impl.presentation.model.SendClickIntents -import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter - -internal class SendRecipientStateConverter( - private val clickIntents: SendClickIntents, - private val cryptoCurrencyStatusProvider: Provider, -) : Converter { - - private val addressFieldConverter by lazy { SendRecipientAddressFieldConverter(clickIntents) } - private val memoFieldConverter by lazy { - SendRecipientMemoFieldConverter( - clickIntents, - cryptoCurrencyStatusProvider, - ) - } - - override fun convert(value: Data): SendStates.RecipientState { - return SendStates.RecipientState( - addressTextField = addressFieldConverter.convert(value.address), - memoTextField = memoFieldConverter.convertOrNull(value.memo), - network = cryptoCurrencyStatusProvider().currency.network.name, - isPrimaryButtonEnabled = false, - wallets = loadingListState(WALLET_KEY_TAG, WALLET_DEFAULT_COUNT), - recent = loadingListState(RECENT_KEY_TAG, RECENT_DEFAULT_COUNT), - ) - } - - data class Data(val address: String, val memo: String? = null) -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientWalletListConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientWalletListConverter.kt deleted file mode 100644 index 76c25eb640..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientWalletListConverter.kt +++ /dev/null @@ -1,65 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.recipient - -import com.tangem.core.ui.extensions.TextReference -import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.features.send.impl.presentation.domain.AvailableWallet -import com.tangem.features.send.impl.presentation.domain.SendRecipientListContent -import com.tangem.features.send.impl.presentation.state.recipient.utils.WALLET_DEFAULT_COUNT -import com.tangem.features.send.impl.presentation.state.recipient.utils.WALLET_KEY_TAG -import com.tangem.features.send.impl.presentation.state.recipient.utils.emptyListState -import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter -import kotlinx.collections.immutable.PersistentList -import kotlinx.collections.immutable.toPersistentList - -internal class SendRecipientWalletListConverter( - private val cryptoCurrencyStatusProvider: Provider, - private val isUtxoConsolidationAvailableProvider: Provider, -) : - Converter, PersistentList> { - override fun convert(value: List): PersistentList { - return value.filterWallets().ifEmpty { - emptyListState(WALLET_KEY_TAG, WALLET_DEFAULT_COUNT) - } - } - - private fun List.filterWallets(): PersistentList { - var walletsCounter = 0 - val currentAddress: String = runCatching { - cryptoCurrencyStatusProvider().value.networkAddress?.defaultAddress?.value - }.getOrNull().orEmpty() - - return this.filterNotNull() - .filter { - val isCoin = it.cryptoCurrency is CryptoCurrency.Coin - val isNotSameAddress = it.address != currentAddress - val isNotBlankAddress = it.address.isNotBlank() - - isNotBlankAddress && isCoin && (isNotSameAddress || isUtxoConsolidationAvailableProvider()) - } - .groupBy { item -> item.name } - .values.map { wallets -> - val groupedByWallet = wallets.groupBy { it.userWalletId } - var i = 0 - groupedByWallet - .flatMap { item -> - item.value.map { wallet -> - val name = if (groupedByWallet.size > 1) { - "${wallet.name} ${++i}" - } else { - wallet.name - } - - SendRecipientListContent( - id = "${WALLET_KEY_TAG}${walletsCounter++}", - title = TextReference.Str(wallet.address), - subtitle = TextReference.Str(name), - ) - } - } - } - .flatten() - .toPersistentList() - } -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/utils/RecentListUtils.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/utils/RecentListUtils.kt deleted file mode 100644 index c2d5ad9739..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/utils/RecentListUtils.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.recipient.utils - -import com.tangem.features.send.impl.presentation.domain.SendRecipientListContent -import kotlinx.collections.immutable.toPersistentList - -internal const val WALLET_DEFAULT_COUNT = 1 -internal const val RECENT_DEFAULT_COUNT = 3 -internal const val WALLET_KEY_TAG = "wallet" -internal const val RECENT_KEY_TAG = "recent" - -internal fun loadingListState(tag: String, count: Int) = buildList { - repeat(count) { - add( - SendRecipientListContent( - id = "$tag$it", - isLoading = true, - ), - ) - } -}.toPersistentList() - -internal fun emptyListState(tag: String, count: Int) = buildList { - repeat(count) { - add( - SendRecipientListContent( - id = "$tag$it", - isLoading = false, - isVisible = false, - ), - ) - } -}.toPersistentList() \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendEventEffect.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendEventEffect.kt deleted file mode 100644 index aa5604d797..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendEventEffect.kt +++ /dev/null @@ -1,79 +0,0 @@ -package com.tangem.features.send.impl.presentation.ui - -import androidx.compose.material3.SnackbarHostState -import androidx.compose.runtime.* -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalSoftwareKeyboardController -import com.tangem.common.ui.alerts.models.AlertUM -import com.tangem.core.ui.components.BasicDialog -import com.tangem.core.ui.components.DialogButtonUM -import com.tangem.core.ui.event.EventEffect -import com.tangem.core.ui.event.StateEvent -import com.tangem.core.ui.extensions.resolveReference -import com.tangem.core.ui.extensions.stringResourceSafe -import com.tangem.features.send.impl.R -import com.tangem.features.send.impl.presentation.state.SendEvent - -@Composable -internal fun SendEventEffect(event: StateEvent, snackbarHostState: SnackbarHostState) { - val resources = LocalContext.current.resources - var alertConfig by remember { mutableStateOf(value = null) } - - val keyboardController = LocalSoftwareKeyboardController.current - LaunchedEffect(key1 = alertConfig) { - keyboardController?.hide() - } - - alertConfig?.let { - SendAlert(state = it, onDismiss = { alertConfig = null }) - } - - EventEffect( - event = event, - onTrigger = { value -> - when (value) { - is SendEvent.ShowSnackBar -> { - snackbarHostState.showSnackbar(message = value.text.resolveReference(resources)) - } - is SendEvent.ShowAlert -> { - alertConfig = value.alert - } - } - }, - ) -} - -@Composable -internal fun SendAlert(state: AlertUM, onDismiss: () -> Unit) { - val confirmButton: DialogButtonUM - val dismissButton: DialogButtonUM? - - val onActionClick = state.onConfirmClick - if (onActionClick != null) { - confirmButton = DialogButtonUM( - title = state.confirmButtonText.resolveReference(), - onClick = { - onActionClick() - onDismiss() - }, - ) - dismissButton = DialogButtonUM( - title = stringResourceSafe(id = R.string.common_cancel), - onClick = onDismiss, - ) - } else { - confirmButton = DialogButtonUM( - title = state.confirmButtonText.resolveReference(), - onClick = onDismiss, - ) - dismissButton = null - } - - BasicDialog( - message = state.message.resolveReference(), - confirmButton = confirmButton, - onDismissDialog = onDismiss, - title = state.title?.resolveReference(), - dismissButton = dismissButton, - ) -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt deleted file mode 100644 index f1a3d33ffc..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt +++ /dev/null @@ -1,309 +0,0 @@ -package com.tangem.features.send.impl.presentation.ui - -import androidx.compose.animation.* -import androidx.compose.animation.core.tween -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Icon -import androidx.compose.material3.Text -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.hapticfeedback.HapticFeedbackType -import androidx.compose.ui.platform.LocalHapticFeedback -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.style.TextAlign -import com.tangem.blockchain.common.transaction.Fee -import com.tangem.common.ui.amountScreen.models.AmountState -import com.tangem.common.ui.amountScreen.ui.SendDoneButtons -import com.tangem.common.ui.amountScreen.utils.getFiatString -import com.tangem.core.ui.R -import com.tangem.core.ui.components.Keyboard -import com.tangem.core.ui.components.SpacerW12 -import com.tangem.core.ui.components.buttons.common.TangemButton -import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition -import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults -import com.tangem.core.ui.components.keyboardAsState -import com.tangem.core.ui.extensions.* -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.utils.BigDecimalFormatter -import com.tangem.features.send.impl.presentation.state.SendStates -import com.tangem.features.send.impl.presentation.state.SendUiCurrentScreen -import com.tangem.features.send.impl.presentation.state.SendUiState -import com.tangem.features.send.impl.presentation.state.SendUiStateType - -@Composable -internal fun SendNavigationButtons( - uiState: SendUiState, - currentState: SendUiCurrentScreen, - modifier: Modifier = Modifier, -) { - val sendState = uiState.sendState ?: return - val isSuccess = sendState.isSuccess - val isSendingState = currentState.type == SendUiStateType.Send && !isSuccess - val isSentState = currentState.type == SendUiStateType.Send && isSuccess - - Column( - modifier = modifier - .padding( - start = TangemTheme.dimens.spacing16, - end = TangemTheme.dimens.spacing16, - bottom = TangemTheme.dimens.spacing16, - ), - ) { - SendingText( - uiState = uiState, - isEditState = currentState.isFromConfirmation, - isVisible = isSendingState, - ) - SendDoneButtons( - txUrl = sendState.txUrl, - onExploreClick = uiState.clickIntents::onExploreClick, - onShareClick = { uiState.clickIntents.onShareClick(it) }, - isVisible = isSentState, - ) - SendNavigationButton( - uiState = uiState, - currentState = currentState, - modifier = Modifier, - ) - } -} - -@Composable -private fun SendNavigationButton( - uiState: SendUiState, - currentState: SendUiCurrentScreen, - modifier: Modifier = Modifier, -) { - val hapticFeedback = LocalHapticFeedback.current - val sendState = uiState.sendState ?: return - val isEditingDisabled = uiState.isEditingDisabled - val isSuccess = sendState.isSuccess - val isSending = sendState.isSending - - val isFromConfirmation = currentState.isFromConfirmation - val isCorrectScreen = currentState.type == SendUiStateType.Amount || currentState.type == SendUiStateType.Fee - val isSendingState = currentState.type == SendUiStateType.Send && !isSuccess && !isSending - - val (buttonTextId, buttonClick) = getButtonData( - currentState = currentState, - isSuccess = isSuccess, - isSending = isSending, - uiState = uiState, - ) - val isButtonEnabled = isButtonEnabled(currentState, uiState) - val buttonIcon = if (isSendingState) { - TangemButtonIconPosition.End(R.drawable.ic_tangem_24) - } else { - TangemButtonIconPosition.None - } - - Row(modifier = modifier) { - AnimatedVisibility( - visible = !isEditingDisabled && isCorrectScreen && !isFromConfirmation, - enter = expandHorizontally(expandFrom = Alignment.End), - exit = shrinkHorizontally(shrinkTowards = Alignment.End), - ) { - Row { - Icon( - painter = painterResource(R.drawable.ic_back_24), - tint = TangemTheme.colors.icon.primary1, - contentDescription = null, - modifier = Modifier - .clip(RoundedCornerShape(TangemTheme.dimens.radius16)) - .background(TangemTheme.colors.button.secondary) - .clickable { uiState.clickIntents.onPrevClick() } - .padding(TangemTheme.dimens.spacing12), - ) - SpacerW12() - } - } - TangemButton( - modifier = Modifier.fillMaxWidth(), - text = stringResourceSafe(buttonTextId), - icon = buttonIcon, - enabled = isButtonEnabled, - onClick = { - if (isSendingState) hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) - buttonClick() - }, - showProgress = false, - colors = TangemButtonsDefaults.primaryButtonColors, - textStyle = TangemTheme.typography.subtitle1, - ) - } -} - -@Composable -private fun SendingText( - uiState: SendUiState, - isEditState: Boolean, - isVisible: Boolean, - modifier: Modifier = Modifier, -) { - var isVisibleProxy by remember { mutableStateOf(isVisible) } - val keyboard by keyboardAsState() - - // the text should appear when the keyboard is closed - LaunchedEffect(isVisible, keyboard) { - if (isVisible && keyboard is Keyboard.Opened) { - return@LaunchedEffect - } - isVisibleProxy = isVisible - } - - AnimatedVisibility( - visible = isVisibleProxy, - modifier = modifier, - enter = slideInVertically() + fadeIn(), - exit = fadeOut(tween(durationMillis = 300)), - label = "Animate show sending state text", - ) { - val amountState = uiState.getAmountState(isEditState) as? AmountState.Data - val feeState = uiState.getFeeState(isEditState) - val fiatRate = feeState?.rate - val fiatAmount = amountState?.amountTextField?.fiatAmount - val feeFiat = fiatRate?.let { feeState.fee?.amount?.value?.multiply(it) } - val sendingFiat = if (uiState.isSubtracted) { - fiatAmount?.value - } else { - if (feeState?.isFeeConvertibleToFiat == true) { - feeFiat?.let { fiatAmount?.value?.plus(it) } - } else { - fiatAmount?.value - } - } - - if (feeFiat != null && sendingFiat != null) { - val sendingValue = BigDecimalFormatter.formatFiatAmount( - fiatAmount = sendingFiat, - fiatCurrencySymbol = feeState.appCurrency.symbol, - fiatCurrencyCode = feeState.appCurrency.code, - ) - val textResource = remember(uiState) { - val fee = feeState.fee - if (feeState.isTronToken && fee is Fee.Tron) { - getTokenFeeSendingText( - feeState = feeState, - fee = fee, - sendingValue = sendingValue, - ) - } else { - resourceReference( - id = if (feeState.isFeeConvertibleToFiat) { - R.string.send_summary_transaction_description - } else { - R.string.send_summary_transaction_description_no_fiat_fee - }, - formatArgs = wrappedList(sendingValue, feeState.getFiatValue()), - ) - } - } - Text( - text = textResource.resolveAnnotatedReference(), - textAlign = TextAlign.Center, - style = TangemTheme.typography.caption2, - color = TangemTheme.colors.text.primary1, - modifier = Modifier - .fillMaxWidth() - .padding(TangemTheme.dimens.spacing12), - ) - } - } -} - -private fun SendStates.FeeState.getFiatValue() = if (isFeeConvertibleToFiat) { - getFiatString( - value = fee?.amount?.value, - rate = rate, - appCurrency = appCurrency, - ) -} else { - val amount = fee?.amount - amount?.value.format { - crypto( - decimals = amount?.decimals ?: 0, - symbol = amount?.currencySymbol.orEmpty(), - ).fee( - canBeLower = isFeeApproximate, - ) - } -} - -private fun getButtonData( - uiState: SendUiState, - currentState: SendUiCurrentScreen, - isSuccess: Boolean, - isSending: Boolean, -): Pair Unit> { - return when (currentState.type) { - SendUiStateType.None, - SendUiStateType.Amount, - SendUiStateType.Recipient, - SendUiStateType.Fee, - -> R.string.common_next to { uiState.clickIntents.onNextClick() } - SendUiStateType.EditFee, - SendUiStateType.EditAmount, - SendUiStateType.EditRecipient, - -> R.string.common_continue to { uiState.clickIntents.onNextClick(isFromEdit = true) } - SendUiStateType.Send -> when { - isSuccess -> R.string.common_close - isSending -> R.string.send_sending - else -> R.string.common_send - } to uiState.clickIntents::onSendClick - } -} - -private fun isButtonEnabled(currentState: SendUiCurrentScreen, uiState: SendUiState): Boolean { - return when (currentState.type) { - SendUiStateType.Amount -> uiState.amountState.isPrimaryButtonEnabled - SendUiStateType.Recipient -> uiState.recipientState?.isPrimaryButtonEnabled - SendUiStateType.Fee -> uiState.feeState?.isPrimaryButtonEnabled - SendUiStateType.Send -> uiState.sendState?.isPrimaryButtonEnabled - SendUiStateType.EditAmount -> uiState.editAmountState.isPrimaryButtonEnabled - SendUiStateType.EditRecipient -> uiState.editRecipientState?.isPrimaryButtonEnabled - SendUiStateType.EditFee -> uiState.editFeeState?.isPrimaryButtonEnabled - else -> true - } ?: false -} - -private fun getTokenFeeSendingText(feeState: SendStates.FeeState, fee: Fee.Tron, sendingValue: String): TextReference { - val suffix = when { - fee.remainingEnergy == 0L -> { - resourceReference( - R.string.send_summary_transaction_description_suffix_including, - wrappedList(feeState.getFiatValue()), - ) - } - fee.feeEnergy <= fee.remainingEnergy -> { - resourceReference( - R.string.send_summary_transaction_description_suffix_fee_covered, - wrappedList(fee.feeEnergy), - ) - } - else -> { - resourceReference( - R.string.send_summary_transaction_description_suffix_fee_reduced, - wrappedList(fee.remainingEnergy), - ) - } - } - val prefix = resourceReference( - R.string.send_summary_transaction_description_prefix, - wrappedList(sendingValue), - ) - - return combinedReference(prefix, COMMA_SEPARATOR, suffix) -} - -private val COMMA_SEPARATOR = stringReference(", ") \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt deleted file mode 100644 index c588753385..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt +++ /dev/null @@ -1,240 +0,0 @@ -package com.tangem.features.send.impl.presentation.ui - -import android.content.res.Configuration -import androidx.activity.compose.BackHandler -import androidx.compose.animation.AnimatedContent -import androidx.compose.animation.AnimatedContentTransitionScope -import androidx.compose.animation.ExperimentalAnimationApi -import androidx.compose.animation.core.tween -import androidx.compose.animation.togetherWith -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* -import androidx.compose.material3.SnackbarHostState -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import com.tangem.common.ui.amountScreen.AmountScreenContent -import com.tangem.common.ui.amountScreen.models.AmountState -import com.tangem.core.ui.components.appbar.AppBarWithBackButtonAndIcon -import com.tangem.core.ui.extensions.resolveReference -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.res.TangemThemePreview -import com.tangem.features.send.impl.R -import com.tangem.features.send.impl.presentation.state.SendUiCurrentScreen -import com.tangem.features.send.impl.presentation.state.SendUiState -import com.tangem.features.send.impl.presentation.state.SendUiStateType -import com.tangem.features.send.impl.presentation.state.previewdata.ConfirmStatePreviewData -import com.tangem.features.send.impl.presentation.state.previewdata.SendStatesPreviewData -import com.tangem.features.send.impl.presentation.ui.fee.SendSpeedAndFeeContent -import com.tangem.features.send.impl.presentation.ui.recipient.SendRecipientContent -import com.tangem.features.send.impl.presentation.ui.send.SendContent -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.withIndex - -@Composable -internal fun SendScreen(uiState: SendUiState, currentState: SendUiCurrentScreen) { - val snackbarHostState = remember { SnackbarHostState() } - val onBackClick = uiState.clickIntents::onBackClick.takeIf { - uiState.sendState?.isSending != true - } ?: {} - BackHandler(onBack = onBackClick) - Column( - modifier = Modifier - .background(color = TangemTheme.colors.background.tertiary) - .fillMaxSize() - .imePadding() - .systemBarsPadding(), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - SendAppBar( - uiState = uiState, - currentState = currentState, - ) - SendScreenContent( - uiState = uiState, - currentState = currentState, - modifier = Modifier.weight(1f), - ) - SendNavigationButtons( - uiState = uiState, - currentState = currentState, - ) - } - - SendEventEffect( - event = uiState.event, - snackbarHostState = snackbarHostState, - ) -} - -@Composable -private fun SendAppBar(uiState: SendUiState, currentState: SendUiCurrentScreen) { - val (titleRes, subtitleRes) = when (currentState.type) { - SendUiStateType.Amount, - SendUiStateType.EditAmount, - -> resourceReference(R.string.send_amount_label) to null - SendUiStateType.Recipient, - SendUiStateType.EditRecipient, - -> resourceReference(R.string.send_recipient_label) to null - SendUiStateType.Fee, - SendUiStateType.EditFee, - -> resourceReference(R.string.common_fee_selector_title) to null - SendUiStateType.Send -> if (uiState.sendState?.isSuccess == false) { - resourceReference(R.string.send_summary_title, wrappedList(uiState.cryptoCurrencyName)) to - (uiState.amountState as? AmountState.Data)?.title - } else { - null to null - } - else -> null to null - } - val iconRes = if (currentState.type == SendUiStateType.Recipient) { - R.drawable.ic_qrcode_scan_24 - } else { - null - } - val backIcon = when (currentState.type) { - SendUiStateType.EditAmount, - SendUiStateType.EditFee, - SendUiStateType.EditRecipient, - -> R.drawable.ic_back_24 - else -> R.drawable.ic_close_24 - } - AppBarWithBackButtonAndIcon( - text = titleRes?.resolveReference(), - subtitle = subtitleRes?.resolveReference(), - onBackClick = uiState.clickIntents::onCloseClick, - onIconClick = uiState.clickIntents::onQrCodeScanClick, - backIconRes = backIcon, - iconRes = iconRes, - backgroundColor = TangemTheme.colors.background.tertiary, - modifier = Modifier.height(TangemTheme.dimens.size56), - ) -} - -@OptIn(ExperimentalAnimationApi::class) -@Composable -private fun SendScreenContent(uiState: SendUiState, currentState: SendUiCurrentScreen, modifier: Modifier = Modifier) { - var currentStateProxy by remember { mutableStateOf(currentState) } - var isTransitionAnimationRunning by remember { mutableStateOf(false) } - - // Prevent quick screen changes to avoid some of the transition animation distortions - LaunchedEffect(currentState) { - snapshotFlow { isTransitionAnimationRunning } - .withIndex() - .map { (index, running) -> - if (running && index != 0) { - delay(timeMillis = 200) - } - running - } - .first { !it } - - currentStateProxy = currentState - } - // Restrict pressing the back button while screen transition is running to avoid most of the animation distortions - BackHandler(enabled = isTransitionAnimationRunning) {} - - // Box is needed to fix animation with resizing of AnimatedContent - Box(modifier = modifier.fillMaxSize()) { - AnimatedContent( - targetState = currentStateProxy, - contentAlignment = Alignment.TopCenter, - label = "Send Scree Navigation", - transitionSpec = { - val direction = if (initialState.type.ordinal < targetState.type.ordinal) { - AnimatedContentTransitionScope.SlideDirection.Start - } else { - AnimatedContentTransitionScope.SlideDirection.End - } - - slideIntoContainer(towards = direction, animationSpec = tween()) - .togetherWith(slideOutOfContainer(towards = direction, animationSpec = tween())) - }, - ) { state -> - isTransitionAnimationRunning = transition.targetState != transition.currentState - - when (state.type) { - SendUiStateType.Amount -> AmountScreenContent( - amountState = uiState.amountState, - isBalanceHidden = uiState.isBalanceHidden, - clickIntents = uiState.clickIntents, - modifier = Modifier.background(TangemTheme.colors.background.tertiary), - ) - SendUiStateType.EditAmount -> AmountScreenContent( - amountState = uiState.editAmountState, - isBalanceHidden = uiState.isBalanceHidden, - clickIntents = uiState.clickIntents, - modifier = Modifier.background(TangemTheme.colors.background.tertiary), - ) - SendUiStateType.Recipient -> SendRecipientContent( - uiState = uiState.recipientState, - clickIntents = uiState.clickIntents, - isBalanceHidden = uiState.isBalanceHidden, - ) - SendUiStateType.EditRecipient -> SendRecipientContent( - uiState = uiState.editRecipientState, - clickIntents = uiState.clickIntents, - isBalanceHidden = uiState.isBalanceHidden, - ) - SendUiStateType.EditFee -> SendSpeedAndFeeContent( - state = uiState.editFeeState, - clickIntents = uiState.clickIntents, - ) - SendUiStateType.Send -> SendContent(uiState) - else -> Unit - } - } - } -} - -// region Preview -@Preview(showBackground = true, widthDp = 360, heightDp = 736) -@Preview(showBackground = true, widthDp = 360, heightDp = 736, uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -private fun SendScreen_Preview(@PreviewParameter(SendScreenPreviewProvider::class) data: SendScreenPreview) { - TangemThemePreview { - SendScreen( - uiState = data.uiState, - currentState = data.currentState, - ) - } -} - -private class SendScreenPreviewProvider : PreviewParameterProvider { - override val values: Sequence - get() = sequenceOf( - SendScreenPreview( - uiState = SendStatesPreviewData.uiState, - currentState = SendUiCurrentScreen(type = SendUiStateType.Recipient, isFromConfirmation = false), - ), - SendScreenPreview( - uiState = SendStatesPreviewData.uiState, - currentState = SendUiCurrentScreen(type = SendUiStateType.Amount, isFromConfirmation = false), - ), - SendScreenPreview( - uiState = SendStatesPreviewData.uiState, - currentState = SendUiCurrentScreen(type = SendUiStateType.Send, isFromConfirmation = false), - ), - SendScreenPreview( - uiState = SendStatesPreviewData.uiState, - currentState = SendUiCurrentScreen(type = SendUiStateType.EditFee, isFromConfirmation = true), - ), - SendScreenPreview( - uiState = SendStatesPreviewData.uiState.copy(sendState = ConfirmStatePreviewData.sendDoneState), - currentState = SendUiCurrentScreen(type = SendUiStateType.Send, isFromConfirmation = false), - ), - ) -} - -private data class SendScreenPreview( - val uiState: SendUiState, - val currentState: SendUiCurrentScreen, -) -// endregion \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/common/Notifications.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/common/Notifications.kt deleted file mode 100644 index 87d8645108..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/common/Notifications.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.tangem.features.send.impl.presentation.ui.common - -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyListScope -import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.ui.Modifier -import com.tangem.common.ui.notifications.NotificationUM -import com.tangem.core.ui.components.notifications.Notification -import com.tangem.core.ui.res.TangemTheme -import kotlinx.collections.immutable.ImmutableList - -internal fun LazyListScope.notifications( - notifications: ImmutableList, - modifier: Modifier = Modifier, - hasPaddingAbove: Boolean = false, - isClickDisabled: Boolean = false, -) { - itemsIndexed( - items = notifications, - key = { _, item -> item::class.java }, - contentType = { _, item -> item::class.java }, - itemContent = { i, item -> - val topPadding = if (i == 0 && hasPaddingAbove) { - TangemTheme.dimens.spacing0 - } else { - TangemTheme.dimens.spacing12 - } - Notification( - config = item.config, - modifier = modifier - .padding(top = topPadding) - .animateItem(fadeInSpec = null, fadeOutSpec = null), - containerColor = when (item) { - is NotificationUM.Error.TokenExceedsBalance, - is NotificationUM.Warning.NetworkFeeUnreachable, - is NotificationUM.Warning.HighFeeError, - -> TangemTheme.colors.background.action - else -> TangemTheme.colors.button.disabled - }, - iconTint = when (item) { - is NotificationUM.Error.TokenExceedsBalance, - is NotificationUM.Warning, - -> null - is NotificationUM.Error -> TangemTheme.colors.icon.warning - is NotificationUM.Info -> TangemTheme.colors.icon.accent - }, - isEnabled = !isClickDisabled, - ) - }, - ) -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFee.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFee.kt deleted file mode 100644 index 65ad8d5140..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFee.kt +++ /dev/null @@ -1,84 +0,0 @@ -package com.tangem.features.send.impl.presentation.ui.fee - -import androidx.compose.animation.* -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.padding -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import com.tangem.core.ui.components.inputrow.InputRowEnterAmount -import com.tangem.core.ui.components.inputrow.InputRowEnterInfoAmount -import com.tangem.core.ui.res.TangemTheme -import com.tangem.features.send.impl.presentation.state.fee.FeeType -import com.tangem.features.send.impl.presentation.state.fields.SendTextField -import com.tangem.core.ui.components.containers.FooterContainer -import kotlinx.collections.immutable.ImmutableList - -@Composable -internal fun SendCustomFee( - customValues: ImmutableList, - selectedFee: FeeType, - hasNotifications: Boolean, - modifier: Modifier = Modifier, -) { - AnimatedVisibility( - visible = selectedFee == FeeType.Custom && customValues.isNotEmpty(), - label = "Custom Fee Selected Animation", - enter = expandVertically().plus(fadeIn()), - exit = shrinkVertically().plus(fadeOut()), - ) { - val bottomPadding = if (hasNotifications) { - TangemTheme.dimens.spacing12 - } else { - TangemTheme.dimens.spacing0 - } - Column( - verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), - modifier = modifier.padding(bottom = bottomPadding), - ) { - repeat(customValues.size) { index -> - val value = customValues[index] - FooterContainer( - footer = value.footer, - ) { - if (value.label != null) { - InputRowEnterInfoAmount( - text = value.value, - decimals = value.decimals, - symbol = value.symbol, - title = value.title, - info = value.label, - keyboardOptions = value.keyboardOptions, - keyboardActions = value.keyboardActions, - onValueChange = value.onValueChange, - showDivider = false, - isReadOnly = value.isReadonly, - modifier = Modifier - .background( - color = TangemTheme.colors.background.action, - shape = TangemTheme.shapes.roundedCornersXMedium, - ), - ) - } else { - InputRowEnterAmount( - text = value.value, - decimals = value.decimals, - title = value.title, - symbol = value.symbol, - onValueChange = value.onValueChange, - keyboardOptions = value.keyboardOptions, - keyboardActions = value.keyboardActions, - showDivider = false, - modifier = Modifier - .background( - color = TangemTheme.colors.background.action, - shape = TangemTheme.shapes.roundedCornersXMedium, - ), - ) - } - } - } - } - } -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedAndFeeContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedAndFeeContent.kt deleted file mode 100644 index cbb85ab5e8..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedAndFeeContent.kt +++ /dev/null @@ -1,111 +0,0 @@ -package com.tangem.features.send.impl.presentation.ui.fee - -import android.content.res.Configuration -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.LazyListScope -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.features.send.impl.presentation.state.SendStates -import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState -import com.tangem.features.send.impl.presentation.state.fee.FeeType -import com.tangem.features.send.impl.presentation.state.previewdata.FeeStatePreviewData -import com.tangem.features.send.impl.presentation.state.previewdata.SendClickIntentsStub -import com.tangem.features.send.impl.presentation.ui.common.notifications -import com.tangem.features.send.impl.presentation.model.SendClickIntents - -private const val FEE_SELECTOR_KEY = "FEE_SELECTOR_KEY" -private const val FEE_CUSTOM_KEY = "FEE_CUSTOM_KEY" - -@Composable -internal fun SendSpeedAndFeeContent(state: SendStates.FeeState?, clickIntents: SendClickIntents) { - if (state == null) return - val feeSendState = state.feeSelectorState as? FeeSelectorState.Content - val notifications = state.notifications - val isCustomSelected = feeSendState?.selectedFee == FeeType.Custom - val hasNotifications = notifications.isNotEmpty() - LazyColumn( - modifier = Modifier // Do not put fillMaxSize() in here - .background(TangemTheme.colors.background.tertiary) - .padding( - start = TangemTheme.dimens.spacing16, - end = TangemTheme.dimens.spacing16, - bottom = TangemTheme.dimens.spacing16, - ), - ) { - feeSelector(state, clickIntents) - if (feeSendState != null) { - customFee(feeSendState = feeSendState, hasNotifications = hasNotifications) - } - notifications(notifications = notifications, hasPaddingAbove = isCustomSelected) - } -} - -@OptIn(ExperimentalFoundationApi::class) -private fun LazyListScope.feeSelector(state: SendStates.FeeState, clickIntents: SendClickIntents) { - item( - key = FEE_SELECTOR_KEY, - ) { - SendSpeedSelector( - state = state, - clickIntents = clickIntents, - modifier = Modifier.animateItemPlacement(), - ) - } -} - -@OptIn(ExperimentalFoundationApi::class) -internal fun LazyListScope.customFee( - feeSendState: FeeSelectorState.Content, - hasNotifications: Boolean, - modifier: Modifier = Modifier, -) { - item( - key = FEE_CUSTOM_KEY, - ) { - SendCustomFee( - customValues = feeSendState.customValues, - selectedFee = feeSendState.selectedFee, - hasNotifications = hasNotifications, - modifier = modifier - .fillMaxWidth() - .animateItemPlacement() - .background(TangemTheme.colors.background.tertiary) - .padding(top = TangemTheme.dimens.spacing12), - ) - } -} - -// region Preview -@Preview(showBackground = true, widthDp = 360) -@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -private fun SendSpeedAndFeeContent_Preview( - @PreviewParameter(FeeStatePreviewProvider::class) feeState: SendStates.FeeState, -) { - TangemThemePreview { - SendSpeedAndFeeContent( - state = feeState, - clickIntents = SendClickIntentsStub, - ) - } -} - -private class FeeStatePreviewProvider : PreviewParameterProvider { - override val values: Sequence - get() = sequenceOf( - FeeStatePreviewData.feeState, - FeeStatePreviewData.feeChoosableState, - FeeStatePreviewData.feeCustomState, - FeeStatePreviewData.errorFeeState, - ) -} -// endregion \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelector.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelector.kt deleted file mode 100644 index 1e8293ea4b..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelector.kt +++ /dev/null @@ -1,129 +0,0 @@ -package com.tangem.features.send.impl.presentation.ui.fee - -import android.content.res.Configuration -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.text.ClickableText -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.text.SpanStyle -import androidx.compose.ui.text.buildAnnotatedString -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.withStyle -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.tooling.preview.PreviewParameterProvider -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.impl.R -import com.tangem.features.send.impl.presentation.state.SendStates -import com.tangem.features.send.impl.presentation.state.fee.FeeType -import com.tangem.features.send.impl.presentation.state.previewdata.FeeStatePreviewData -import com.tangem.features.send.impl.presentation.state.previewdata.SendClickIntentsStub -import com.tangem.features.send.impl.presentation.model.SendClickIntents - -@Suppress("LongMethod") -@Composable -internal fun SendSpeedSelector( - state: SendStates.FeeState, - clickIntents: SendClickIntents, - modifier: Modifier = Modifier, -) { - Column(modifier = modifier) { - Column( - modifier = Modifier - .fillMaxWidth() - .clip(TangemTheme.shapes.roundedCornersXMedium) - .background(TangemTheme.colors.background.action), - ) { - SendSpeedSelectorItem( - titleRes = R.string.common_fee_selector_option_slow, - iconRes = R.drawable.ic_tortoise_24, - feeType = FeeType.Slow, - state = state, - onSelect = { clickIntents.onFeeSelectorClick(FeeType.Slow) }, - ) - SendSpeedSelectorItem( - titleRes = R.string.common_fee_selector_option_market, - iconRes = R.drawable.ic_bird_24, - feeType = FeeType.Market, - state = state, - onSelect = { clickIntents.onFeeSelectorClick(FeeType.Market) }, - ) - SendSpeedSelectorItem( - titleRes = R.string.common_fee_selector_option_fast, - iconRes = R.drawable.ic_hare_24, - feeType = FeeType.Fast, - state = state, - onSelect = { clickIntents.onFeeSelectorClick(FeeType.Fast) }, - ) - SendSpeedSelectorItem( - titleRes = R.string.common_custom, - iconRes = R.drawable.ic_edit_24, - feeType = FeeType.Custom, - state = state, - onSelect = { clickIntents.onFeeSelectorClick(FeeType.Custom) }, - ) - } - FooterText(clickIntents::onReadMoreClick) - } -} - -@Composable -private fun FooterText(onReadMoreClick: () -> Unit) { - val linkText = stringResourceSafe(R.string.common_read_more) - val fullString = stringResourceSafe(R.string.common_fee_selector_footer, linkText) - val linkTextPosition = fullString.length - linkText.length - val defaultStyle = TangemTheme.colors.text.tertiary - val linkStyle = TangemTheme.colors.text.accent - val annotatedString = remember(defaultStyle, linkStyle) { - buildAnnotatedString { - withStyle(SpanStyle(defaultStyle)) { - append(fullString.substring(0, linkTextPosition)) - } - withStyle(SpanStyle(linkStyle)) { - append(fullString.substring(linkTextPosition, fullString.length)) - } - } - } - - val click = { i: Int -> - val readMoreStyle = requireNotNull(annotatedString.spanStyles.getOrNull(1)) - if (i in readMoreStyle.start..readMoreStyle.end) { - onReadMoreClick() - } - } - - ClickableText( - text = annotatedString, - style = TangemTheme.typography.caption2.copy(textAlign = TextAlign.Start), - modifier = Modifier.padding(top = TangemTheme.dimens.spacing8), - onClick = click, - ) -} - -// region Preview -@Preview -@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -private fun SendSpeedSelectorPreview( - @PreviewParameter(SendSpeedSelectorPreviewProvider::class) feeState: SendStates.FeeState, -) { - TangemThemePreview { - SendSpeedSelector(state = feeState, clickIntents = SendClickIntentsStub) - } -} - -private class SendSpeedSelectorPreviewProvider : PreviewParameterProvider { - override val values: Sequence - get() = sequenceOf( - FeeStatePreviewData.feeState, - FeeStatePreviewData.errorFeeState, - ) -} -// endregion \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelectorItem.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelectorItem.kt deleted file mode 100644 index 6774c2572d..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelectorItem.kt +++ /dev/null @@ -1,151 +0,0 @@ -package com.tangem.features.send.impl.presentation.ui.fee - -import androidx.annotation.DrawableRes -import androidx.annotation.StringRes -import androidx.compose.animation.* -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import com.tangem.blockchain.common.Amount -import com.tangem.blockchain.common.transaction.TransactionFee -import com.tangem.common.ui.amountScreen.utils.getFiatReference -import com.tangem.core.ui.components.RectangleShimmer -import com.tangem.core.ui.components.SpacerWMax -import com.tangem.core.ui.components.rows.SelectorRowItem -import com.tangem.core.ui.extensions.stringReference -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.utils.BigDecimalFormatter -import com.tangem.core.ui.utils.parseToBigDecimal -import com.tangem.features.send.impl.presentation.state.SendStates -import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState -import com.tangem.features.send.impl.presentation.state.fee.FeeType - -@Composable -internal fun SendSpeedSelectorItem( - @StringRes titleRes: Int, - @DrawableRes iconRes: Int, - feeType: FeeType, - state: SendStates.FeeState, - onSelect: () -> Unit, - modifier: Modifier = Modifier, -) { - val feeSelectorState = state.feeSelectorState - val content = feeSelectorState as? FeeSelectorState.Content - val amount = content?.getAmount(feeType) - val (showDivider, isVisible) = content.getDividerAndVisibility(feeType) - AnimatedVisibility( - visible = isVisible, - label = "Fee Selector Visibility Animation", - enter = expandVertically().plus(fadeIn()), - exit = shrinkVertically().plus(fadeOut()), - ) { - Box( - modifier = modifier - .fillMaxWidth() - .clickable { onSelect() }, - ) { - SelectorRowItem( - titleRes = titleRes, - iconRes = iconRes, - onSelect = onSelect, - modifier = modifier, - preDot = stringReference( - amount?.value.format { - crypto( - symbol = amount?.currencySymbol.orEmpty(), - decimals = amount?.decimals ?: 0, - ).fee(canBeLower = state.isFeeApproximate) - }, - ), - postDot = if (state.isFeeConvertibleToFiat) { - getFiatReference(amount?.value, state.rate, state.appCurrency) - } else { - null - }, - ellipsizeOffset = amount?.currencySymbol?.length, - isSelected = content?.selectedFee == feeType, - showDivider = showDivider, - ) - FeeLoading(feeSelectorState) - FeeError(feeSelectorState) - } - } -} - -@Composable -private fun FeeLoading(feeSelectorState: FeeSelectorState) { - Row { - SpacerWMax() - AnimatedVisibility( - visible = feeSelectorState == FeeSelectorState.Loading, - label = "Fee Loading State Change", - modifier = Modifier.align(Alignment.CenterVertically), - ) { - RectangleShimmer( - radius = TangemTheme.dimens.radius3, - modifier = Modifier - .padding( - vertical = TangemTheme.dimens.spacing18, - horizontal = TangemTheme.dimens.spacing12, - ) - .size( - height = TangemTheme.dimens.size12, - width = TangemTheme.dimens.size90, - ), - ) - } - } -} - -@Composable -private fun FeeError(feeSelectorState: FeeSelectorState) { - Row { - SpacerWMax() - AnimatedVisibility( - visible = feeSelectorState is FeeSelectorState.Error, - label = "Fee Error State Change", - modifier = Modifier.align(Alignment.CenterVertically), - ) { - Text( - text = BigDecimalFormatter.EMPTY_BALANCE_SIGN, - color = TangemTheme.colors.text.primary1, - style = TangemTheme.typography.body2, - modifier = Modifier - .padding( - vertical = TangemTheme.dimens.spacing14, - horizontal = TangemTheme.dimens.spacing12, - ), - ) - } - } -} - -private fun FeeSelectorState.Content.getAmount(feeType: FeeType): Amount? { - val choosableFees = fees as? TransactionFee.Choosable - val decimals = fees.normal.amount.decimals - val customValue = this.customValues.firstOrNull()?.value?.parseToBigDecimal(decimals) - val customAmount = fees.normal.amount.copy(value = customValue) - return when (feeType) { - FeeType.Slow -> choosableFees?.minimum?.amount - FeeType.Market -> fees.normal.amount - FeeType.Fast -> choosableFees?.priority?.amount - FeeType.Custom -> customAmount - } -} - -private fun FeeSelectorState.Content?.getDividerAndVisibility(feeType: FeeType): Pair { - val hasCustomValues = !this?.customValues.isNullOrEmpty() - val isNotSingle = this?.fees !is TransactionFee.Single - return when (feeType) { - FeeType.Slow -> true to isNotSingle - FeeType.Market -> (isNotSingle || hasCustomValues) to true - FeeType.Fast -> hasCustomValues to isNotSingle - FeeType.Custom -> false to hasCustomValues - } -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/ListItemWithIcon.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/ListItemWithIcon.kt deleted file mode 100644 index ec3e048834..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/ListItemWithIcon.kt +++ /dev/null @@ -1,244 +0,0 @@ -package com.tangem.features.send.impl.presentation.ui.recipient - -import android.content.res.Configuration -import androidx.annotation.DrawableRes -import androidx.compose.animation.AnimatedContent -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.togetherWith -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Icon -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider -import com.tangem.core.ui.components.CircleShimmer -import com.tangem.core.ui.components.RectangleShimmer -import com.tangem.core.ui.components.atoms.text.EllipsisText -import com.tangem.core.ui.components.atoms.text.TextEllipsis -import com.tangem.core.ui.components.icons.identicon.IdentIcon -import com.tangem.core.ui.extensions.rememberHapticFeedback -import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.core.ui.res.TangemTheme -import com.tangem.features.send.impl.R - -/** - * Row item with title and subtitle - * - * @param title title - * @param subtitle subtitle - * @param onClick click listener - * @param modifier modifier - * @param info info - * @param subtitleEndOffset offset for subtitle ellipsis - * @param subtitleIconRes icon - */ -@Composable -fun ListItemWithIcon( - title: String, - subtitle: String, - onClick: () -> Unit, - modifier: Modifier = Modifier, - info: String? = null, - subtitleEndOffset: Int = 0, - @DrawableRes subtitleIconRes: Int? = null, - isLoading: Boolean = false, -) { - AnimatedContent( - targetState = isLoading, - label = "Recent List Content Animation", - transitionSpec = { fadeIn().togetherWith(fadeOut()) }, - ) { isLoadingState -> - if (isLoadingState) { - ListItemLoading(modifier = modifier) - } else { - ListItemWithIcon( - title = title, - subtitle = subtitle, - onClick = onClick, - info = info, - subtitleEndOffset = subtitleEndOffset, - subtitleIconRes = subtitleIconRes, - modifier = modifier, - ) - } - } -} - -@Composable -private fun ListItemWithIcon( - title: String, - subtitle: String, - onClick: () -> Unit, - modifier: Modifier = Modifier, - info: String? = null, - subtitleEndOffset: Int = 0, - @DrawableRes subtitleIconRes: Int? = null, -) { - val hapticFeedback = rememberHapticFeedback(state = title, onAction = onClick) - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = modifier - .fillMaxWidth() - .clickable { hapticFeedback() } - .padding(horizontal = TangemTheme.dimens.spacing12), - ) { - IdentIcon( - address = title, - modifier = Modifier - .padding(vertical = TangemTheme.dimens.spacing8) - .size(TangemTheme.dimens.size40) - .clip(RoundedCornerShape(TangemTheme.dimens.radius20)), - ) - Column( - modifier = Modifier - .height(TangemTheme.dimens.size36) - .padding(start = TangemTheme.dimens.spacing12), - verticalArrangement = Arrangement.SpaceBetween, - ) { - EllipsisText( - text = title, - style = TangemTheme.typography.subtitle2, - color = TangemTheme.colors.text.primary1, - textAlign = TextAlign.Justify, - ellipsis = TextEllipsis.Middle, - modifier = Modifier, - ) - Row { - if (subtitleIconRes != null) { - Icon( - painter = painterResource(id = subtitleIconRes), - contentDescription = null, - tint = TangemTheme.colors.icon.informative, - modifier = Modifier - .padding(end = TangemTheme.dimens.spacing2) - .size(TangemTheme.dimens.size16) - .background(TangemTheme.colors.background.tertiary, CircleShape) - .padding(TangemTheme.dimens.spacing2), - ) - } - val (text, offset) = remember(subtitle, info) { - if (info != null) { - val suffix = ", $info" - subtitle + suffix to suffix.length + subtitleEndOffset - } else { - subtitle to 0 - } - } - EllipsisText( - text = text, - style = TangemTheme.typography.caption2, - color = TangemTheme.colors.text.tertiary, - ellipsis = TextEllipsis.OffsetEnd(offsetEnd = offset), - ) - } - } - } -} - -@Composable -private fun ListItemLoading(modifier: Modifier = Modifier) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = modifier - .fillMaxWidth() - .padding(horizontal = TangemTheme.dimens.spacing12), - ) { - CircleShimmer( - modifier = Modifier - .padding(vertical = TangemTheme.dimens.spacing8) - .size(TangemTheme.dimens.size40), - ) - Column( - modifier = Modifier - .height(TangemTheme.dimens.size36) - .padding(start = TangemTheme.dimens.spacing12), - verticalArrangement = Arrangement.SpaceBetween, - ) { - RectangleShimmer( - radius = TangemTheme.dimens.radius3, - modifier = Modifier.size( - width = TangemTheme.dimens.spacing70, - height = TangemTheme.dimens.spacing12, - ), - ) - RectangleShimmer( - radius = TangemTheme.dimens.radius3, - modifier = Modifier.size( - width = TangemTheme.dimens.spacing52, - height = TangemTheme.dimens.spacing12, - ), - ) - } - } -} - -// region preview -@Preview -@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -private fun ListItemWithIconPreview( - @PreviewParameter(ListItemWithIconPreviewProvider::class) config: ListItemWithIconPreviewConfig, -) { - TangemThemePreview { - ListItemWithIcon( - title = config.title, - subtitle = config.subtitle, - subtitleEndOffset = config.subtitleEndOffset, - subtitleIconRes = config.iconRes, - onClick = {}, - isLoading = config.isLoading, - ) - } -} - -private data class ListItemWithIconPreviewConfig( - val title: String, - val subtitle: String, - val info: String? = null, - val subtitleEndOffset: Int = 0, - val iconRes: Int? = null, - val isLoading: Boolean = false, -) - -private class ListItemWithIconPreviewProvider : CollectionPreviewParameterProvider( - collection = listOf( - ListItemWithIconPreviewConfig( - title = "0x34B4492A412D84A6E606288f3Bd714b89135D4dE", - subtitle = "0.000000000000000000000000000000 BTC", - info = "0.0.0000 at 00:00", - subtitleEndOffset = "BTC".length, - iconRes = R.drawable.ic_arrow_down_24, - ), - ListItemWithIconPreviewConfig( - title = "0x34B4492A412D84A6E606288f3Bd714b89135D4dE", - subtitle = "1 BTC", - info = "0.0.0000 at 00:00", - subtitleEndOffset = "BTC".length, - iconRes = R.drawable.ic_arrow_down_24, - ), - ListItemWithIconPreviewConfig( - title = "0x34B4492A412D84A6E606288f3Bd714b89135D4dE", - subtitle = "Wallet", - ), - ListItemWithIconPreviewConfig( - title = "0x34B4492A412D84A6E606288f3Bd714b89135D4dE", - subtitle = "0.000000000000000000000000000000 BTC", - info = "0.0.0000 at 00:00", - subtitleEndOffset = "BTC".length, - iconRes = R.drawable.ic_arrow_down_24, - isLoading = true, - ), - ), -) -//endregion \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt deleted file mode 100644 index d16ff2d06c..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt +++ /dev/null @@ -1,279 +0,0 @@ -package com.tangem.features.send.impl.presentation.ui.recipient - -import android.content.res.Configuration -import androidx.annotation.StringRes -import androidx.compose.animation.* -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.LazyListScope -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import com.tangem.core.ui.components.containers.FooterContainer -import com.tangem.core.ui.components.inputrow.InputRowRecipient -import com.tangem.core.ui.extensions.* -import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.features.send.impl.R -import com.tangem.features.send.impl.presentation.analytics.EnterAddressSource -import com.tangem.features.send.impl.presentation.domain.SendRecipientListContent -import com.tangem.features.send.impl.presentation.model.SendClickIntents -import com.tangem.features.send.impl.presentation.state.SendStates -import com.tangem.features.send.impl.presentation.state.fields.SendTextField -import com.tangem.features.send.impl.presentation.state.previewdata.RecipientStatePreviewData -import com.tangem.features.send.impl.presentation.state.previewdata.SendClickIntentsStub -import kotlinx.collections.immutable.ImmutableList - -private const val ADDRESS_FIELD_KEY = "ADDRESS_FIELD_KEY" -private const val MEMO_FIELD_KEY = "MEMO_FIELD_KEY" - -@Composable -internal fun SendRecipientContent( - uiState: SendStates.RecipientState?, - clickIntents: SendClickIntents, - isBalanceHidden: Boolean, -) { - if (uiState == null) return - val recipients = uiState.recent - val wallets = uiState.wallets - val memoField = uiState.memoTextField - val address = uiState.addressTextField - val isValidating by remember(uiState.isValidating) { derivedStateOf { uiState.isValidating } } - val isError by remember(address.isError) { derivedStateOf { address.isError } } - LazyColumn( - modifier = Modifier // Do not put fillMaxSize() in here - .background(TangemTheme.colors.background.tertiary) - .padding( - start = TangemTheme.dimens.spacing16, - end = TangemTheme.dimens.spacing16, - bottom = TangemTheme.dimens.spacing16, - ), - ) { - addressItem( - address = address, - network = uiState.network, - isError = isError, - isValidating = isValidating, - onAddressChange = clickIntents::onRecipientAddressValueChange, - ) - memoField( - memoField = memoField, - onMemoChange = { clickIntents.onRecipientMemoValueChange(it, true) }, - ) - listHeaderItem( - titleRes = R.string.send_recipient_wallets_title, - isVisible = wallets.isNotEmpty() && wallets.first().isVisible, - isFirst = true, - ) - listItem( - list = wallets, - clickIntents = clickIntents, - isLast = recipients.any { !it.isVisible }, - isBalanceHidden = isBalanceHidden, - type = EnterAddressSource.MyWallet, - ) - listHeaderItem( - titleRes = R.string.send_recent_transactions, - isVisible = recipients.isNotEmpty() && recipients.first().isVisible, - isFirst = wallets.any { !it.isVisible }, - ) - listItem( - list = recipients, - clickIntents = clickIntents, - isLast = true, - isBalanceHidden = isBalanceHidden, - type = EnterAddressSource.RecentAddress, - ) - } -} - -private fun LazyListScope.addressItem( - address: SendTextField.RecipientAddress, - network: String, - isError: Boolean, - isValidating: Boolean, - onAddressChange: (String, EnterAddressSource?) -> Unit, -) { - item(key = ADDRESS_FIELD_KEY) { - FooterContainer( - footer = resourceReference(R.string.send_recipient_address_footer, wrappedList(network)), - ) { - InputRowRecipient( - value = address.value, - title = address.label, - placeholder = address.placeholder, - onValueChange = address.onValueChange, - onPasteClick = { onAddressChange(it, EnterAddressSource.PasteButton) }, - isError = isError, - isLoading = isValidating, - error = address.error, - isValuePasted = address.isValuePasted, - modifier = Modifier - .background( - color = TangemTheme.colors.background.action, - shape = TangemTheme.shapes.roundedCornersXMedium, - ), - ) - } - } -} - -private fun LazyListScope.memoField(memoField: SendTextField.RecipientMemo?, onMemoChange: (String) -> Unit) { - if (memoField != null) { - item(key = MEMO_FIELD_KEY) { - val placeholder = if (memoField.isEnabled) memoField.placeholder else memoField.disabledText - TextFieldWithPaste( - value = memoField.value, - label = memoField.label, - placeholder = placeholder, - footer = resourceReference(R.string.send_recipient_memo_footer), - onValueChange = memoField.onValueChange, - onPasteClick = onMemoChange, - modifier = Modifier.padding(top = TangemTheme.dimens.spacing20), - labelStyle = TangemTheme.typography.subtitle2, - isError = memoField.isError, - error = memoField.error, - isReadOnly = !memoField.isEnabled, - isValuePasted = memoField.isValuePasted, - ) - } - } -} - -private fun LazyListScope.listHeaderItem(@StringRes titleRes: Int, isVisible: Boolean, isFirst: Boolean) { - item(key = titleRes) { - AnimateRecentAppearance(isVisible) { - val (topPadding, paddingFromTop) = if (isFirst) { - TangemTheme.dimens.spacing20 to TangemTheme.dimens.spacing12 - } else { - TangemTheme.dimens.spacing0 to TangemTheme.dimens.spacing8 - } - val topRadius = if (isFirst) { - TangemTheme.dimens.radius16 - } else { - TangemTheme.dimens.radius0 - } - Text( - text = stringResourceSafe(titleRes), - style = TangemTheme.typography.subtitle2, - color = TangemTheme.colors.text.tertiary, - modifier = Modifier - .fillMaxWidth() - .padding(top = topPadding) - .clip( - RoundedCornerShape( - topEnd = topRadius, - topStart = topRadius, - ), - ) - .background(TangemTheme.colors.background.action) - .padding( - top = paddingFromTop, - bottom = TangemTheme.dimens.spacing12, - start = TangemTheme.dimens.spacing12, - end = TangemTheme.dimens.spacing12, - ), - ) - } - } -} - -private fun LazyListScope.listItem( - list: ImmutableList, - clickIntents: SendClickIntents, - isLast: Boolean, - isBalanceHidden: Boolean, - type: EnterAddressSource, -) { - items( - count = list.size, - key = { list[it].id }, - contentType = { list[it]::class.java }, - ) { index -> - val item = list[index] - val title = item.title.resolveReference() - AnimateRecentAppearance(item.isVisible) { - ListItemWithIcon( - title = title, - subtitle = item.subtitle.orMaskWithStars(isBalanceHidden).resolveReference(), - info = item.timestamp?.resolveReference(), - subtitleEndOffset = item.subtitleEndOffset, - subtitleIconRes = item.subtitleIconRes, - onClick = { - clickIntents.onRecipientAddressValueChange(title, type) - }, - isLoading = item.isLoading, - modifier = Modifier - .then( - if (isLast && index == list.lastIndex) { - Modifier - .clip( - shape = RoundedCornerShape( - bottomStart = TangemTheme.dimens.radius16, - bottomEnd = TangemTheme.dimens.radius16, - ), - ) - } else { - Modifier - }, - ) - .background(TangemTheme.colors.background.action), - ) - } - } -} - -@Composable -private fun AnimateRecentAppearance(isVisible: Boolean, content: @Composable () -> Unit) { - AnimatedContent( - targetState = isVisible, - label = "Item Appearance Animation", - transitionSpec = { - (slideInHorizontally() + fadeIn()) - .togetherWith(slideOutVertically() + fadeOut()) - }, - ) { - if (it) { - content() - } else { - Box(modifier = Modifier.fillMaxWidth()) - } - } -} - -// region Preview -@Preview(showBackground = true, widthDp = 360) -@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -private fun SendRecipientContent_Preview( - @PreviewParameter(SendRecipientContentPreviewProvider::class) recipientState: SendStates.RecipientState, -) { - TangemThemePreview { - SendRecipientContent( - uiState = recipientState, - clickIntents = SendClickIntentsStub, - isBalanceHidden = false, - ) - } -} - -private class SendRecipientContentPreviewProvider : PreviewParameterProvider { - override val values: Sequence - get() = sequenceOf( - RecipientStatePreviewData.recipientWithRecentState, - RecipientStatePreviewData.recipientState, - RecipientStatePreviewData.recipientAddressState, - ) -} -// endregion \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFieldWithPaste.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFieldWithPaste.kt deleted file mode 100644 index cc33643eab..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFieldWithPaste.kt +++ /dev/null @@ -1,102 +0,0 @@ -package com.tangem.features.send.impl.presentation.ui.recipient - -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment.Companion.CenterEnd -import androidx.compose.ui.Alignment.Companion.CenterVertically -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.TextStyle -import com.tangem.core.ui.components.fields.SimpleTextField -import com.tangem.core.ui.components.inputrow.inner.CrossIcon -import com.tangem.core.ui.components.inputrow.inner.PasteButton -import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.resolveReference -import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.components.containers.FooterContainer - -@Composable -internal fun TextFieldWithPaste( - value: String, - placeholder: TextReference, - label: TextReference, - onValueChange: (String) -> Unit, - onPasteClick: (String) -> Unit, - modifier: Modifier = Modifier, - footer: TextReference? = null, - labelStyle: TextStyle = TangemTheme.typography.body2, - error: TextReference? = null, - isError: Boolean = false, - isReadOnly: Boolean = false, - isValuePasted: Boolean = false, -) { - val (title, color) = when { - isError && error != null -> error to TangemTheme.colors.text.warning - isReadOnly -> label to TangemTheme.colors.text.tertiary - else -> label to TangemTheme.colors.text.secondary - } - val placeholderColor = if (isReadOnly) TangemTheme.colors.text.tertiary else TangemTheme.colors.text.disabled - FooterContainer(modifier, footer) { - Box( - modifier = Modifier - .background( - color = TangemTheme.colors.background.action, - shape = TangemTheme.shapes.roundedCornersXMedium, - ) - .padding(end = TangemTheme.dimens.spacing12), - ) { - Row { - Column( - modifier = Modifier - .weight(1f) - .padding(TangemTheme.dimens.spacing12), - ) { - Text( - text = title.resolveReference(), - style = labelStyle, - color = color, - ) - SimpleTextField( - value = value, - placeholder = placeholder, - placeholderColor = placeholderColor, - onValueChange = onValueChange, - readOnly = isReadOnly, - isValuePasted = isValuePasted, - modifier = Modifier - .fillMaxWidth() - .padding(top = TangemTheme.dimens.spacing8), - ) - } - AnimatedVisibility( - visible = !isReadOnly, - label = "Animate read only status change", - enter = fadeIn(), - exit = fadeOut(), - modifier = Modifier - .align(CenterVertically), - ) { - CrossIcon( - onClick = onPasteClick, - ) - } - } - AnimatedVisibility( - visible = !isReadOnly, - label = "Animate read only status change", - enter = fadeIn(), - exit = fadeOut(), - modifier = Modifier.align(CenterEnd), - ) { - PasteButton( - isPasteButtonVisible = value.isBlank(), - onClick = onPasteClick, - ) - } - } - } -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt deleted file mode 100644 index 99c44da3df..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt +++ /dev/null @@ -1,149 +0,0 @@ -package com.tangem.features.send.impl.presentation.ui.send - -import android.content.res.Configuration -import androidx.compose.animation.AnimatedContent -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import com.tangem.common.ui.amountScreen.utils.getFiatReference -import com.tangem.core.ui.components.RectangleShimmer -import com.tangem.core.ui.components.rows.SelectorRowItem -import com.tangem.core.ui.extensions.stringReference -import com.tangem.core.ui.extensions.stringResourceSafe -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.core.ui.utils.BigDecimalFormatter -import com.tangem.features.send.impl.R -import com.tangem.features.send.impl.presentation.state.SendStates -import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState -import com.tangem.features.send.impl.presentation.state.fee.FeeType -import com.tangem.features.send.impl.presentation.state.previewdata.FeeStatePreviewData - -@Composable -internal fun FeeBlock(feeState: SendStates.FeeState, isClickDisabled: Boolean, onClick: () -> Unit) { - Column( - modifier = Modifier - .fillMaxWidth() - .clip(TangemTheme.shapes.roundedCornersXMedium) - .background(TangemTheme.colors.background.action) - .clickable(enabled = !isClickDisabled, onClick = onClick) - .padding(TangemTheme.dimens.spacing12), - ) { - Text( - text = stringResourceSafe(R.string.common_network_fee_title), - style = TangemTheme.typography.subtitle1, - color = TangemTheme.colors.text.secondary, - ) - - Box( - modifier = Modifier.padding(top = TangemTheme.dimens.spacing8), - ) { - val feeSelectorState = feeState.feeSelectorState - val feeAmount = feeState.fee?.amount - val (title, icon) = if (feeSelectorState is FeeSelectorState.Content) { - when (feeSelectorState.selectedFee) { - FeeType.Slow -> R.string.common_fee_selector_option_slow to R.drawable.ic_tortoise_24 - FeeType.Market -> R.string.common_fee_selector_option_market to R.drawable.ic_bird_24 - FeeType.Fast -> R.string.common_fee_selector_option_fast to R.drawable.ic_hare_24 - FeeType.Custom -> R.string.common_custom to R.drawable.ic_edit_24 - } - } else { - R.string.common_fee_selector_option_market to R.drawable.ic_bird_24 - } - SelectorRowItem( - titleRes = title, - iconRes = icon, - preDot = stringReference( - feeAmount?.value.format { - crypto( - symbol = feeAmount?.currencySymbol.orEmpty(), - decimals = feeAmount?.decimals ?: 0, - ).fee(canBeLower = feeState.isFeeApproximate) - }, - ), - postDot = if (feeState.isFeeConvertibleToFiat) { - getFiatReference(feeAmount?.value, feeState.rate, feeState.appCurrency) - } else { - null - }, - ellipsizeOffset = feeAmount?.currencySymbol?.length, - isSelected = true, - showDivider = false, - showSelectedAppearance = false, - paddingValues = PaddingValues(), - ) - FeeLoading(feeSelectorState) - FeeError(feeSelectorState) - } - } -} - -@Composable -private fun BoxScope.FeeLoading(feeSelectorState: FeeSelectorState) { - AnimatedContent( - targetState = feeSelectorState, - label = "Fee Loading State Change", - modifier = Modifier.align(Alignment.CenterEnd), - ) { - if (it == FeeSelectorState.Loading) { - RectangleShimmer( - radius = TangemTheme.dimens.radius3, - modifier = Modifier.size( - height = TangemTheme.dimens.size12, - width = TangemTheme.dimens.size90, - ), - ) - } - } -} - -@Composable -private fun BoxScope.FeeError(feeSelectorState: FeeSelectorState) { - AnimatedContent( - targetState = feeSelectorState, - label = "Fee Error State Change", - modifier = Modifier.align(Alignment.CenterEnd), - ) { - if (it is FeeSelectorState.Error) { - Text( - text = BigDecimalFormatter.EMPTY_BALANCE_SIGN, - color = TangemTheme.colors.text.primary1, - style = TangemTheme.typography.body2, - ) - } - } -} - -// region Preview -@Preview -@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -private fun FeeBlockPreview(@PreviewParameter(FeeBlockPreviewProvider::class) value: SendStates.FeeState) { - TangemThemePreview { - FeeBlock( - feeState = value, - isClickDisabled = true, - onClick = {}, - ) - } -} - -private class FeeBlockPreviewProvider : PreviewParameterProvider { - - override val values: Sequence - get() = sequenceOf( - FeeStatePreviewData.feeState, - ) -} -// endregion \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt deleted file mode 100644 index 9451e29e99..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt +++ /dev/null @@ -1,118 +0,0 @@ -package com.tangem.features.send.impl.presentation.ui.send - -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.RoundedCornerShape -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import com.tangem.core.ui.components.icons.identicon.IdentIcon -import com.tangem.core.ui.extensions.resolveReference -import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.core.ui.res.TangemTheme -import com.tangem.features.send.impl.presentation.state.SendStates -import com.tangem.features.send.impl.presentation.state.fields.SendTextField -import com.tangem.features.send.impl.presentation.state.previewdata.RecipientStatePreviewData - -@Composable -internal fun RecipientBlock( - recipientState: SendStates.RecipientState, - isClickDisabled: Boolean, - isEditingDisabled: Boolean, - onClick: () -> Unit, -) { - Column( - modifier = Modifier - .fillMaxWidth() - .clip(TangemTheme.shapes.roundedCornersXMedium) - .background(TangemTheme.colors.background.action) - .clickable(enabled = !isClickDisabled && !isEditingDisabled, onClick = onClick) - .padding(TangemTheme.dimens.spacing12), - ) { - AddressBlock(recipientState.addressTextField) - MemoBlock(recipientState.memoTextField) - } -} - -@Composable -private fun AddressBlock(address: SendTextField.RecipientAddress) { - Text( - text = address.label.resolveReference(), - style = TangemTheme.typography.subtitle2, - color = TangemTheme.colors.text.secondary, - ) - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), - modifier = Modifier.padding(top = TangemTheme.dimens.spacing8), - ) { - IdentIcon( - address = address.value, - modifier = Modifier - .size(TangemTheme.dimens.size36) - .clip(RoundedCornerShape(TangemTheme.dimens.radius18)) - .background(TangemTheme.colors.background.tertiary), - ) - Text( - text = address.value, - style = TangemTheme.typography.body2, - color = TangemTheme.colors.text.primary1, - ) - } -} - -@Composable -private fun MemoBlock(memo: SendTextField.RecipientMemo?) { - val showMemo = memo != null && memo.value.isNotBlank() - if (showMemo) { - HorizontalDivider( - color = TangemTheme.colors.icon.inactive, - modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing12), - ) - Text( - text = memo?.label?.resolveReference().orEmpty(), - style = TangemTheme.typography.caption2, - color = TangemTheme.colors.text.secondary, - ) - Text( - text = memo?.value.orEmpty(), - style = TangemTheme.typography.body2, - color = TangemTheme.colors.text.primary1, - modifier = Modifier.padding(top = TangemTheme.dimens.spacing8), - ) - } -} - -// region Preview -@Preview -@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -private fun RecipientBlockPreview( - @PreviewParameter(RecipientBlockPreviewProvider::class) value: SendStates.RecipientState, -) { - TangemThemePreview { - RecipientBlock( - recipientState = value, - isClickDisabled = true, - isEditingDisabled = false, - onClick = {}, - ) - } -} - -private class RecipientBlockPreviewProvider : PreviewParameterProvider { - - override val values: Sequence - get() = sequenceOf( - RecipientStatePreviewData.recipientState, - ) -} -// endregion \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt deleted file mode 100644 index a1484e4532..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt +++ /dev/null @@ -1,173 +0,0 @@ -package com.tangem.features.send.impl.presentation.ui.send - -import android.content.res.Configuration -import androidx.compose.animation.* -import androidx.compose.animation.core.MutableTransitionState -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.LazyListScope -import androidx.compose.material3.Icon -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import com.tangem.common.ui.amountScreen.ui.AmountBlock -import com.tangem.core.ui.components.transactions.TransactionDoneTitle -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.extensions.stringResourceSafe -import com.tangem.core.ui.extensions.wrappedList -import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.core.ui.utils.DateTimeFormatters -import com.tangem.core.ui.utils.toTimeFormat -import com.tangem.features.send.impl.R -import com.tangem.features.send.impl.presentation.state.SendUiState -import com.tangem.features.send.impl.presentation.state.previewdata.ConfirmStatePreviewData -import com.tangem.features.send.impl.presentation.state.previewdata.SendStatesPreviewData -import com.tangem.features.send.impl.presentation.ui.common.notifications -import kotlinx.coroutines.delay - -private const val TAP_HELP_KEY = "TAP_HELP_KEY" -private const val BLOCKS_KEY = "BLOCKS_KEY" -private const val TAP_HELP_ANIMATION_DELAY = 500L - -@Suppress("LongMethod") -@Composable -internal fun SendContent(uiState: SendUiState) { - val sendState = uiState.sendState ?: return - val isClickDisabled = sendState.isSending || sendState.isSuccess - LazyColumn( - modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16), - ) { - blocks(uiState) - tapHelp(isDisplay = sendState.showTapHelp) - notifications(notifications = sendState.notifications, isClickDisabled = isClickDisabled) - } -} - -private fun LazyListScope.blocks(uiState: SendUiState) { - val amountState = uiState.amountState - val recipientState = uiState.recipientState ?: return - val feeState = uiState.feeState ?: return - val sendState = uiState.sendState ?: return - val isSuccess = sendState.isSuccess - val isClickDisabled = sendState.isSending || isSuccess - val timestamp = sendState.transactionDate - - item(key = BLOCKS_KEY) { - Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12)) { - AnimatedVisibility( - visible = isSuccess, - modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing12), - ) { - TransactionDoneTitle( - title = resourceReference(R.string.sent_transaction_sent_title), - subtitle = resourceReference( - R.string.send_date_format, - wrappedList( - timestamp.toTimeFormat(DateTimeFormatters.dateFormatter), - timestamp.toTimeFormat(), - ), - ), - ) - } - RecipientBlock( - recipientState = recipientState, - isClickDisabled = isClickDisabled, - isEditingDisabled = uiState.isEditingDisabled, - onClick = uiState.clickIntents::showRecipient, - ) - AmountBlock( - amountState = amountState, - isClickDisabled = isClickDisabled, - isEditingDisabled = uiState.isEditingDisabled, - onClick = uiState.clickIntents::showAmount, - ) - FeeBlock( - feeState = feeState, - isClickDisabled = isClickDisabled, - onClick = uiState.clickIntents::showFee, - ) - } - } -} - -private fun LazyListScope.tapHelp(isDisplay: Boolean, modifier: Modifier = Modifier) { - item(key = TAP_HELP_KEY) { - val animationState = remember { MutableTransitionState(false) } - - LaunchedEffect(key1 = isDisplay) { - delay(TAP_HELP_ANIMATION_DELAY) - animationState.targetState = isDisplay - } - - AnimatedVisibility( - visibleState = animationState, - label = "Tap Help Animation", - enter = slideInVertically( - initialOffsetY = { it / 2 }, - ).plus(fadeIn()), - exit = slideOutVertically().plus(fadeOut()), - ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = modifier - .fillMaxWidth() - .animateItem(fadeInSpec = null, fadeOutSpec = null) - .padding(top = TangemTheme.dimens.spacing20), - ) { - val background = TangemTheme.colors.button.secondary - Icon( - painter = painterResource(id = R.drawable.send_hint_shape_12), - tint = TangemTheme.colors.button.secondary, - contentDescription = null, - modifier = Modifier, - ) - Text( - text = stringResourceSafe(id = R.string.send_summary_tap_hint), - style = TangemTheme.typography.body2, - color = TangemTheme.colors.text.secondary, - modifier = Modifier - .clip(TangemTheme.shapes.roundedCornersXMedium) - .background(background) - .padding( - horizontal = TangemTheme.dimens.spacing14, - vertical = TangemTheme.dimens.spacing12, - ), - ) - } - } - } -} - -// region Preview -@Preview(showBackground = true, widthDp = 360) -@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -private fun SendContent_Preview(@PreviewParameter(SendContentPreviewProvider::class) uiState: SendUiState) { - TangemThemePreview { - SendContent( - uiState = uiState, - ) - } -} - -private class SendContentPreviewProvider : PreviewParameterProvider { - override val values: Sequence - get() = sequenceOf( - SendStatesPreviewData.uiState, - SendStatesPreviewData.uiState.copy(sendState = ConfirmStatePreviewData.sendDoneState), - ) -} -// endregion \ No newline at end of file diff --git a/features/send/impl/src/main/res/drawable/send_hint_shape_12.xml b/features/send/impl/src/main/res/drawable/send_hint_shape_12.xml deleted file mode 100644 index 05cc9e9b5b..0000000000 --- a/features/send/impl/src/main/res/drawable/send_hint_shape_12.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/features/tokendetails/impl/build.gradle.kts b/features/tokendetails/impl/build.gradle.kts index 3515424d77..035f337fa2 100644 --- a/features/tokendetails/impl/build.gradle.kts +++ b/features/tokendetails/impl/build.gradle.kts @@ -96,7 +96,6 @@ dependencies { /** Feature Apis */ implementation(projects.features.tokendetails.api) - implementation(projects.features.send.api) implementation(projects.features.staking.api) implementation(projects.features.markets.api) implementation(projects.features.onramp.api) diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts index fc42f54104..03c5af01a5 100644 --- a/features/wallet/impl/build.gradle.kts +++ b/features/wallet/impl/build.gradle.kts @@ -103,7 +103,6 @@ dependencies { implementation(projects.features.onboardingV2.api) implementation(projects.features.onramp.api) implementation(projects.features.pushNotifications.api) - implementation(projects.features.send.api) implementation(projects.features.swap.api) implementation(projects.features.tester.api) implementation(projects.features.tokendetails.api) diff --git a/settings.gradle.kts b/settings.gradle.kts index 40453f033b..d096b06988 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -195,8 +195,6 @@ include(":features:wallet:impl") include(":features:tokendetails:api") include(":features:tokendetails:impl") -include(":features:send:api") -include(":features:send:impl") include(":features:send-v2:api") include(":features:send-v2:impl")