Updated on 2026-08-14
This commit is contained in:
commit
2c739e2a4f
23 changed files with 455 additions and 40 deletions
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,5 +5,6 @@ plugins {
|
|||
|
||||
dependencies {
|
||||
implementation(deps.kotlin.coroutines)
|
||||
implementation(deps.arrow.core)
|
||||
implementation(projects.domain.balanceHiding.models)
|
||||
}
|
||||
|
|
@ -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() }
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -13,4 +13,8 @@ interface SettingsRepository {
|
|||
|
||||
@Throws
|
||||
suspend fun deleteDeprecatedLogs(maxSize: Int)
|
||||
|
||||
suspend fun isSendTapHelpPreviewEnabled(): Boolean
|
||||
|
||||
suspend fun setSendTapHelpPreviewAvailability(isEnabled: Boolean)
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
|
|
@ -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(),
|
||||
)
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue