Updated on 2026-08-14

This commit is contained in:
Tangem 2024-03-28 13:45:12 +05:00
commit 2c739e2a4f
23 changed files with 455 additions and 40 deletions

View file

@ -126,4 +126,18 @@ internal object SettingsDomainModule {
fun provideDeleteDeprecatedLogsUseCase(settingsRepository: SettingsRepository): DeleteDeprecatedLogsUseCase {
return DeleteDeprecatedLogsUseCase(settingsRepository)
}
@Provides
@ViewModelScoped
fun provideIsSendTapHelpPreviewEnabledUseCase(
settingsRepository: SettingsRepository,
): IsSendTapHelpEnabledUseCase {
return IsSendTapHelpEnabledUseCase(settingsRepository = settingsRepository)
}
@Provides
@ViewModelScoped
fun provideNeverShowTapHelpUseCase(settingsRepository: SettingsRepository): NeverShowTapHelpUseCase {
return NeverShowTapHelpUseCase(settingsRepository = settingsRepository)
}
}

View file

@ -63,6 +63,8 @@ object PreferencesKeys {
val APP_LOGS_KEY by lazy { stringPreferencesKey(name = "app_logs") }
val SEND_TAP_HELP_PREVIEW_KEY by lazy { booleanPreferencesKey(name = "sendTapHelpPreview") }
fun getStart2CoinTOSAcceptedKey(region: String?) = booleanPreferencesKey(name = "start2Coin_tos_accepted_$region")
}

View file

@ -61,4 +61,18 @@ internal class DefaultSettingsRepository(
)
}
}
override suspend fun isSendTapHelpPreviewEnabled(): Boolean {
return appPreferencesStore.getSyncOrDefault(
key = PreferencesKeys.SEND_TAP_HELP_PREVIEW_KEY,
default = true,
)
}
override suspend fun setSendTapHelpPreviewAvailability(isEnabled: Boolean) {
appPreferencesStore.store(
key = PreferencesKeys.SEND_TAP_HELP_PREVIEW_KEY,
value = isEnabled,
)
}
}

View file

