diff --git a/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt index 084134986d..7a05bef47d 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt @@ -8,6 +8,8 @@ import com.tangem.domain.demo.DemoConfig import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.wallets.legacy.WalletsStateHolder import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase +import com.tangem.tap.domain.TangemSdkManager +import com.tangem.tap.domain.card.DefaultDeleteSavedAccessCodesUseCase import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -77,4 +79,10 @@ internal object CardDomainModule { ): GetExtendedPublicKeyForCurrencyUseCase { return GetExtendedPublicKeyForCurrencyUseCase(derivationsRepository) } + + @Provides + @ViewModelScoped + fun provideDeleteSavedAccessCodesUseCase(tangemSdkManager: TangemSdkManager): DeleteSavedAccessCodesUseCase { + return DefaultDeleteSavedAccessCodesUseCase(tangemSdkManager) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index 81d84db2d2..662e68f7bb 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -351,4 +351,12 @@ internal object TokensDomainModule { ): RunPolkadotAccountHealthCheckUseCase { return RunPolkadotAccountHealthCheckUseCase(repository) } + + @Provides + @ViewModelScoped + fun provideGetNetworkStatusesUseCase(networksRepository: NetworksRepository): GetNetworkAddressesUseCase { + return GetNetworkAddressesUseCase( + networksRepository = networksRepository, + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/card/DefaultDeleteSavedAccessCodesUseCase.kt b/app/src/main/java/com/tangem/tap/domain/card/DefaultDeleteSavedAccessCodesUseCase.kt new file mode 100644 index 0000000000..b0fadb5bcf --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/card/DefaultDeleteSavedAccessCodesUseCase.kt @@ -0,0 +1,22 @@ +package com.tangem.tap.domain.card + +import arrow.core.Either +import arrow.core.left +import arrow.core.right +import com.tangem.common.doOnFailure +import com.tangem.common.doOnSuccess +import com.tangem.domain.card.DeleteSavedAccessCodesUseCase +import com.tangem.tap.domain.TangemSdkManager + +internal class DefaultDeleteSavedAccessCodesUseCase( + private val tangemSdkManager: TangemSdkManager, +) : DeleteSavedAccessCodesUseCase { + + override suspend fun invoke(cardId: String): Either { + tangemSdkManager.deleteSavedUserCodes(setOf(cardId)) + .doOnFailure { return it.left() } + .doOnSuccess { return Unit.right() } + + return Unit.right() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DelegatedKeystoreManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DelegatedKeystoreManager.kt index 7e7a781189..3db56b3dcb 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DelegatedKeystoreManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DelegatedKeystoreManager.kt @@ -8,15 +8,20 @@ internal class DelegatedKeystoreManager( private val keystoreManagerProvider: Provider, ) : KeystoreManager { - override suspend fun get(masterKeyConfig: KeystoreManager.MasterKeyConfig, keyAlias: String): SecretKey? { - return keystoreManagerProvider().get(masterKeyConfig, keyAlias) + override suspend fun get( + masterKeyConfig: KeystoreManager.MasterKeyConfig, + keyAlias: String, + forceAuthentication: Boolean, + ): SecretKey? { + return keystoreManagerProvider().get(masterKeyConfig, keyAlias, forceAuthentication) } override suspend fun get( masterKeyConfig: KeystoreManager.MasterKeyConfig, keyAliases: Set, + forceAuthentication: Boolean, ): Map { - return keystoreManagerProvider().get(masterKeyConfig, keyAliases) + return keystoreManagerProvider().get(masterKeyConfig, keyAliases, forceAuthentication) } override suspend fun store(masterKeyConfig: KeystoreManager.MasterKeyConfig, keyAlias: String, key: SecretKey) { diff --git a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt index 6f5d289dd9..d61c7ffebf 100644 --- a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt @@ -11,6 +11,7 @@ import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.balancehiding.ListenToFlipsUseCase import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase import com.tangem.domain.settings.DeleteDeprecatedLogsUseCase +import com.tangem.features.send.api.featuretoggles.SendFeatureToggles import com.tangem.tap.features.main.model.MainScreenState import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.hilt.android.lifecycle.HiltViewModel @@ -26,6 +27,7 @@ internal class MainViewModel @Inject constructor( private val reduxNavController: ReduxNavController, private val fetchAppCurrenciesUseCase: FetchAppCurrenciesUseCase, private val deleteDeprecatedLogsUseCase: DeleteDeprecatedLogsUseCase, + private val sendFeatureToggles: SendFeatureToggles, private val dispatchers: CoroutineDispatcherProvider, getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, ) : ViewModel(), MainIntents { @@ -41,6 +43,7 @@ internal class MainViewModel @Inject constructor( init { updateAppCurrencies() + updateSendFeatureToggle() observeFlips() displayBalancesHidingStatusToast() displayHiddenBalancesModalNotification() @@ -56,6 +59,12 @@ internal class MainViewModel @Inject constructor( } } + private fun updateSendFeatureToggle() { + viewModelScope.launch(dispatchers.main) { + sendFeatureToggles.fetchNewSendEnabled() + } + } + private fun observeFlips() { listenToFlipsUseCase().launchIn(viewModelScope) } diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt index a20223d2cb..80517d7175 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt @@ -97,10 +97,11 @@ sealed class AnalyticsParam { } sealed class FeeType(val value: String) { - object Fixed : FeeType("Fixed") - object Min : FeeType("Min") - object Normal : FeeType("Normal") - object Max : FeeType("Max") + data object Fixed : FeeType("Fixed") + data object Min : FeeType("Min") + data object Normal : FeeType("Normal") + data object Max : FeeType("Max") + data object Custom : FeeType("Custom") companion object { fun fromString(feeType: String): FeeType { diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt index 63e632438e..38e872886c 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt @@ -82,4 +82,7 @@ interface TangemTechApi { @Header("card_id") cardId: String, @Body body: CreateUserNetworkAccountBody, ): ApiResponse + + @GET("features") + suspend fun getFeatures(): ApiResponse } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/FeaturesResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/FeaturesResponse.kt new file mode 100644 index 0000000000..2d72021cd0 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/FeaturesResponse.kt @@ -0,0 +1,7 @@ +package com.tangem.datasource.api.tangemTech.models + +import com.squareup.moshi.Json + +data class FeaturesResponse( + @Json(name = "send") val isNewSendEnabled: Boolean, +) \ No newline at end of file diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index 629a8b5e9b..244c40eadf 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -17,7 +17,7 @@ }, { "name": "LOCAL_USER_LOGS_ENABLED", - "version": "5.10.0" + "version": "5.11.0" }, { "name": "GENERATE_XPUB_ENABLED", diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 931a4aff2e..c10f726779 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -102,6 +102,7 @@ Заблокирован Основная сеть Сетевая комиссия + Сумма отправки будет уменьшена на %1$s (%2$s) для покрытия выбранного уровня комиссии Далее Нет Нет адреса @@ -448,11 +449,13 @@ Причина: %1$s\nКод: %2$s Транзакция не выполнена Сумма + Вы можете установить комиссию за транзакцию, изменив значение в поле Satoshi per vByte. %1$s, %2$s Адрес Код назначения Введите адрес Адрес совпадает с адресом кошелька + Комиссия, которая будет взята за вашу транзакцию. Вы можете выставить своё собственное значение. Недопустимый Tag. Он не будет добавлен в транзакцию. Недопустимый Memo. Он не будет добавлен в транзакцию. Tag @@ -477,12 +480,12 @@ Покрытие сетевой комиссии Недостаточно средств для перевода, так как сумма комиссии и сумма перевода в совокупности больше имеющегося баланса Недостаточно средств - Оставить %s + Оставить %s Аккаунт будет удален из блокчейна, если баланс упадет ниже экзистенциального депозита. Пожалуйста, оставьте %s на балансе. Экзистенциальный депозит Сумма комиссии в %s раз превышает рекомендованную. Убедитесь, что указанная комиссия верна. Установлена высокая комиссия - Ввиду особенности сети Tezos комиссия при переводе всего баланса выше. Для того, чтобы снизить комиссию Вы можете оставить %s. + Ввиду особенности сети %1$s комиссия при переводе всего баланса выше. Для того, чтобы снизить комиссию Вы можете оставить %2$s. Комиссия повышена Включенная комиссия превышает сумму перевода, что приводит к отрицательному значению Недопустимая сумма @@ -490,7 +493,6 @@ Адрес получателя не активирован. \nПожалуйста, измените сумму отправки, чтобы продолжить. Сумма отправки не может быть менее %s Уменьшить на %s - Уменьшить до %s Обратите внимание, что при определенных параметрах комиссии возможны задержки по вашей транзакции Возможны задержки по транзакции Из-за ограничений %1$s в одну транзакцию может поместиться только %2$s UTXO. Это означает, что вы можете отправить только %3$s или меньше. Вам нужно уменьшить сумму. @@ -500,7 +502,7 @@ Последние Получатель Неверный адрес - Убедитесь, что вы отправляете средства на адрес кошелька %s. Ошибки могут привести к потере ваших токенов + Убедитесь, что вы отправляете средства на адрес кошелька %s. Ошибки могут привести к потере ваших токенов. Отправить Мемо/ Код назначения - это код, разделяющий транзакции к общему получателю в сети криптовалют. Внимание: отсутствие мемо может привести к потере средств. Мои кошельки @@ -509,7 +511,7 @@ Отправка Нажмите на любое поле, чтобы изменить его Отправка %s - Вы отправляете %1$s, включая комиссию сети %2$s + Вы отправляете **%1$s**, включая комиссию сети %2$s Отправка %s Всего %1$s и %2$s будет отправлено @@ -643,6 +645,8 @@ Используйте %s или отсканируйте карту, чтобы разблокировать доступ к вашему кошельку Пожалуйста, выведите все средства из этого кошелька, сбросьте его к заводским настройкам и создайте новый. Доступ к текущему кошельку будет утерян. Ошибка активации + По решению разработчиков сети BNB, стандарт BEP-2 перестанет поддерживаться в июне 2024 года. Чтобы не потерять свои активы, их необходимо преобразовать в стандарт BEP-20. Используйте функцию обмена в приложении чтобы перевести их в cеть BNB Smart Chain. + Отключение сети BNB Beacon Chain Можно лучше Нравится Понятно! diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index f0fc91c000..b08722ddd9 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -101,6 +101,7 @@ Locked Main network Network fee + Amount sent will be reduced by %1$s (%2$s) to cover the selected fee level Next No No address @@ -445,11 +446,13 @@ Amount Base fee Represents the part of the transaction fee that goes to the miner + You can set your transaction fee by adjusting the value in the Satoshi per vByte field. %1$s, %2$s Address Destination Tag Enter address Address is the same as wallet address + The fee that will be charged for your transaction. You can set your own value. Invalid Tag. It won\'t be added to the transaction. Invalid Memo. It won\'t be added to the transaction. Tag @@ -474,20 +477,19 @@ Network fee coverage Insufficient funds for the transfer, as the total of the fee and transfer amount exceeds the existing balance Total exceeds balance - Leave %s The account will be wiped from the blockchain if a balance goes below the existential deposit. Please leave %s on your balance. Existential deposit The commission amount is %s times the recommended amount. Make sure that the custom settings are correct. Custom fee is high - Due to the peculiarities of the Tezos network, the fee for transferring the entire balance is higher. To reduce the commission, you can leave %s. + Due to the peculiarities of the %1$s network, the fee for transferring the entire balance is higher. To reduce the commission, you can leave %2$s. The fee is higher The included commission exceeds the transfer amount, leading to a negative value Invalid amount The minimum sending amount is %1$s. Please ensure that the remaining balance after sending will not be less than %2$s. Target account is not created. Please change the amount to send. The amount to send must be at least %s + Leave %s Reduce by %s - Reduce to %s Kindly be aware that your transaction may experience delays under specific fee settings Transaction delays are possible Due to %1$s limitations only %2$s UTXOs can fit in a single transaction. This means you can only send %3$s or less. You need to reduce the amount. @@ -499,7 +501,7 @@ Recent Recipient Not a valid address - Ensure that you are sending funds to an %s wallet address. Errors may result in the loss of your tokens + Ensure the receiving wallet address is on the %s network to avoid losing your tokens Send to A Memo/Destination Tag is a unique ID for differentiating transactions sent to the same recipient on the same network. Caution: Omitting a memo may lead to misplaced funds My wallets @@ -508,7 +510,7 @@ Sending... Tap any field to change it Send %s - You are sending %1$s including a network fee of %2$s + You are sending **%1$s** including a network fee of %2$s Sending %s Total %1$s and %2$s will be sent @@ -643,6 +645,8 @@ Use %s or scan a card to unlock access to your wallet Please withdraw all funds from this wallet, reset it to factory settings, and create a new one. Access to the current wallet will be lost. Activation error + According to BNB network developers, support for the BEP-2 standard will end in June 2024. To avoid losing assets with this standard, please convert them to the BEP-20 standard. Use our swap service to transfer them to the BNB Smart Chain network. + BNB Beacon Chain will shut down Could be better Like it Ok, Got it! diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index e2abfa0f4b..3129c8917b 100644 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -45,4 +45,5 @@ dependencies { implementation(deps.zxing.qrCore) implementation(deps.jodatime) implementation(deps.timber) + implementation(deps.markdown) } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountTextField.kt b/core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountTextField.kt index c095c2b8fd..521245b940 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountTextField.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountTextField.kt @@ -94,10 +94,14 @@ fun AmountTextField( SimpleTextField( value = value, onValueChange = { newText -> - if (decimalFormat.isValidSymbols(newText)) { - val trimmed = decimalFormat.getValidatedNumberWithFixedDecimals(newText, decimals) - onValueChange(trimmed) - } + onValueChange( + prepareEnter( + oldValue = value, + newValue = newText, + decimalFormat = decimalFormat, + decimals = decimals, + ), + ) }, textStyle = textStyle.copy( fontSize = fontSize, @@ -116,8 +120,37 @@ fun AmountTextField( } } +private fun prepareEnter(oldValue: String, newValue: String, decimalFormat: DecimalFormat, decimals: Int): String { + val decimalSymbol = decimalFormat.decimalFormatSymbols.decimalSeparator + return if (decimalFormat.isValidSymbols(newValue)) { + val parsedValue = newValue.parseBigDecimalOrNull()?.toPlainString() + ?: if (newValue.isBlank()) "" else oldValue + val replacedWithSymbol = if (parsedValue.findLast { it != decimalSymbol } != null) { + when { + parsedValue.findLast { it == COMMA_SEPARATOR } != null -> { + parsedValue.replace(COMMA_SEPARATOR, decimalSymbol) + } + parsedValue.findLast { it == POINT_SEPARATOR } != null -> { + parsedValue.replace(POINT_SEPARATOR, decimalSymbol) + } + else -> parsedValue + } + } else { + parsedValue + } + val joinedSymbol = if (newValue.endsWith(COMMA_SEPARATOR) || newValue.endsWith(POINT_SEPARATOR)) { + replacedWithSymbol.plus(decimalSymbol) + } else { + replacedWithSymbol + } + decimalFormat.getValidatedNumberWithFixedDecimals(joinedSymbol, decimals) + } else { + oldValue + } +} + private fun DecimalFormat.isValidSymbols(text: String): Boolean { - return checkDecimalSeparatorDuplicate(text) && checkGroupingSeparator(text) + return checkDecimalSeparatorDuplicate(text) } // region preview diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt b/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt index ff2bf968ae..59128a93a9 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt @@ -38,6 +38,7 @@ fun SimpleTextField( keyboardActions: KeyboardActions = KeyboardActions.Default, color: Color = TangemTheme.colors.text.primary1, textStyle: TextStyle = TangemTheme.typography.body2.copy(color = color), + placeholderColor: Color = TangemTheme.colors.text.disabled, readOnly: Boolean = false, isValuePasted: Boolean = false, onValuePastedTriggerDismiss: () -> Unit = {}, @@ -108,6 +109,7 @@ fun SimpleTextField( value = value, textStyle = textStyle, textValue = textValue, + color = placeholderColor, ) }, modifier = modifier @@ -122,6 +124,7 @@ private fun SimpleTextPlaceholder( value: String, textStyle: TextStyle, textValue: @Composable () -> Unit, + color: Color = TangemTheme.colors.text.disabled, ) { Box { if (value.isBlank() && placeholder != null) { @@ -132,7 +135,7 @@ private fun SimpleTextPlaceholder( Text( text = it.resolveReference(), style = textStyle, - color = TangemTheme.colors.text.disabled, + color = color, ) } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt index c352cf41eb..4a71c9c99c 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt @@ -49,12 +49,14 @@ fun Notification( modifier: Modifier = Modifier, containerColor: Color? = null, iconTint: Color? = null, + isEnabled: Boolean = true, ) { BaseContainer( buttonsState = config.buttonsState, onClick = config.onClick, modifier = modifier, containerColor = containerColor, + isEnabled = isEnabled, ) { Column( modifier = Modifier.padding(all = TangemTheme.dimens.spacing12), @@ -65,15 +67,16 @@ fun Notification( iconTint = iconTint, title = config.title, subtitle = config.subtitle, - isClickableComponent = config.onClick != null, + isClickableComponent = isEnabled && config.onClick != null, ) - Buttons(state = config.buttonsState) + Buttons(state = config.buttonsState, isEnabled = isEnabled) } CloseableIconButton( onClick = config.onCloseClick, modifier = Modifier.align(alignment = Alignment.TopEnd), + isEnabled = isEnabled, ) } } @@ -83,6 +86,7 @@ private fun BaseContainer( buttonsState: NotificationConfig.ButtonsState?, onClick: (() -> Unit)?, modifier: Modifier = Modifier, + isEnabled: Boolean = true, containerColor: Color? = null, content: @Composable BoxScope.() -> Unit, ) { @@ -99,7 +103,7 @@ private fun BaseContainer( modifier = modifier .defaultMinSize(minHeight = TangemTheme.dimens.size62) .fillMaxWidth(), - enabled = onClick != null, + enabled = onClick != null && isEnabled, shape = TangemTheme.shapes.roundedCornersXMedium, color = containerColor ?: tempContainerColor, ) { @@ -177,27 +181,31 @@ private fun TextsBlock(title: TextReference, subtitle: TextReference) { } @Composable -private fun Buttons(state: NotificationButtonsState?) { +private fun Buttons(state: NotificationButtonsState?, isEnabled: Boolean = true) { when (state) { - is NotificationButtonsState.SecondaryButtonConfig -> SingleSecondaryButton(config = state) - is NotificationButtonsState.PrimaryButtonConfig -> SinglePrimaryButton(config = state) - is NotificationButtonsState.PairButtonsConfig -> PairButtons(config = state) + is NotificationButtonsState.SecondaryButtonConfig -> SingleSecondaryButton( + config = state, + isEnabled = isEnabled, + ) + is NotificationButtonsState.PrimaryButtonConfig -> SinglePrimaryButton(config = state, isEnabled = isEnabled) + is NotificationButtonsState.PairButtonsConfig -> PairButtons(config = state, isEnabled = isEnabled) null -> Unit } } @Composable -private fun SingleSecondaryButton(config: NotificationButtonsState.SecondaryButtonConfig) { +private fun SingleSecondaryButton(config: NotificationButtonsState.SecondaryButtonConfig, isEnabled: Boolean = true) { SecondaryButton( text = config.text.resolveReference(), onClick = config.onClick, modifier = Modifier.fillMaxWidth(), size = TangemButtonSize.WideAction, + enabled = isEnabled, ) } @Composable -private fun SinglePrimaryButton(config: NotificationButtonsState.PrimaryButtonConfig) { +private fun SinglePrimaryButton(config: NotificationButtonsState.PrimaryButtonConfig, isEnabled: Boolean = true) { if (config.iconResId != null) { PrimaryButtonIconEnd( text = config.text.resolveReference(), @@ -205,6 +213,7 @@ private fun SinglePrimaryButton(config: NotificationButtonsState.PrimaryButtonCo onClick = config.onClick, modifier = Modifier.fillMaxWidth(), size = TangemButtonSize.WideAction, + enabled = isEnabled, ) } else { PrimaryButton( @@ -212,18 +221,20 @@ private fun SinglePrimaryButton(config: NotificationButtonsState.PrimaryButtonCo onClick = config.onClick, modifier = Modifier.fillMaxWidth(), size = TangemButtonSize.WideAction, + enabled = isEnabled, ) } } @Composable -private fun PairButtons(config: NotificationButtonsState.PairButtonsConfig) { +private fun PairButtons(config: NotificationButtonsState.PairButtonsConfig, isEnabled: Boolean = true) { Row(horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing8)) { SecondaryButton( text = config.secondaryText.resolveReference(), onClick = config.onSecondaryClick, modifier = Modifier.weight(weight = 1f), size = TangemButtonSize.WideAction, + enabled = isEnabled, ) PrimaryButton( @@ -231,12 +242,13 @@ private fun PairButtons(config: NotificationButtonsState.PairButtonsConfig) { onClick = config.onPrimaryClick, modifier = Modifier.weight(weight = 1f), size = TangemButtonSize.WideAction, + enabled = isEnabled, ) } } @Composable -private fun CloseableIconButton(onClick: (() -> Unit)?, modifier: Modifier = Modifier) { +private fun CloseableIconButton(onClick: (() -> Unit)?, modifier: Modifier = Modifier, isEnabled: Boolean = true) { AnimatedVisibility(visible = onClick != null, modifier = modifier) { onClick ?: return@AnimatedVisibility @@ -253,6 +265,7 @@ private fun CloseableIconButton(onClick: (() -> Unit)?, modifier: Modifier = Mod interactionSource = remember { MutableInteractionSource() }, indication = LocalIndication.current, role = Role.Button, + enabled = isEnabled, onClick = onClick, ), ) { diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/MarkdownExtension.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/MarkdownExtension.kt new file mode 100644 index 0000000000..408e9130ee --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/MarkdownExtension.kt @@ -0,0 +1,56 @@ +package com.tangem.core.ui.extensions + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.withStyle +import org.intellij.markdown.MarkdownElementTypes +import org.intellij.markdown.ast.ASTNode +import org.intellij.markdown.ast.getTextInNode +import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor +import org.intellij.markdown.parser.MarkdownParser + +/** Markdown parser */ +@Composable +fun rememberMarkdownParser() = remember { + MarkdownParser(CommonMarkFlavourDescriptor()) +} + +/** + * Styling markdown tree recursively + * + * @param markdownText original text + * @param node current processed node + */ +@Composable +fun AnnotatedString.Builder.appendMarkdown(markdownText: String, node: ASTNode): AnnotatedString.Builder { + when (node.type) { + MarkdownElementTypes.MARKDOWN_FILE, MarkdownElementTypes.PARAGRAPH -> { + node.children.forEach { childNode -> + appendMarkdown( + markdownText = markdownText, + node = childNode, + ) + } + } + MarkdownElementTypes.STRONG -> { + withStyle(SpanStyle(fontWeight = FontWeight.Medium)) { + node.children + .drop(2) + .dropLast(2) + .forEach { childNode -> + appendMarkdown( + markdownText = markdownText, + node = childNode, + ) + } + } + } + else -> { + append(node.getTextInNode(markdownText).toString()) + } + } + return this +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt index 766815d328..2f22994f64 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt @@ -8,6 +8,9 @@ import androidx.compose.runtime.Immutable import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.buildAnnotatedString +import org.intellij.markdown.MarkdownElementTypes /** * Utility class for creating text as [String] or [StringRes]. @@ -160,6 +163,29 @@ fun TextReference.resolveReference(resources: Resources): String { } } +/** Resolve [TextReference] as [AnnotatedString] */ +@Composable +fun TextReference.resolveAnnotatedReference(): AnnotatedString { + return when (this) { + is TextReference.Res -> { + val args = formatArgs + .map { if (it is TextReference) it.resolveReference() else it } + .toTypedArray() + + formatAnnotated(stringResource(id = id, *args)) + } + is TextReference.PluralRes -> formatAnnotated( + pluralStringResource(id, count, *formatArgs.toTypedArray()), + ) + is TextReference.Str -> formatAnnotated(value) + is TextReference.Combined -> buildAnnotatedString { + refs.forEach { + append(formatAnnotated(it.resolveReference())) + } + } + } +} + /** Concatenate [this] reference with [ref] */ operator fun TextReference.plus(ref: TextReference): TextReference { return when (this) { @@ -169,4 +195,14 @@ operator fun TextReference.plus(ref: TextReference): TextReference { is TextReference.Str, -> TextReference.Combined(refs = wrappedList(this, ref)) } +} + +@Composable +private fun formatAnnotated(rawString: String): AnnotatedString { + val markdownDescriptor = rememberMarkdownParser() + val parsedTree = markdownDescriptor.parse(MarkdownElementTypes.MARKDOWN_FILE, rawString, true) + + return buildAnnotatedString { + appendMarkdown(markdownText = rawString, node = parsedTree) + } } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/DecimalFormatterExt.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/DecimalFormatterExt.kt index 5006df1d7a..2d685763e3 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/DecimalFormatterExt.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/DecimalFormatterExt.kt @@ -10,9 +10,9 @@ import java.text.DecimalFormatSymbols import java.util.Locale private const val TEXT_CHUNK_THOUSAND = 3 -private const val POINT_SEPARATOR = '.' -private const val COMMA_SEPARATOR = ',' private const val SCIENTIFIC_NOTATION = 'e' +const val POINT_SEPARATOR = '.' +const val COMMA_SEPARATOR = ',' const val DECIMAL_SEPARATOR_LIMIT = 1 @Composable diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/DeleteSavedAccessCodesUseCase.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/DeleteSavedAccessCodesUseCase.kt new file mode 100644 index 0000000000..dc118e8f15 --- /dev/null +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/DeleteSavedAccessCodesUseCase.kt @@ -0,0 +1,8 @@ +package com.tangem.domain.card + +import arrow.core.Either + +interface DeleteSavedAccessCodesUseCase { + + suspend operator fun invoke(cardId: String): Either +} \ No newline at end of file diff --git a/domain/tokens/build.gradle.kts b/domain/tokens/build.gradle.kts index 5d91ab4124..3e0bb9743c 100644 --- a/domain/tokens/build.gradle.kts +++ b/domain/tokens/build.gradle.kts @@ -25,6 +25,7 @@ dependencies { /** Project - Other */ implementation(projects.core.utils) + implementation(projects.libs.crypto) /** Android - Other */ implementation(deps.androidx.paging.runtime) diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt index 6e477d8a10..36d186b41d 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt @@ -47,4 +47,6 @@ sealed class CryptoCurrencyWarning { val startDateTime: DateTime, val endDateTime: DateTime, ) : CryptoCurrencyWarning() + + data object BeaconChainShutdown : CryptoCurrencyWarning() } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt index 91430442e1..5b3bf0649d 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt @@ -12,6 +12,7 @@ import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.swap.domain.api.SwapRepository import com.tangem.feature.swap.domain.models.domain.LeastTokenInfo +import com.tangem.lib.crypto.BlockchainUtils import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.runCatching import com.tangem.utils.isNullOrZero @@ -74,6 +75,7 @@ class GetCurrencyWarningsUseCase( *coinRelatedWarnings.toTypedArray(), getNetworkUnavailableWarning(currencyStatus), getNetworkNoAccountWarning(currencyStatus), + getBeaconChainShutdownWarning(currency.network.id), ) }.flowOn(dispatchers.io) } @@ -264,6 +266,10 @@ class GetCurrencyWarningsUseCase( } } + private fun getBeaconChainShutdownWarning(networkId: Network.ID): CryptoCurrencyWarning.BeaconChainShutdown? { + return if (BlockchainUtils.isBeaconChain(networkId.value)) CryptoCurrencyWarning.BeaconChainShutdown else null + } + private fun BigDecimal?.isZero(): Boolean { return this?.signum() == 0 } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetNetworkAddressesUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetNetworkAddressesUseCase.kt new file mode 100644 index 0000000000..0cc768a333 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetNetworkAddressesUseCase.kt @@ -0,0 +1,24 @@ +package com.tangem.domain.tokens + +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.tokens.model.NetworkStatus +import com.tangem.domain.tokens.repository.NetworksRepository +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +class GetNetworkAddressesUseCase( + internal val networksRepository: NetworksRepository, +) { + + operator fun invoke(userWalletId: UserWalletId, network: Network): Flow = + networksRepository.getNetworkStatusesUpdates(userWalletId, setOf(network)) + .map { networkStatuses -> + when (val networkStatus = networkStatuses.singleOrNull { it.network.id == network.id }?.value) { + is NetworkStatus.NoAccount -> networkStatus.address.defaultAddress.value + is NetworkStatus.Unreachable -> networkStatus.address?.defaultAddress?.value.orEmpty() + is NetworkStatus.Verified -> networkStatus.address.defaultAddress.value + else -> "" + } + } +} \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetWalletsUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetWalletsUseCase.kt index 3c589f1bcd..e40e284270 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetWalletsUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetWalletsUseCase.kt @@ -3,6 +3,7 @@ package com.tangem.domain.wallets.usecase import com.tangem.domain.wallets.legacy.WalletsStateHolder import com.tangem.domain.wallets.models.UserWallet import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.firstOrNull /** * Use case for getting list of user wallets @@ -14,7 +15,9 @@ import kotlinx.coroutines.flow.Flow class GetWalletsUseCase(private val walletsStateHolder: WalletsStateHolder) { @Throws(IllegalArgumentException::class) - operator fun invoke(): Flow> { - return requireNotNull(walletsStateHolder.userWalletsListManager).userWallets - } + operator fun invoke(): Flow> = + requireNotNull(walletsStateHolder.userWalletsListManager).userWallets + + @Throws(IllegalArgumentException::class) + suspend fun invokeSync(): List? = walletsStateHolder.userWalletsListManager?.userWallets?.firstOrNull() } \ No newline at end of file diff --git a/features/send/api/src/main/kotlin/com/tangem/features/send/api/featuretoggles/SendFeatureToggles.kt b/features/send/api/src/main/kotlin/com/tangem/features/send/api/featuretoggles/SendFeatureToggles.kt index d80f08cf56..453a79b60c 100644 --- a/features/send/api/src/main/kotlin/com/tangem/features/send/api/featuretoggles/SendFeatureToggles.kt +++ b/features/send/api/src/main/kotlin/com/tangem/features/send/api/featuretoggles/SendFeatureToggles.kt @@ -7,4 +7,7 @@ interface SendFeatureToggles { /** Availability of redesigned send screen */ val isRedesignedSendEnabled: Boolean + + /** Updates remote toggle */ + suspend fun fetchNewSendEnabled() } \ No newline at end of file diff --git a/features/send/impl/build.gradle.kts b/features/send/impl/build.gradle.kts index 587cb6d67c..4d0d072624 100644 --- a/features/send/impl/build.gradle.kts +++ b/features/send/impl/build.gradle.kts @@ -48,6 +48,7 @@ dependencies { implementation(projects.core.navigation) implementation(projects.core.analytics) implementation(projects.core.analytics.models) + implementation(projects.core.datasource) /** Common */ implementation(projects.common) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/di/SendFeatureTogglesModule.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/di/SendFeatureTogglesModule.kt index 6b1465762c..60c8b2822c 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/di/SendFeatureTogglesModule.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/di/SendFeatureTogglesModule.kt @@ -1,8 +1,10 @@ package com.tangem.features.send.impl.di import com.tangem.core.featuretoggle.manager.FeatureTogglesManager +import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.features.send.api.featuretoggles.SendFeatureToggles import com.tangem.features.send.impl.featuretoggles.DefaultSendFeatureToggles +import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -18,7 +20,15 @@ internal object SendFeatureTogglesModule { @Provides @Singleton - fun provideSendFeatureToggles(featureTogglesManager: FeatureTogglesManager): SendFeatureToggles { - return DefaultSendFeatureToggles(featureTogglesManager = featureTogglesManager) + fun provideSendFeatureToggles( + featureTogglesManager: FeatureTogglesManager, + tangemTechApi: TangemTechApi, + dispatchers: CoroutineDispatcherProvider, + ): SendFeatureToggles { + return DefaultSendFeatureToggles( + featureTogglesManager = featureTogglesManager, + tangemTechApi = tangemTechApi, + dispatchers = dispatchers, + ) } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/featuretoggles/DefaultSendFeatureToggles.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/featuretoggles/DefaultSendFeatureToggles.kt index 71f11c480d..b36cba4cbd 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/featuretoggles/DefaultSendFeatureToggles.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/featuretoggles/DefaultSendFeatureToggles.kt @@ -1,16 +1,41 @@ package com.tangem.features.send.impl.featuretoggles import com.tangem.core.featuretoggle.manager.FeatureTogglesManager +import com.tangem.datasource.api.common.response.getOrThrow +import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.features.send.api.featuretoggles.SendFeatureToggles +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.coroutines.runCatching +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update +import timber.log.Timber /** * Default implementation of Send feature toggles * * @property featureTogglesManager manager for getting information about the availability of feature toggles + * @property tangemTechApi api to get remote feature toggle for send + * @property dispatchers coroutine dispatchers */ internal class DefaultSendFeatureToggles( private val featureTogglesManager: FeatureTogglesManager, + private val tangemTechApi: TangemTechApi, + private val dispatchers: CoroutineDispatcherProvider, ) : SendFeatureToggles { + + private val remoteSendEnabled: MutableStateFlow = MutableStateFlow(true) + override val isRedesignedSendEnabled: Boolean - get() = featureTogglesManager.isFeatureEnabled(name = "REDESIGNED_SEND_SCREEN_ENABLED") + get() = featureTogglesManager.isFeatureEnabled(name = "REDESIGNED_SEND_SCREEN_ENABLED") && + remoteSendEnabled.value + + override suspend fun fetchNewSendEnabled() { + runCatching(dispatchers.io) { + tangemTechApi.getFeatures().getOrThrow() + }.onSuccess { response -> + remoteSendEnabled.update { response.isNewSendEnabled } + }.onFailure { + Timber.e(it.localizedMessage, "Unable to fetch new send toggle") + } + } } \ 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 index feca8a0786..c4ce202599 100644 --- 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 @@ -1,7 +1,9 @@ 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 import com.tangem.core.analytics.models.AnalyticsParam.Key.TYPE @@ -66,9 +68,9 @@ internal sealed class SendAnalyticEvents( data object FeeScreenOpened : SendAnalyticEvents(event = "Fee Screen Opened") /** Selected fee (send after next screen opened) */ - data class SelectedFee(val feeType: SelectedFeeType) : SendAnalyticEvents( + data class SelectedFee(val feeType: AnalyticsParam.FeeType) : SendAnalyticEvents( event = "Fee Selected", - params = mapOf("Fee Type" to feeType.name), + params = mapOf("Fee Type" to feeType.value), ) /** Custom fee selected */ @@ -97,7 +99,16 @@ internal sealed class SendAnalyticEvents( // region Transaction Result /** Transaction send screen opened */ - data object TransactionScreenOpened : SendAnalyticEvents(event = "Transaction Sent Screen Opened") + data class TransactionScreenOpened( + val token: String, + val feeType: AnalyticsParam.FeeType, + ) : SendAnalyticEvents( + event = "Transaction Sent Screen Opened", + params = mapOf( + TOKEN to token, + FEE_TYPE to feeType.value, + ), + ) /** Share button clicked */ data object ShareButtonClicked : SendAnalyticEvents(event = "Button - Share") @@ -145,12 +156,4 @@ internal enum class EnterAddressSource { internal enum class SelectedCurrencyType(val value: String) { Token("Token"), AppCurrency("App Currency"), -} - -internal enum class SelectedFeeType { - Min, - Max, - Fixed, - Normal, - Custom, } \ 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 index 27c93b9743..ac6485fc8a 100644 --- 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 @@ -2,8 +2,10 @@ package com.tangem.features.send.impl.presentation.analytics.utils import com.tangem.blockchain.common.transaction.TransactionFee 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.tokens.model.CryptoCurrency import com.tangem.features.send.impl.presentation.analytics.SelectedCurrencyType -import com.tangem.features.send.impl.presentation.analytics.SelectedFeeType 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 @@ -11,11 +13,13 @@ 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) { @@ -73,16 +77,57 @@ internal class SendScreenAnalyticSender( ) } + 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 = when (feeSelectorState.fees) { - is TransactionFee.Single -> SelectedFeeType.Fixed - is TransactionFee.Choosable -> when (feeSelectorState.selectedFee) { - FeeType.Slow -> SelectedFeeType.Min - FeeType.Market -> SelectedFeeType.Normal - FeeType.Fast -> SelectedFeeType.Max - FeeType.Custom -> SelectedFeeType.Custom - } - } + 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 index 69e56bfc62..8a7d5e7f3a 100644 --- 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 @@ -1,15 +1,18 @@ package com.tangem.features.send.impl.presentation.domain import androidx.compose.runtime.Immutable +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, ) \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt index a22b3ee620..fcd33d986f 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt @@ -54,7 +54,7 @@ internal sealed class SendNotification(val config: NotificationConfig) { wrappedList(cryptoCurrency, utxoLimit, amountLimit), ), buttonState = NotificationConfig.ButtonsState.PrimaryButtonConfig( - text = resourceReference(R.string.send_notification_reduce_to, wrappedList(amountLimit)), + text = resourceReference(R.string.send_notification_leave_button, wrappedList(amountLimit)), onClick = onConfirmClick, ), ) @@ -98,7 +98,7 @@ internal sealed class SendNotification(val config: NotificationConfig) { title = resourceReference(R.string.send_notification_existential_deposit_title), subtitle = resourceReference(R.string.send_notification_existential_deposit_text, wrappedList(deposit)), buttonState = NotificationConfig.ButtonsState.PrimaryButtonConfig( - text = resourceReference(R.string.send_notification_existential_deposit_button, wrappedList(deposit)), + text = resourceReference(R.string.send_notification_leave_button, wrappedList(deposit)), onClick = onConfirmClick, ), ) @@ -119,12 +119,13 @@ internal sealed class SendNotification(val config: NotificationConfig) { ), ) { data class HighFeeError( + val currencyName: String, val amount: String, val onConfirmClick: () -> Unit, val onCloseClick: () -> Unit, ) : Warning( title = resourceReference(R.string.send_notification_high_fee_title), - subtitle = resourceReference(R.string.send_notification_high_fee_text, wrappedList(amount)), + subtitle = resourceReference(R.string.send_notification_high_fee_text, wrappedList(currencyName, amount)), buttonsState = NotificationConfig.ButtonsState.PrimaryButtonConfig( text = resourceReference(R.string.send_notification_reduce_by, wrappedList(amount)), onClick = onConfirmClick, @@ -156,7 +157,7 @@ internal sealed class SendNotification(val config: NotificationConfig) { data class FeeCoverageNotification(val cryptoAmount: String, val fiatAmount: String) : Warning( title = resourceReference(R.string.send_network_fee_warning_title), subtitle = resourceReference( - R.string.send_network_fee_warning_content, + R.string.common_network_fee_warning_content, wrappedList(cryptoAmount, fiatAmount), ), ) 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 index 4b30a9466f..ad764dbb71 100644 --- 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 @@ -66,11 +66,12 @@ internal class SendNotificationFactory( amountValue = amountValue, feeValue = feeValue, ) - val sendingAmount = calculateSubtractedAmount( - isFeeCoverage = isFeeCoverage, + val sendingAmount = checkAndCalculateSubtractedAmount( + isAmountSubtractAvailable = isFeeCoverage, cryptoCurrencyStatus = cryptoCurrencyStatusProvider(), amountValue = amountValue, feeValue = feeValue, + reduceAmountBy = sendState.reduceAmountBy, ) buildList { // errors @@ -194,7 +195,7 @@ internal class SendNotificationFactory( val spendingAmount = if (cryptoCurrency is CryptoCurrency.Token) { feeAmount } else { - feeAmount + receivedAmount + receivedAmount } val currencyDeposit = currencyChecksRepository.getExistentialDeposit( userWalletId, @@ -260,6 +261,7 @@ internal class SendNotificationFactory( if (!ignoreAmountReduce && isTotalBalance && isTezos) { add( SendNotification.Warning.HighFeeError( + currencyName = cryptoCurrencyStatus.currency.name, amount = threshold.toPlainString(), onConfirmClick = { clickIntents.onAmountReduceClick( 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 index b06d42a23d..a8a93bcd32 100644 --- 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 @@ -54,7 +54,7 @@ internal fun checkFeeCoverage( /** * Calculates subtracted amount */ -internal fun calculateSubtractedAmount( +private fun calculateSubtractedAmount( isFeeCoverage: Boolean, cryptoCurrencyStatus: CryptoCurrencyStatus, amountValue: BigDecimal, 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 index a2a13e987a..62bf1460ad 100644 --- 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 @@ -47,7 +47,7 @@ internal class BitcoinCustomFeeConverter( keyboardType = KeyboardType.Number, ), title = resourceReference(R.string.send_max_fee), - footer = resourceReference(R.string.send_max_fee_footer), + footer = resourceReference(R.string.send_bitcoin_custom_fee_footer), label = getFiatReference( rate = feeCurrency?.fiatRate, value = feeValue, 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 index 6bed843250..d64f1c3d47 100644 --- 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 @@ -43,7 +43,7 @@ internal class EthereumCustomFeeConverter( keyboardType = KeyboardType.Number, ), title = resourceReference(R.string.send_max_fee), - footer = resourceReference(R.string.send_max_fee_footer), + footer = resourceReference(R.string.send_evm_custom_fee_footer), label = getFiatReference( rate = feeCurrency?.fiatRate, value = feeValue, 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 index 7d4dfe3b45..8f3d03dbbc 100644 --- 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 @@ -10,6 +10,7 @@ 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 +import java.math.RoundingMode internal class SendAmountFieldMaxAmountConverter( private val stateRouterProvider: Provider, @@ -33,7 +34,7 @@ internal class SendAmountFieldMaxAmountConverter( val isDoneActionEnabled = !decimalCryptoValue.isNullOrZero() val cryptoValue = decimalCryptoValue?.parseBigDecimal(cryptoDecimals).orEmpty() - val fiatValue = decimalFiatValue?.parseBigDecimal(fiatDecimals).orEmpty() + val fiatValue = decimalFiatValue?.parseBigDecimal(fiatDecimals, roundingMode = RoundingMode.HALF_UP).orEmpty() return state.copyWrapped( isEditState = isEditState, amountState = amountState.copy( 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 index e858d9b150..93cc9067fd 100644 --- 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 @@ -26,12 +26,15 @@ 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.resolveAnnotatedReference +import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.shareText +import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.utils.BigDecimalFormatter 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.utils.getFiatFormatted @Composable internal fun SendNavigationButtons( @@ -172,21 +175,27 @@ private fun SendingText( } if (feeFiat != null && sendingFiat != null) { - val sendingValue = BigDecimalFormatter.formatFiatAmount( - fiatAmount = sendingFiat, - fiatCurrencyCode = feeState.appCurrency.code, - fiatCurrencySymbol = feeState.appCurrency.symbol, + val sendingValue = getFiatFormatted( + value = sendingFiat, + currencySymbol = feeState.appCurrency.symbol, + currencyCode = feeState.appCurrency.code, ) - val feeValue = BigDecimalFormatter.formatFiatAmount( - fiatAmount = feeFiat, - fiatCurrencyCode = feeState.appCurrency.code, - fiatCurrencySymbol = feeState.appCurrency.symbol, + val feeValue = getFiatFormatted( + value = feeState.fee?.amount?.value, + currencySymbol = feeState.appCurrency.symbol, + currencyCode = feeState.appCurrency.code, ) + val textResource = remember(sendingValue, feeValue) { + resourceReference( + id = R.string.send_summary_transaction_description, + formatArgs = wrappedList(sendingValue, feeValue), + ) + } Text( - text = stringResource(id = R.string.send_summary_transaction_description, sendingValue, feeValue), + text = textResource.resolveAnnotatedReference(), textAlign = TextAlign.Center, - style = TangemTheme.typography.caption1, - color = TangemTheme.colors.text.tertiary, + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.primary1, modifier = Modifier .fillMaxWidth() .padding(TangemTheme.dimens.spacing12), 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 index d6529709b7..819655152a 100644 --- 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 @@ -33,7 +33,10 @@ import kotlinx.coroutines.flow.withIndex @Composable internal fun SendScreen(uiState: SendUiState, currentState: SendUiCurrentScreen) { val snackbarHostState = remember { SnackbarHostState() } - BackHandler(onBack = uiState.clickIntents::onBackClick) + val onBackClick = uiState.clickIntents::onBackClick.takeIf { + uiState.sendState?.isSending != true + } ?: {} + BackHandler(onBack = onBackClick) Column( modifier = Modifier .fillMaxSize() 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 index fcff795b70..929cfdf3b2 100644 --- 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 @@ -15,6 +15,7 @@ internal fun LazyListScope.notifications( notifications: ImmutableList, modifier: Modifier = Modifier, hasPaddingAbove: Boolean = false, + isClickDisabled: Boolean = false, ) { itemsIndexed( items = notifications, @@ -44,6 +45,7 @@ internal fun LazyListScope.notifications( -> null is SendNotification.Error -> TangemTheme.colors.icon.warning }, + isEnabled = !isClickDisabled, ) }, ) 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 index d9c609a5ca..d29fea2394 100644 --- 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 @@ -16,6 +16,7 @@ import com.tangem.core.ui.components.SpacerWMax import com.tangem.core.ui.components.rows.SelectorRowItem 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 @@ -114,11 +115,14 @@ private fun FeeError(feeSelectorState: FeeSelectorState) { 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 -> null + FeeType.Custom -> customAmount } } 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 index 5c0ca33a84..c59617aac0 100644 --- 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 @@ -33,9 +33,10 @@ internal fun TextFieldWithPaste( ) { val (title, color) = when { isError && error != null -> error to TangemTheme.colors.text.warning - isReadOnly -> label to TangemTheme.colors.text.disabled + 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 @@ -59,6 +60,7 @@ internal fun TextFieldWithPaste( SimpleTextField( value = value, placeholder = placeholder, + placeholderColor = placeholderColor, onValueChange = onValueChange, readOnly = isReadOnly, modifier = Modifier diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/AmountBlock.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/AmountBlock.kt index ab68759908..22d8835ec6 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/AmountBlock.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/AmountBlock.kt @@ -24,7 +24,7 @@ import com.tangem.features.send.impl.presentation.state.previewdata.AmountStateP @Composable internal fun AmountBlock( amountState: SendStates.AmountState, - isSuccess: Boolean, + isClickDisabled: Boolean, isEditingDisabled: Boolean, onClick: () -> Unit, ) { @@ -53,7 +53,7 @@ internal fun AmountBlock( modifier = Modifier .clip(TangemTheme.shapes.roundedCornersXMedium) .background(backgroundColor) - .clickable(enabled = !isSuccess && !isEditingDisabled, onClick = onClick) + .clickable(enabled = !isClickDisabled && !isEditingDisabled, onClick = onClick) .padding( vertical = TangemTheme.dimens.spacing14, horizontal = TangemTheme.dimens.spacing16, @@ -91,7 +91,7 @@ private fun AmountBlockPreview_Light( TangemTheme { AmountBlock( amountState = value, - isSuccess = false, + isClickDisabled = false, isEditingDisabled = false, onClick = {}, ) @@ -106,7 +106,7 @@ private fun AmountBlockPreview_Dark( TangemTheme(isDark = true) { AmountBlock( amountState = value, - isSuccess = true, + isClickDisabled = true, isEditingDisabled = false, onClick = {}, ) 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 index 54ef73296a..3af4e1c827 100644 --- 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 @@ -26,13 +26,13 @@ import com.tangem.features.send.impl.presentation.utils.getCryptoReference import com.tangem.features.send.impl.presentation.utils.getFiatReference @Composable -internal fun FeeBlock(feeState: SendStates.FeeState, isSuccess: Boolean, onClick: () -> Unit) { +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 = !isSuccess, onClick = onClick) + .clickable(enabled = !isClickDisabled, onClick = onClick) .padding(TangemTheme.dimens.spacing12), ) { Text( @@ -115,7 +115,7 @@ private fun FeeBlockPreview_Light(@PreviewParameter(FeeBlockPreviewProvider::cla TangemTheme { FeeBlock( feeState = value, - isSuccess = true, + isClickDisabled = true, onClick = {}, ) } @@ -127,7 +127,7 @@ private fun FeeBlockPreview_Dark(@PreviewParameter(FeeBlockPreviewProvider::clas TangemTheme(isDark = true) { FeeBlock( feeState = value, - isSuccess = true, + isClickDisabled = true, onClick = {}, ) } 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 index e3b92db56c..3936e4985b 100644 --- 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 @@ -23,7 +23,7 @@ import com.tangem.features.send.impl.presentation.state.previewdata.RecipientSta @Composable internal fun RecipientBlock( recipientState: SendStates.RecipientState, - isSuccess: Boolean, + isClickDisabled: Boolean, isEditingDisabled: Boolean, onClick: () -> Unit, ) { @@ -38,7 +38,7 @@ internal fun RecipientBlock( .fillMaxWidth() .clip(TangemTheme.shapes.roundedCornersXMedium) .background(backgroundColor) - .clickable(enabled = !isSuccess && !isEditingDisabled, onClick = onClick) + .clickable(enabled = !isClickDisabled && !isEditingDisabled, onClick = onClick) .padding(TangemTheme.dimens.spacing12), ) { AddressBlock(recipientState.addressTextField) @@ -104,7 +104,7 @@ private fun RecipientBlockPreview_Light( TangemTheme { RecipientBlock( recipientState = value, - isSuccess = true, + isClickDisabled = true, isEditingDisabled = false, onClick = {}, ) @@ -119,7 +119,7 @@ private fun RecipientBlockPreview_Dark( TangemTheme(isDark = true) { RecipientBlock( recipientState = value, - isSuccess = true, + isClickDisabled = true, isEditingDisabled = false, onClick = {}, ) 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 index 6a6cc0550d..6c6de6e979 100644 --- 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 @@ -35,12 +35,13 @@ private const val TAP_HELP_ANIMATION_DELAY = 500L @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(sendState.notifications) + notifications(notifications = sendState.notifications, isClickDisabled = isClickDisabled) } } @@ -50,6 +51,7 @@ private fun LazyListScope.blocks(uiState: SendUiState) { 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) { @@ -65,19 +67,19 @@ private fun LazyListScope.blocks(uiState: SendUiState) { } RecipientBlock( recipientState = recipientState, - isSuccess = isSuccess, + isClickDisabled = isClickDisabled, isEditingDisabled = uiState.isEditingDisabled, onClick = uiState.clickIntents::showRecipient, ) AmountBlock( amountState = amountState, - isSuccess = isSuccess, + isClickDisabled = isClickDisabled, isEditingDisabled = uiState.isEditingDisabled, onClick = uiState.clickIntents::showAmount, ) FeeBlock( feeState = feeState, - isSuccess = isSuccess, + isClickDisabled = isClickDisabled, onClick = uiState.clickIntents::showFee, ) } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/utils/FormatterUtils.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/utils/FormatterUtils.kt index 73ef044814..f2be7e6ed2 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/utils/FormatterUtils.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/utils/FormatterUtils.kt @@ -11,6 +11,7 @@ import java.math.BigDecimal import java.math.RoundingMode private const val FIAT_DECIMALS = 2 +private const val CRYPTO_FEE_DECIMALS = 6 private const val FEE_MINIMUM_VALUE = 0.01 internal fun getCryptoReference(amount: Amount?, isFeeApproximate: Boolean): TextReference? { @@ -21,7 +22,7 @@ internal fun getCryptoReference(amount: Amount?, isFeeApproximate: Boolean): Tex BigDecimalFormatter.formatCryptoAmount( cryptoAmount = amount.value, cryptoCurrency = amount.currencySymbol, - decimals = amount.decimals, + decimals = amount.decimals.coerceAtMost(CRYPTO_FEE_DECIMALS), ), ), ) @@ -36,24 +37,27 @@ internal fun getFiatReference(value: BigDecimal?, rate: BigDecimal?, appCurrency internal fun getFiatString(value: BigDecimal?, rate: BigDecimal?, appCurrency: AppCurrency): String { if (value == null || rate == null) return EMPTY_BALANCE_SIGN val feeValue = value.multiply(rate) - val scaled = feeValue.setScale(FIAT_DECIMALS, RoundingMode.UP) ?: BigDecimal.ZERO - val formattedValue = if (scaled < BigDecimal(FEE_MINIMUM_VALUE)) { + return getFiatFormatted(feeValue, appCurrency.code, appCurrency.symbol) +} + +internal fun getFiatFormatted(value: BigDecimal?, currencyCode: String, currencySymbol: String): String { + val scaled = value?.setScale(FIAT_DECIMALS, RoundingMode.UP) ?: BigDecimal.ZERO + return if (scaled < BigDecimal(FEE_MINIMUM_VALUE)) { buildString { append(BigDecimalFormatter.CAN_BE_LOWER_SIGN) append( BigDecimalFormatter.formatFiatAmount( fiatAmount = BigDecimal(FEE_MINIMUM_VALUE), - fiatCurrencyCode = appCurrency.code, - fiatCurrencySymbol = appCurrency.symbol, + fiatCurrencyCode = currencyCode, + fiatCurrencySymbol = currencySymbol, ), ) } } else { BigDecimalFormatter.formatFiatAmount( - fiatAmount = feeValue, - fiatCurrencyCode = appCurrency.code, - fiatCurrencySymbol = appCurrency.symbol, + fiatAmount = value, + fiatCurrencyCode = currencyCode, + fiatCurrencySymbol = currencySymbol, ) } - return formattedValue } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index 5410b54f17..fc9c9af998 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt @@ -26,7 +26,6 @@ import com.tangem.domain.tokens.* import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.repository.CurrencyChecksRepository import com.tangem.domain.tokens.utils.convertToAmount import com.tangem.domain.transaction.error.GetFeeError @@ -59,8 +58,9 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.JobHolder import com.tangem.utils.coroutines.saveIn import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.* +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch import timber.log.Timber import java.math.BigDecimal import java.util.Locale @@ -79,8 +79,8 @@ internal class SendViewModel @Inject constructor( private val getWalletsUseCase: GetWalletsUseCase, private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase, private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase, - private val getCryptoCurrencyStatusSyncUseCase: GetCryptoCurrencyStatusSyncUseCase, - private val getCryptoCurrencyStatusesSyncUseCase: GetCryptoCurrencyStatusesSyncUseCase, + private val getCryptoCurrencyUseCase: GetCryptoCurrencyUseCase, + private val getNetworkAddressesUseCase: GetNetworkAddressesUseCase, private val getFixedTxHistoryItemsUseCase: GetFixedTxHistoryItemsUseCase, private val getFeeUseCase: GetFeeUseCase, private val sendTransactionUseCase: SendTransactionUseCase, @@ -95,6 +95,7 @@ internal class SendViewModel @Inject constructor( private val neverShowTapHelpUseCase: NeverShowTapHelpUseCase, private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase, private val listenToQrScanningUseCase: ListenToQrScanningUseCase, + private val addCryptoCurrenciesUseCase: AddCryptoCurrenciesUseCase, currencyChecksRepository: CurrencyChecksRepository, isFeeApproximateUseCase: IsFeeApproximateUseCase, validateWalletMemoUseCase: ValidateWalletMemoUseCase, @@ -180,6 +181,7 @@ internal class SendViewModel @Inject constructor( stateRouterProvider = Provider { stateRouter }, currentStateProvider = Provider { uiState }, analyticsEventHandler = analyticsEventHandler, + cryptoCurrencyProvider = Provider { cryptoCurrency }, ) } @@ -188,6 +190,7 @@ internal class SendViewModel @Inject constructor( private set private var userWallet: UserWallet by Delegates.notNull() + private var userWallets: List = emptyList() private var isAmountSubtractAvailable: Boolean = false private var isTapHelpPreviewEnabled: Boolean = false private var coinCryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull() @@ -205,7 +208,6 @@ internal class SendViewModel @Inject constructor( private var sendIdleTimer = 0L init { - subscribeOnQRScannerResult() subscribeOnCurrencyStatusUpdates() subscribeOnBalanceHidden() getTapHelpPreviewAvailability() @@ -372,7 +374,7 @@ internal class SendViewModel @Inject constructor( cryptoCurrencyStatus = currencyStatus coinCryptoCurrencyStatus = coinCurrencyStatus feeCryptoCurrencyStatus = feeCurrencyStatus - + subscribeOnQRScannerResult() when { uiState.sendState?.isSuccess == true -> { stateRouter.showSend() @@ -400,57 +402,48 @@ internal class SendViewModel @Inject constructor( } private fun getUserWallets() { - getWalletsUseCase() - .conflate() - .distinctUntilChanged() - .onEach { userWallets -> - coroutineScope { - runCatching { - userWallets - .filterNot { it.walletId == userWalletId || it.isLocked } - .map { wallet -> - async(dispatchers.io) { wallet.toAvailableWallet() } - }.awaitAll() - }.onSuccess { result -> - uiState = stateFactory.onLoadedWalletsList(wallets = result) - }.onFailure { - uiState = stateFactory.onLoadedWalletsList(wallets = emptyList()) + viewModelScope.launch(dispatchers.main) { + runCatching { + getWalletsUseCase.invokeSync() + ?.toAvailableWallets() + .orEmpty() + }.onSuccess { result -> + combine(*result.toTypedArray()) { it } + .onEach { + userWallets = it.filterNotNull().toList() + uiState = stateFactory.onLoadedWalletsList(wallets = userWallets) } - } - } - .flowOn(dispatchers.main) - .launchIn(viewModelScope) - } - - private suspend fun UserWallet.toAvailableWallet(): AvailableWallet? { - return if (!isMultiCurrency) { - val status = getCryptoCurrencyStatusSyncUseCase(walletId).getOrNull() - val address = status?.value?.networkAddress.takeIf { - status?.currency?.network?.id == cryptoCurrency.network.id && - status.currency.network.derivationPath !is Network.DerivationPath.Custom - } - address?.let { - AvailableWallet( - name = name, - address = it.defaultAddress.value, - ) - } - } else { - val statuses = getCryptoCurrencyStatusesSyncUseCase(walletId).getOrNull() - val walletCurrency = statuses?.firstOrNull { - it.currency.network.id == cryptoCurrency.network.id && - it.currency.network.derivationPath !is Network.DerivationPath.Custom - } - val address = walletCurrency?.value?.networkAddress - address?.let { - AvailableWallet( - name = name, - address = it.defaultAddress.value, - ) + .flowOn(dispatchers.main) + .launchIn(viewModelScope) + }.onFailure { + uiState = stateFactory.onLoadedWalletsList(wallets = emptyList()) } } } + private suspend fun List.toAvailableWallets(): List> = + filterNot { it.walletId == userWalletId || it.isLocked } + .mapNotNull { wallet -> + val status = if (!wallet.isMultiCurrency) { + getCryptoCurrencyUseCase(wallet.walletId).getOrNull()?.let { + if (it.network.id == cryptoCurrency.network.id) { + getNetworkAddressesUseCase(wallet.walletId, it.network) + } else { + null + } + } + } else { + getNetworkAddressesUseCase(wallet.walletId, cryptoCurrency.network) + } + status?.map { address -> + AvailableWallet( + name = wallet.name, + address = address, + userWalletId = wallet.walletId, + ) + } + } + private suspend fun getTxHistory() { val txHistoryList = getFixedTxHistoryItemsUseCase.getSync( userWalletId = userWalletId, @@ -637,7 +630,7 @@ internal class SendViewModel @Inject constructor( }.saveIn(memoValidationJobHolder) } - private suspend fun validateAddress(value: String): Boolean { + private suspend fun validateAddress(value: String): Boolean = runCatching { val isValidAddress = validateWalletAddressUseCase( userWalletId = userWalletId, network = cryptoCurrency.network, @@ -647,7 +640,7 @@ internal class SendViewModel @Inject constructor( ?.any { it.value == value } ?: true onEnteredValidAddress(isValidAddress, isAddressInWallet) return isValidAddress - } + }.getOrElse { false } private suspend fun checkIfXrpAddressValue(value: String): Boolean { return BlockchainUtils.decodeRippleXAddress(value, cryptoCurrency.network.id.value)?.let { decodedAddress -> @@ -875,11 +868,25 @@ internal class SendViewModel @Inject constructor( uiState = stateFactory.getSendingStateUpdate(isSending = false) updateTransactionStatus(txData) scheduleBalanceUpdate() - analyticsEventHandler.send(SendAnalyticEvents.TransactionScreenOpened) + addTokenToWalletIfNeeded() + sendScreenAnalyticSender.sendTransaction() }, ) } + private fun addTokenToWalletIfNeeded() { + if (cryptoCurrency !is CryptoCurrency.Token) return + + val recipientState = uiState.getRecipientState(stateRouter.isEditState) ?: return + val destinationAddress = recipientState.addressTextField.value + + val maybeUserWallet = userWallets.firstOrNull { it.address == destinationAddress } ?: return + + viewModelScope.launch(dispatchers.io) { + addCryptoCurrenciesUseCase(userWalletId = maybeUserWallet.userWalletId, currency = cryptoCurrency) + } + } + private suspend fun updateTransactionStatus(txData: TransactionData) { val txUrl = getExplorerTransactionUrlUseCase( userWalletId = userWalletId, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index c3fd4f4fd5..1147346e65 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -218,6 +218,7 @@ internal class StateBuilder( quoteModel = quoteModel, fromToken = fromToken, ignoreAmountReduce = uiStateHolder.reduceAmountIgnore, + selectedFeeType = selectedFeeType, ) val feeState = createFeeState(quoteModel.txFee, selectedFeeType) val fromCurrencyStatus = quoteModel.fromTokenInfo.cryptoCurrencyStatus @@ -317,12 +318,13 @@ internal class StateBuilder( quoteModel: SwapState.QuotesLoadedState, fromToken: CryptoCurrency, ignoreAmountReduce: Boolean, + selectedFeeType: FeeType, ): List { val warnings = mutableListOf() maybeAddDomainWarnings(quoteModel, warnings, ignoreAmountReduce) maybeAddNeedReserveToCreateAccountWarning(quoteModel, warnings) maybeAddPermissionNeededWarning(quoteModel, warnings, fromToken) - maybeAddNetworkFeeCoverageWarning(quoteModel, warnings) + maybeAddNetworkFeeCoverageWarning(quoteModel, warnings, selectedFeeType) maybeAddUnableCoverFeeWarning(quoteModel, fromToken, warnings) maybeAddInsufficientFundsWarning(quoteModel, warnings) maybeAddTransactionInProgressWarning(quoteModel, warnings) @@ -461,18 +463,37 @@ internal class StateBuilder( private fun maybeAddNetworkFeeCoverageWarning( quoteModel: SwapState.QuotesLoadedState, warnings: MutableList, + selectedFeeType: FeeType, ) { when (quoteModel.preparedSwapConfigState.includeFeeInAmount) { - is IncludeFeeInAmount.Included -> + is IncludeFeeInAmount.Included -> { + val fee = selectFeeByType(selectedFeeType, quoteModel.txFee) ?: return warnings.add( SwapWarning.GeneralWarning( - createNetworkFeeCoverageNotificationConfig(), + createNetworkFeeCoverageNotificationConfig( + quoteModel.fromTokenInfo.tokenAmount.getFormattedCryptoAmount( + quoteModel.fromTokenInfo.cryptoCurrencyStatus.currency, + ), + fee.feeFiatFormatted, + ), ), ) + } else -> Unit } } + private fun selectFeeByType(feeType: FeeType, txFeeState: TxFeeState): TxFee? { + return when (txFeeState) { + TxFeeState.Empty -> null + is TxFeeState.SingleFeeState -> txFeeState.fee + is TxFeeState.MultipleFeeState -> when (feeType) { + FeeType.NORMAL -> txFeeState.normalFee + FeeType.PRIORITY -> txFeeState.priorityFee + } + } + } + private fun maybeAddUnableCoverFeeWarning( quoteModel: SwapState.QuotesLoadedState, fromToken: CryptoCurrency, @@ -548,12 +569,12 @@ internal class StateBuilder( if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder val warnings = mutableListOf() warnings.add(getWarningForError(dataError, fromToken.cryptoCurrencyStatus.currency)) - if (includeFeeInAmount is IncludeFeeInAmount.Included) { - warnings.add( - SwapWarning.GeneralWarning( - createNetworkFeeCoverageNotificationConfig(), - ), + if (includeFeeInAmount is IncludeFeeInAmount.Included && uiStateHolder.fee is FeeItemState.Content) { + val feeCoverageNotification = createNetworkFeeCoverageNotificationConfig( + fromToken.tokenAmount.getFormattedCryptoAmount(fromToken.cryptoCurrencyStatus.currency), + uiStateHolder.fee.amountFiatFormatted, ) + warnings.add(SwapWarning.GeneralWarning(feeCoverageNotification)) } val providerState = getProviderStateForError( swapProvider = swapProvider, @@ -1397,10 +1418,16 @@ internal class StateBuilder( ) } - private fun createNetworkFeeCoverageNotificationConfig(): NotificationConfig { + private fun createNetworkFeeCoverageNotificationConfig( + cryptoAmount: String, + fiatAmount: String, + ): NotificationConfig { return NotificationConfig( title = resourceReference(R.string.send_network_fee_warning_title), - subtitle = resourceReference(R.string.swapping_network_fee_warning_content), + subtitle = resourceReference( + R.string.common_network_fee_warning_content, + wrappedList(cryptoAmount, fiatAmount), + ), iconResId = R.drawable.img_attention_20, ) } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt index 3a470f1a30..cf4d420f32 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt @@ -42,6 +42,7 @@ internal class TokenDetailsNotificationsAnalyticsSender( is TokenDetailsNotification.TopUpWithoutReserve, is TokenDetailsNotification.RentInfo, is TokenDetailsNotification.SwapPromo, + is TokenDetailsNotification.NetworkShutdown, -> null } } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt index a33cc576e3..874efc22b9 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt @@ -155,7 +155,11 @@ internal sealed class TokenDetailsNotification(val config: NotificationConfig) { ), ) - class NetworksNoAccount(val network: String, val symbol: String, val amount: String) : Informational( + data class NetworksNoAccount( + private val network: String, + private val symbol: String, + private val amount: String, + ) : Informational( title = resourceReference(R.string.warning_no_account_title), subtitle = resourceReference( id = R.string.no_account_generic, @@ -175,4 +179,9 @@ internal sealed class TokenDetailsNotification(val config: NotificationConfig) { formatArgs = wrappedList(coinSymbol), ), ) + + data class NetworkShutdown(private val title: TextReference, private val subtitle: TextReference) : Warning( + title = title, + subtitle = subtitle, + ) } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt index eaeb7f760a..23080d5d8e 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt @@ -1,6 +1,7 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory import com.tangem.blockchain.common.Blockchain +import com.tangem.core.ui.extensions.resourceReference import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning @@ -8,6 +9,7 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDeta import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsNotification import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsNotification.* import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents +import com.tangem.features.tokendetails.impl.R import com.tangem.utils.converter.Converter import com.tangem.utils.extensions.removeBy import kotlinx.collections.immutable.ImmutableList @@ -78,6 +80,10 @@ internal class TokenDetailsNotificationConverter( onSwapClick = clickIntents::onSwapPromoClick, onCloseClick = clickIntents::onSwapPromoDismiss, ) + is CryptoCurrencyWarning.BeaconChainShutdown -> NetworkShutdown( + title = resourceReference(R.string.warning_beacon_chain_retirement_title), + subtitle = resourceReference(R.string.warning_beacon_chain_retirement_content), + ) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt index 7e47a2a490..10d52a0047 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt @@ -1,8 +1,12 @@ package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.domain.card.DeleteSavedAccessCodesUseCase +import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.DeleteWalletUseCase +import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase +import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.domain.wallets.usecase.UpdateWalletUseCase import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader @@ -32,9 +36,13 @@ internal class WalletCardClickIntentsImplementor @Inject constructor( private val stateHolder: WalletStateController, private val walletEventSender: WalletEventSender, private val walletScreenContentLoader: WalletScreenContentLoader, + private val getUserWalletUseCase: GetUserWalletUseCase, + private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, private val updateWalletUseCase: UpdateWalletUseCase, private val deleteWalletUseCase: DeleteWalletUseCase, + private val deleteSavedAccessCodesUseCase: DeleteSavedAccessCodesUseCase, private val analyticsEventHandler: AnalyticsEventHandler, + private val reduxStateHolder: ReduxStateHolder, private val dispatchers: CoroutineDispatcherProvider, ) : BaseWalletClickIntents(), WalletCardClickIntents { @@ -72,7 +80,18 @@ internal class WalletCardClickIntentsImplementor @Inject constructor( override fun onDeleteAfterConfirmationClick(userWalletId: UserWalletId) { viewModelScope.launch(dispatchers.main) { walletScreenContentLoader.cancel(userWalletId) + + val deletedUserWallet = getUserWalletUseCase(userWalletId).getOrNull() ?: return@launch + + deleteSavedAccessCodesUseCase(cardId = deletedUserWallet.cardId) + .onLeft { Timber.e(it.toString()) } + deleteWalletUseCase(userWalletId) + .onRight { + getSelectedWalletSyncUseCase().getOrNull()?.let { + reduxStateHolder.onUserWalletSelected(it) + } + } .onLeft { Timber.e(it.toString()) } } } diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 80bda90466..954d918b05 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -82,12 +82,13 @@ swipeRefreshLayout = "1.1.0" spr-client = "3.6.2" web3j = "4.10.1" leakcanary = "2.13" +markdown = "0.7.2" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.10-618" +tangemBlockchainSdk = "release-app_5.10-630" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "release-app_5.9-343" +tangemCardSdk = "release-app_5.10-353" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ # endregion Tangem @@ -249,4 +250,5 @@ camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = camera-view = { module = "androidx.camera:camera-view", version.ref = "androidXCamera" } web3j-core = { module = "org.web3j:core", version.ref = "web3j" } leakcanary = { module = "com.squareup.leakcanary:leakcanary-android", version.ref = "leakcanary" } +markdown = { module = "org.jetbrains:markdown", version.ref = "markdown" } # endregion Other diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt index a4492852b0..85ef064f98 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt @@ -44,4 +44,10 @@ object BlockchainUtils { val blockchain = Blockchain.fromId(networkId) return blockchain == Blockchain.Tezos } + + /** If current [networkId] is BeaconChain */ + fun isBeaconChain(networkId: String): Boolean { + val blockchain = Blockchain.fromId(networkId) + return blockchain == Blockchain.Binance || blockchain == Blockchain.BinanceTestnet + } } \ No newline at end of file