@ -5,5 +5,6 @@ plugins {
dependencies {
implementation(deps.kotlin.coroutines)
implementation(deps.arrow.core)
implementation(projects.domain.balanceHiding.models)
}

View file

@ -0,0 +1,14 @@
package com.tangem.domain.settings
import arrow.core.Either
import com.tangem.domain.settings.repositories.SettingsRepository
/**
* Checks if tap help is enabled
*
* @property settingsRepository settings repository
*/
class IsSendTapHelpEnabledUseCase(private val settingsRepository: SettingsRepository) {
suspend operator fun invoke() = Either.catch { settingsRepository.isSendTapHelpPreviewEnabled() }
}

View file

@ -0,0 +1,16 @@
package com.tangem.domain.settings
import arrow.core.Either
import com.tangem.domain.settings.repositories.SettingsRepository
/**
* Never to show tap help
*
* @property settingsRepository settings repository
*/
class NeverShowTapHelpUseCase(private val settingsRepository: SettingsRepository) {
suspend operator fun invoke() = Either.catch {
settingsRepository.setSendTapHelpPreviewAvailability(isEnabled = false)
}
}

View file

@ -13,4 +13,8 @@ interface SettingsRepository {
@Throws
suspend fun deleteDeprecatedLogs(maxSize: Int)
suspend fun isSendTapHelpPreviewEnabled(): Boolean
suspend fun setSendTapHelpPreviewAvailability(isEnabled: Boolean)
}

View file

@ -72,6 +72,7 @@ dependencies {
implementation(projects.domain.balanceHiding.models)
implementation(projects.domain.qrScanning)
implementation(projects.domain.qrScanning.models)
implementation(projects.domain.settings)
/** Feature modules */
implementation(projects.features.send.api)

View file

@ -39,7 +39,7 @@ internal class SendNotificationFactory(
.filter { it.type == SendUiStateType.Send }
.map {
val state = currentStateProvider()
val sendState = state.sendState
val sendState = state.sendState ?: return@map persistentListOf()
val feeState = state.feeState ?: return@map persistentListOf()
val feeAmount = feeState.fee?.amount?.value ?: BigDecimal.ZERO
val amountValue = state.amountState?.amountTextField?.cryptoAmount?.value ?: BigDecimal.ZERO
@ -60,7 +60,7 @@ internal class SendNotificationFactory(
fun dismissNotificationState(clazz: Class<out SendNotification>): SendUiState {
val state = currentStateProvider()
val sendState = state.sendState
val sendState = state.sendState ?: return state
val notificationsToRemove = sendState.notifications.filterIsInstance(clazz)
val updatedNotifications = sendState.notifications.toMutableList()
updatedNotifications.removeAll(notificationsToRemove)

View file

@ -13,8 +13,9 @@ import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.usecase.ValidateWalletMemoUseCase
import com.tangem.features.send.impl.R
import com.tangem.features.send.impl.presentation.domain.AvailableWallet
import com.tangem.features.send.impl.presentation.state.amount.SendAmountSubtractConverter
import com.tangem.features.send.impl.presentation.state.amount.SendAmountStateConverter
import com.tangem.features.send.impl.presentation.state.amount.SendAmountSubtractConverter
import com.tangem.features.send.impl.presentation.state.confirm.SendConfirmStateConverter
import com.tangem.features.send.impl.presentation.state.fee.SendFeeStateConverter
import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldConverter
import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientListConverter
@ -33,6 +34,7 @@ internal class SendStateFactory(
private val appCurrencyProvider: Provider<AppCurrency>,
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
private val feeCryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
private val isTapHelpPreviewEnabledProvider: Provider<Boolean>,
private val validateWalletMemoUseCase: ValidateWalletMemoUseCase,
private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase,
) {
@ -72,7 +74,11 @@ internal class SendStateFactory(
feeCryptoCurrencyStatusProvider = feeCryptoCurrencyStatusProvider,
)
}
private val confirmStateConverter by lazy(LazyThreadSafetyMode.NONE) {
SendConfirmStateConverter(
isTapHelpPreviewEnabledProvider = isTapHelpPreviewEnabledProvider,
)
}
private val recipientListStateConverter by lazy(LazyThreadSafetyMode.NONE) {
SendRecipientListConverter(
currentStateProvider = currentStateProvider,
@ -96,6 +102,7 @@ internal class SendStateFactory(
recipientState = state.recipientState
?: recipientStateConverter.convert(SendRecipientStateConverter.Data("", null)),
feeState = state.feeState ?: feeStateConverter.convert(Unit),
sendState = confirmStateConverter.convert(Unit),
cryptoCurrencySymbol = cryptoCurrencyStatusProvider().currency.symbol,
)
}
@ -236,21 +243,22 @@ internal class SendStateFactory(
fun getSendingStateUpdate(isSending: Boolean): SendUiState {
val state = currentStateProvider()
return state.copy(sendState = state.sendState.copy(isSending = isSending))
return state.copy(sendState = state.sendState?.copy(isSending = isSending))
}
fun getTransactionSendState(txData: TransactionData): SendUiState {
val state = currentStateProvider()
val cryptoCurrency = cryptoCurrencyStatusProvider().currency
val sendState = state.sendState ?: return state
val txUrl = getExplorerTransactionUrlUseCase(
txHash = txData.hash.orEmpty(),
networkId = cryptoCurrency.network.id,
).getOrElse { "" }
return state.copy(
sendState = state.sendState.copy(
sendState = sendState.copy(
transactionDate = txData.date?.timeInMillis ?: System.currentTimeMillis(),
isSuccess = true,
showTapHelp = false,
txUrl = txUrl,
notifications = persistentListOf(),
),
@ -259,13 +267,23 @@ internal class SendStateFactory(
fun getSendNotificationState(notifications: ImmutableList<SendNotification>): SendUiState {
val state = currentStateProvider()
val sendState = state.sendState ?: return state
val hasErrorNotifications = notifications.any { it is SendNotification.Error }
return state.copy(
sendState = state.sendState.copy(
sendState = sendState.copy(
isPrimaryButtonEnabled = !hasErrorNotifications,
notifications = notifications,
showTapHelp = sendState.showTapHelp && notifications.isEmpty(),
),
)
}
fun getHiddenTapHelpState(): SendUiState {
val state = currentStateProvider()
val sendState = state.sendState ?: return state
return state.copy(
sendState = sendState.copy(showTapHelp = false),
)
}
//endregion
}

View file

@ -15,7 +15,6 @@ import com.tangem.features.send.impl.presentation.state.fields.SendTextField
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
import java.math.BigDecimal
/**
@ -29,7 +28,7 @@ internal data class SendUiState(
val amountState: SendStates.AmountState? = null,
val recipientState: SendStates.RecipientState? = null,
val feeState: SendStates.FeeState? = null,
val sendState: SendStates.SendState = SendStates.SendState(),
val sendState: SendStates.SendState? = null,
val isBalanceHidden: Boolean,
val event: StateEvent<SendEvent>,
)
@ -84,14 +83,15 @@ internal sealed class SendStates {
data class SendState(
override val type: SendUiStateType = SendUiStateType.Send,
override val isPrimaryButtonEnabled: Boolean = true,
val isSending: Boolean = false,
val isSuccess: Boolean = false,
val isSubtract: Boolean = false,
val transactionDate: Long = 0L,
val txUrl: String = "",
val ignoreAmountReduce: Boolean = false,
val isFromConfirmation: Boolean = true,
val notifications: ImmutableList<SendNotification> = persistentListOf(),
val isSending: Boolean,
val isSuccess: Boolean,
val isSubtract: Boolean,
val transactionDate: Long,
val txUrl: String,
val ignoreAmountReduce: Boolean,
val isFromConfirmation: Boolean,
val showTapHelp: Boolean,
val notifications: ImmutableList<SendNotification>,
) : SendStates()
}

View file

@ -31,7 +31,7 @@ internal class SendAmountSubtractConverter(
val fiatValue = decimalFiatValue.parseBigDecimal(fiatDecimals)
return state.copy(
sendState = state.sendState.copy(isSubtract = true),
sendState = state.sendState?.copy(isSubtract = true),
amountState = amountState.copy(
amountTextField = amountTextField.copy(
value = cryptoValue,

View file

@ -0,0 +1,25 @@
package com.tangem.features.send.impl.presentation.state.confirm
import com.tangem.features.send.impl.presentation.state.SendStates
import com.tangem.utils.Provider
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.persistentListOf
internal class SendConfirmStateConverter(
private val isTapHelpPreviewEnabledProvider: Provider<Boolean>,
) : Converter<Unit, SendStates.SendState> {
override fun convert(value: Unit): SendStates.SendState {
return SendStates.SendState(
isPrimaryButtonEnabled = true,
isSending = false,
isSuccess = false,
isSubtract = false,
transactionDate = 0L,
txUrl = "",
ignoreAmountReduce = false,
isFromConfirmation = true,
showTapHelp = isTapHelpPreviewEnabledProvider(),
notifications = persistentListOf(),
)
}
}

View file

@ -0,0 +1,53 @@
package com.tangem.features.send.impl.presentation.state.previewdata
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import com.tangem.core.ui.components.currency.tokenicon.TokenIconState
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.tokens.model.Amount
import com.tangem.domain.tokens.model.AmountType
import com.tangem.features.send.impl.presentation.state.SendStates
import com.tangem.features.send.impl.presentation.state.SendUiStateType
import com.tangem.features.send.impl.presentation.state.fields.SendTextField
import kotlinx.collections.immutable.persistentListOf
import java.math.BigDecimal
internal object AmountStatePreviewData {
val amountState = SendStates.AmountState(
type = SendUiStateType.Amount,
isPrimaryButtonEnabled = false,
walletName = "Wallet",
walletBalance = stringReference("123.123"),
tokenIconState = TokenIconState.Loading,
segmentedButtonConfig = persistentListOf(),
amountTextField = SendTextField.AmountField(
value = "123.123123123123123123",
onValueChange = {},
keyboardOptions = KeyboardOptions.Default,
keyboardActions = KeyboardActions.Default,
cryptoAmount = Amount(
currencySymbol = "ETH",
value = BigDecimal(123.123),
decimals = 18,
type = AmountType.CoinType,
),
fiatAmount = Amount(
currencySymbol = "$",
value = BigDecimal(123.123),
decimals = 2,
type = AmountType.CoinType,
),
isFiatValue = false,
fiatValue = "123.123",
isFiatUnavailable = false,
isError = false,
error = TextReference.EMPTY,
),
)
val fiatAmountState = amountState.copy(
amountTextField = amountState.amountTextField.copy(isFiatValue = true),
)
}

View file

@ -0,0 +1,52 @@
package com.tangem.features.send.impl.presentation.state.previewdata
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.AmountType.Coin
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.features.send.impl.presentation.state.SendStates
import com.tangem.features.send.impl.presentation.state.SendUiStateType
import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState
import com.tangem.features.send.impl.presentation.state.fee.FeeType
import kotlinx.collections.immutable.persistentListOf
import java.math.BigDecimal
internal object FeeStatePreviewData {
val feeState = SendStates.FeeState(
type = SendUiStateType.Amount,
isPrimaryButtonEnabled = false,
feeSelectorState = FeeSelectorState.Content(
fees = TransactionFee.Single(
normal = Fee.Common(
amount = Amount(
currencySymbol = "ETH",
value = BigDecimal(123.123),
decimals = 18,
type = Coin,
),
),
),
selectedFee = FeeType.Market,
customValues = persistentListOf(),
),
fee = Fee.Common(
amount = Amount(
currencySymbol = "ETH",
value = BigDecimal(123.123),
decimals = 18,
type = Coin,
),
),
rate = null,
appCurrency = AppCurrency(
code = "USD",
name = "USD",
symbol = "$",
iconSmallUrl = null,
iconMediumUrl = null,
),
isFeeApproximate = false,
notifications = persistentListOf(),
)
}

View file

@ -0,0 +1,28 @@
package com.tangem.features.send.impl.presentation.state.previewdata
import androidx.compose.foundation.text.KeyboardOptions
import com.tangem.core.ui.extensions.stringReference
import com.tangem.features.send.impl.presentation.state.SendStates
import com.tangem.features.send.impl.presentation.state.fields.SendTextField
import kotlinx.collections.immutable.persistentListOf
internal object RecipientStatePreviewData {
val recipientState = SendStates.RecipientState(
addressTextField = SendTextField.RecipientAddress(
value = "0x23948239805671983476598176",
onValueChange = {},
keyboardOptions = KeyboardOptions.Default,
placeholder = stringReference("Placeholder"),
label = stringReference("Recipient"),
isError = false,
error = null,
),
memoTextField = null,
recent = persistentListOf(),
wallets = persistentListOf(),
network = "Ethereum",
isValidating = false,
isPrimaryButtonEnabled = true,
)
}

View file

@ -32,7 +32,8 @@ import com.tangem.features.send.impl.presentation.state.SendUiStateType
@Composable
internal fun SendNavigationButtons(uiState: SendUiState, currentState: SendUiCurrentScreen) {
val isSuccess = uiState.sendState.isSuccess
val sendState = uiState.sendState ?: return
val isSuccess = sendState.isSuccess
val isSendingState = currentState.type == SendUiStateType.Send && !isSuccess
val isSentState = currentState.type == SendUiStateType.Send && isSuccess
Column(
@ -45,7 +46,7 @@ internal fun SendNavigationButtons(uiState: SendUiState, currentState: SendUiCur
) {
SendingText(uiState = uiState, isVisible = isSendingState)
SendDoneButtons(
txUrl = uiState.sendState.txUrl,
txUrl = sendState.txUrl,
onExploreClick = uiState.clickIntents::onExploreClick,
onShareClick = uiState.clickIntents::onShareClick,
isVisible = isSentState,
@ -65,8 +66,9 @@ private fun SendNavigationButton(
modifier: Modifier = Modifier,
) {
val hapticFeedback = LocalHapticFeedback.current
val sendState = uiState.sendState ?: return
val isEditingDisabled = uiState.isEditingDisabled
val isSuccess = uiState.sendState.isSuccess
val isSuccess = sendState.isSuccess
val isFromConfirmation = currentState.isFromConfirmation
val isCorrectScreen = currentState.type == SendUiStateType.Amount || currentState.type == SendUiStateType.Fee
@ -111,7 +113,7 @@ private fun SendNavigationButton(
if (isSendingState) hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
buttonClick()
},
showProgress = uiState.sendState.isSending,
showProgress = sendState.isSending,
modifier = Modifier.fillMaxWidth(),
colors = TangemButtonsDefaults.primaryButtonColors,
)
@ -226,7 +228,7 @@ private fun isButtonEnabled(currentState: SendUiCurrentScreen, uiState: SendUiSt
SendUiStateType.Amount -> uiState.amountState?.isPrimaryButtonEnabled ?: false
SendUiStateType.Recipient -> uiState.recipientState?.isPrimaryButtonEnabled ?: false
SendUiStateType.Fee -> uiState.feeState?.isPrimaryButtonEnabled ?: false
SendUiStateType.Send -> uiState.sendState.isPrimaryButtonEnabled
SendUiStateType.Send -> uiState.sendState?.isPrimaryButtonEnabled ?: false
else -> true
}
}

View file

@ -31,6 +31,7 @@ import kotlinx.coroutines.flow.StateFlow
internal fun SendScreen(uiState: SendUiState, currentStateFlow: StateFlow<SendUiCurrentScreen>) {
val currentState = currentStateFlow.collectAsStateWithLifecycle()
val snackbarHostState = remember { SnackbarHostState() }
val sendState = uiState.sendState ?: return
BackHandler { uiState.clickIntents.onBackClick() }
Column(
modifier = Modifier
@ -44,7 +45,7 @@ internal fun SendScreen(uiState: SendUiState, currentStateFlow: StateFlow<SendUi
SendUiStateType.Amount -> resourceReference(R.string.send_amount_label)
SendUiStateType.Recipient -> resourceReference(R.string.send_recipient_label)
SendUiStateType.Fee -> resourceReference(R.string.common_fee_selector_title)
SendUiStateType.Send -> if (!uiState.sendState.isSuccess) {
SendUiStateType.Send -> if (!sendState.isSuccess) {
resourceReference(R.string.send_summary_title, wrappedList(uiState.cryptoCurrencySymbol))
} else {
null

View file

@ -11,10 +11,14 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import com.tangem.core.ui.components.ResizableText
import com.tangem.core.ui.components.currency.tokenicon.TokenIcon
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.send.impl.presentation.state.SendStates
import com.tangem.features.send.impl.presentation.state.previewdata.AmountStatePreviewData
@Composable
internal fun AmountBlock(
@ -75,4 +79,43 @@ internal fun AmountBlock(
private fun getAmountWithSymbol(amount: String, symbol: String): String {
return "$amount $symbol"
}
}
// region Preview
@Preview
@Composable
private fun AmountBlockPreview_Light(
@PreviewParameter(AmountBlockPreviewProvider::class) value: SendStates.AmountState,
) {
TangemTheme {
AmountBlock(
amountState = value,
isSuccess = false,
isEditingDisabled = false,
onClick = {},
)
}
}
@Preview
@Composable
private fun AmountBlockPreview_Dark(
@PreviewParameter(AmountBlockPreviewProvider::class) value: SendStates.AmountState,
) {
TangemTheme(isDark = true) {
AmountBlock(
amountState = value,
isSuccess = true,
isEditingDisabled = false,
onClick = {},
)
}
}
private class AmountBlockPreviewProvider : PreviewParameterProvider<SendStates.AmountState> {
override val values: Sequence<SendStates.AmountState>
get() = sequenceOf(
AmountStatePreviewData.amountState,
)
}
// endregion

View file

@ -15,6 +15,9 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import com.tangem.core.ui.components.atoms.text.EllipsisText
import com.tangem.core.ui.components.atoms.text.TextEllipsis
import com.tangem.core.ui.res.TangemTheme
@ -23,6 +26,7 @@ import com.tangem.features.send.impl.R
import com.tangem.features.send.impl.presentation.state.SendStates
import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState
import com.tangem.features.send.impl.presentation.state.fee.FeeType
import com.tangem.features.send.impl.presentation.state.previewdata.FeeStatePreviewData
@Composable
internal fun FeeBlock(feeState: SendStates.FeeState, isSuccess: Boolean, onClick: () -> Unit) {
@ -93,4 +97,38 @@ internal fun FeeBlock(feeState: SendStates.FeeState, isSuccess: Boolean, onClick
)
}
}
}
}
// region Preview
@Preview
@Composable
private fun FeeBlockPreview_Light(@PreviewParameter(FeeBlockPreviewProvider::class) value: SendStates.FeeState) {
TangemTheme {
FeeBlock(
feeState = value,
isSuccess = true,
onClick = {},
)
}
}
@Preview
@Composable
private fun FeeBlockPreview_Dark(@PreviewParameter(FeeBlockPreviewProvider::class) value: SendStates.FeeState) {
TangemTheme(isDark = true) {
FeeBlock(
feeState = value,
isSuccess = true,
onClick = {},
)
}
}
private class FeeBlockPreviewProvider : PreviewParameterProvider<SendStates.FeeState> {
override val values: Sequence<SendStates.FeeState>
get() = sequenceOf(
FeeStatePreviewData.feeState,
)
}
// endregion

View file

@ -10,11 +10,15 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import com.tangem.core.ui.components.icons.identicon.IdentIcon
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.send.impl.presentation.state.SendStates
import com.tangem.features.send.impl.presentation.state.fields.SendTextField
import com.tangem.features.send.impl.presentation.state.previewdata.RecipientStatePreviewData
@Composable
internal fun RecipientBlock(
@ -88,4 +92,44 @@ private fun MemoBlock(memo: SendTextField.RecipientMemo?) {
modifier = Modifier.padding(top = TangemTheme.dimens.spacing8),
)
}
}
}
// region Preview
@Preview
@Composable
private fun RecipientBlockPreview_Light(
@PreviewParameter(RecipientBlockPreviewProvider::class) value: SendStates.RecipientState,
) {
TangemTheme {
RecipientBlock(
recipientState = value,
isSuccess = true,
isEditingDisabled = false,
onClick = {},
)
}
}
@Preview
@Composable
private fun RecipientBlockPreview_Dark(
@PreviewParameter(RecipientBlockPreviewProvider::class) value: SendStates.RecipientState,
) {
TangemTheme(isDark = true) {
RecipientBlock(
recipientState = value,
isSuccess = true,
isEditingDisabled = false,
onClick = {},
)
}
}
private class RecipientBlockPreviewProvider : PreviewParameterProvider<SendStates.RecipientState> {
override val values: Sequence<SendStates.RecipientState>
get() = sequenceOf(
RecipientStatePreviewData.recipientState,
)
}
// endregion

View file

@ -1,6 +1,7 @@
package com.tangem.features.send.impl.presentation.ui.send
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.*
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
@ -10,6 +11,8 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@ -22,14 +25,16 @@ import com.tangem.features.send.impl.R
import com.tangem.features.send.impl.presentation.state.SendNotification
import com.tangem.features.send.impl.presentation.state.SendUiState
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.delay
private const val TAP_HELP_KEY = "TAP_HELP_KEY"
private const val BLOCKS_KEY = "BLOCKS_KEY"
private const val TAP_HELP_ANIMATION_DELAY = 500L
@Suppress("LongMethod")
@Composable
internal fun SendContent(uiState: SendUiState) {
val sendState = uiState.sendState
val sendState = uiState.sendState ?: return
LazyColumn(
modifier = Modifier
.fillMaxSize()
@ -37,7 +42,7 @@ internal fun SendContent(uiState: SendUiState) {
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
) {
blocks(uiState)
tapHelp(isDisplay = sendState.notifications.isEmpty() && !uiState.sendState.isSuccess)
tapHelp(isDisplay = sendState.showTapHelp)
notifications(sendState.notifications)
}
}
@ -46,7 +51,7 @@ private fun LazyListScope.blocks(uiState: SendUiState) {
val amountState = uiState.amountState ?: return
val recipientState = uiState.recipientState ?: return
val feeState = uiState.feeState ?: return
val sendState = uiState.sendState
val sendState = uiState.sendState ?: return
val isSuccess = sendState.isSuccess
val timestamp = sendState.transactionDate
@ -84,8 +89,22 @@ private fun LazyListScope.blocks(uiState: SendUiState) {
@OptIn(ExperimentalFoundationApi::class)
private fun LazyListScope.tapHelp(isDisplay: Boolean, modifier: Modifier = Modifier) {
if (isDisplay) {
item(key = TAP_HELP_KEY) {
item(key = TAP_HELP_KEY) {
val animationState = remember { MutableTransitionState(false) }
LaunchedEffect(key1 = isDisplay) {
delay(TAP_HELP_ANIMATION_DELAY)
animationState.targetState = isDisplay
}
AnimatedVisibility(
visibleState = animationState,
label = "Tap Help Animation",
enter = slideInVertically(
initialOffsetY = { it / 2 },
).plus(fadeIn()),
exit = slideOutVertically().plus(fadeOut()),
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier

View file

@ -16,6 +16,8 @@ import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.qrscanning.usecases.ParseQrCodeUseCase
import com.tangem.domain.redux.LegacyAction
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.settings.IsSendTapHelpEnabledUseCase
import com.tangem.domain.settings.NeverShowTapHelpUseCase
import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.error.CurrencyStatusError
import com.tangem.domain.tokens.model.CryptoCurrency
@ -85,6 +87,8 @@ internal class SendViewModel @Inject constructor(
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
private val parseQrCodeUseCase: ParseQrCodeUseCase,
private val isSendTapHelpEnabledUseCase: IsSendTapHelpEnabledUseCase,
private val neverShowTapHelpUseCase: NeverShowTapHelpUseCase,
currencyChecksRepository: CurrencyChecksRepository,
isFeeApproximateUseCase: IsFeeApproximateUseCase,
getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase,
@ -120,6 +124,7 @@ internal class SendViewModel @Inject constructor(
feeCryptoCurrencyStatusProvider = Provider { feeCryptoCurrencyStatus },
validateWalletMemoUseCase = validateWalletMemoUseCase,
getExplorerTransactionUrlUseCase = getExplorerTransactionUrlUseCase,
isTapHelpPreviewEnabledProvider = Provider { isTapHelpPreviewEnabled },
)
private val amountStateFactory = AmountStateFactory(
@ -173,6 +178,7 @@ internal class SendViewModel @Inject constructor(
private var userWallet: UserWallet by Delegates.notNull()
private var isAmountSubtractAvailable: Boolean = false
private var isTapHelpPreviewEnabled: Boolean = false
private var coinCryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull()
private var cryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull()
private var feeCryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull()
@ -192,6 +198,7 @@ internal class SendViewModel @Inject constructor(
init {
subscribeOnCurrencyStatusUpdates()
subscribeOnBalanceHidden()
getTapHelpPreviewAvailability()
}
override fun onCreate(owner: LifecycleOwner) {
@ -286,6 +293,12 @@ internal class SendViewModel @Inject constructor(
}
}
private fun getTapHelpPreviewAvailability() {
viewModelScope.launch(dispatchers.main) {
isTapHelpPreviewEnabled = isSendTapHelpEnabledUseCase().getOrElse { false }
}
}
private fun getCoinCurrencyStatusUpdates(isSingleWalletWithToken: Boolean) = getNetworkCoinStatusUseCase(
userWalletId = userWalletId,
networkId = cryptoCurrency.network.id,
@ -344,7 +357,7 @@ internal class SendViewModel @Inject constructor(
feeCryptoCurrencyStatus = feeCurrencyStatus
when {
uiState.sendState.isSuccess -> {
uiState.sendState?.isSuccess == true -> {
stateRouter.showSend()
}
transactionId != null && amount != null && destinationAddress != null -> {
@ -454,7 +467,7 @@ internal class SendViewModel @Inject constructor(
// region screen state navigation
override fun popBackStack() = stateRouter.popBackStack()
override fun onBackClick() = stateRouter.onBackClick(uiState.sendState.isSuccess)
override fun onBackClick() = stateRouter.onBackClick(isSuccess = uiState.sendState?.isSuccess == true)
override fun onNextClick() {
val currentState = stateRouter.currentState.value
val isCurrentFee = currentState.type == SendUiStateType.Fee
@ -656,7 +669,7 @@ internal class SendViewModel @Inject constructor(
// region send state clicks
override fun onSendClick() {
val sendState = uiState.sendState
val sendState = uiState.sendState ?: return
if (sendState.isSuccess) popBackStack()
uiState = stateFactory.getSendingStateUpdate(isSending = true)
@ -670,16 +683,20 @@ internal class SendViewModel @Inject constructor(
override fun showAmount() {
stateRouter.showAmount(isFromConfirmation = true)
setNeverToShowTapHelp()
analyticsEventHandler.send(SendAnalyticEvents.ScreenReopened(SendScreenSource.Amount))
}
override fun showRecipient() {
stateRouter.showRecipient(isFromConfirmation = true)
uiState = stateFactory.getHiddenTapHelpState()
setNeverToShowTapHelp()
analyticsEventHandler.send(SendAnalyticEvents.ScreenReopened(SendScreenSource.Address))
}
override fun showFee() {
stateRouter.showFee(isFromConfirmation = true)
setNeverToShowTapHelp()
analyticsEventHandler.send(SendAnalyticEvents.ScreenReopened(SendScreenSource.Fee))
}
@ -688,8 +705,9 @@ internal class SendViewModel @Inject constructor(
}
override fun onExploreClick() {
val sendState = uiState.sendState ?: return
analyticsEventHandler.send(SendAnalyticEvents.ExploreButtonClicked)
innerRouter.openUrl(uiState.sendState.txUrl)
innerRouter.openUrl(sendState.txUrl)
}
override fun onShareClick() {
@ -772,8 +790,9 @@ internal class SendViewModel @Inject constructor(
}
private fun onCheckFeeUpdate() {
val isSuccess = uiState.sendState.isSuccess
val noErrorNotifications = uiState.sendState.notifications.none { it is SendNotification.Error }
val sendState = uiState.sendState ?: return
val isSuccess = sendState.isSuccess
val noErrorNotifications = sendState.notifications.none { it is SendNotification.Error }
if (!isSuccess && noErrorNotifications) {
viewModelScope.launch(dispatchers.main) {
@ -809,6 +828,13 @@ internal class SendViewModel @Inject constructor(
}
}
}
private fun setNeverToShowTapHelp() {
viewModelScope.launch(dispatchers.main) {
neverShowTapHelpUseCase()
}
uiState = stateFactory.getHiddenTapHelpState()
}
// endregion
companion object {