Updated on 2026-08-14
This commit is contained in:
parent
f6b98ab9fa
commit
db16ba6cef
20 changed files with 618 additions and 96 deletions
|
|
@ -98,7 +98,7 @@ class TangemLoggingInitializer(
|
|||
BlockchainSdkConfig.serializer(),
|
||||
environmentConfig.blockchainSdkConfig,
|
||||
)
|
||||
// Drop URL-shaped values (e.g. public endpoint URLs from BlockchainSdkConfig like
|
||||
// Drop URL-shaped drawable (e.g. public endpoint URLs from BlockchainSdkConfig like
|
||||
// kaspaSecondaryApiUrl); they are not secrets and would obscure unrelated requests in logs.
|
||||
val values = JsonStringValuesExtractor.extract(json)
|
||||
.filter { it.isNotBlank() && !it.startsWith("http", ignoreCase = true) }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths>
|
||||
<files-path name="files" path="/"/>
|
||||
<cache-path name="shared_images" path="." />
|
||||
</paths>
|
||||
|
|
@ -2,9 +2,9 @@ package com.tangem.common.ui.expressStatus
|
|||
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM
|
||||
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
data class ExpressStatusBottomSheetConfig(
|
||||
|
|
@ -18,7 +18,7 @@ fun ExpressStatusBottomSheet(config: TangemBottomSheetConfig) {
|
|||
containerColor = TangemTheme.colors.background.tertiary,
|
||||
) { content: ExpressStatusBottomSheetConfig ->
|
||||
when (val state = content.value) {
|
||||
is ExpressTransactionStateUM.OnrampUM -> OnrampStatusBottomSheetContent(state)
|
||||
is ExpressTransactionStateUM.OnrampUM -> OnrampStatusBottomSheetContent(state, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,13 +3,7 @@ package com.tangem.common.ui.expressStatus
|
|||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
|
|
@ -22,11 +16,7 @@ import androidx.compose.ui.platform.testTag
|
|||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressLinkUM
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressStatusUM
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateIconUM
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateInfoUM
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM
|
||||
import com.tangem.common.ui.expressStatus.state.*
|
||||
import com.tangem.core.ui.components.atoms.text.EllipsisText
|
||||
import com.tangem.core.ui.components.atoms.text.TextEllipsis
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
|
||||
|
|
@ -221,13 +211,17 @@ private val PreviewExpressTransactionState: ExpressTransactionStateUM = object :
|
|||
onDisposeExpressStatus = {},
|
||||
iconState = ExpressTransactionStateIconUM.None,
|
||||
toAmount = stringReference("0,11441958 BTC"),
|
||||
toAmountValue = "0.11441958".toBigDecimal(),
|
||||
toFiatAmount = null,
|
||||
toAmountSymbol = "BTC",
|
||||
toCurrencyIcon = CurrencyIconState.Loading,
|
||||
toAddress = "0x",
|
||||
fromAmount = stringReference("100 SOL"),
|
||||
fromAmountValue = "100".toBigDecimal(),
|
||||
fromFiatAmount = null,
|
||||
fromAmountSymbol = "SOL",
|
||||
fromCurrencyIcon = CurrencyIconState.Loading,
|
||||
fromAddress = "0x",
|
||||
)
|
||||
}
|
||||
// endregion
|
||||
|
|
@ -9,18 +9,16 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.SpacerH10
|
||||
import com.tangem.core.ui.components.SpacerH12
|
||||
import com.tangem.core.ui.components.SpacerH16
|
||||
import com.tangem.core.ui.components.SpacerH24
|
||||
import com.tangem.core.ui.components.*
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
@Composable
|
||||
fun OnrampStatusBottomSheetContent(state: ExpressTransactionStateUM.OnrampUM) {
|
||||
fun OnrampStatusBottomSheetContent(state: ExpressTransactionStateUM.OnrampUM, isExpressShareButtonEnabled: Boolean) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
|
|
@ -70,6 +68,10 @@ fun OnrampStatusBottomSheetContent(state: ExpressTransactionStateUM.OnrampUM) {
|
|||
isAutoDisposable = state.activeStatus.isAutoDisposable,
|
||||
onClick = state.info.onDisposeExpressStatus,
|
||||
)
|
||||
SpacerH24()
|
||||
if (isExpressShareButtonEnabled) {
|
||||
SpacerH(80.dp)
|
||||
} else {
|
||||
SpacerH24()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,13 @@
|
|||
package com.tangem.common.ui.expressStatus.state
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.onramp.model.OnrampStatus
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Immutable
|
||||
interface ExpressTransactionStateUM {
|
||||
|
||||
val info: ExpressTransactionStateInfoUM
|
||||
|
|
@ -35,13 +38,17 @@ data class ExpressTransactionStateInfoUM(
|
|||
val onDisposeExpressStatus: () -> Unit,
|
||||
val iconState: ExpressTransactionStateIconUM,
|
||||
val toAmount: TextReference,
|
||||
val toAmountValue: BigDecimal,
|
||||
val toFiatAmount: TextReference?,
|
||||
val toAmountSymbol: String,
|
||||
val toCurrencyIcon: CurrencyIconState,
|
||||
val toAddress: String,
|
||||
val fromAmount: TextReference,
|
||||
val fromAmountValue: BigDecimal,
|
||||
val fromFiatAmount: TextReference?,
|
||||
val fromAmountSymbol: String,
|
||||
val fromCurrencyIcon: CurrencyIconState,
|
||||
val fromAddress: String?,
|
||||
) {
|
||||
val subtitle: TextReference
|
||||
get() = buildExpressStatusSubtitle(activeStatus = activeStatus, date = timestampAgoFormatted)
|
||||
|
|
|
|||
|
|
@ -138,5 +138,9 @@
|
|||
{
|
||||
"name": "TWI_83_ADDRESS_BOOK_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "AND_15489_EXPRESS_SHARE_BUTTON_ENABLED",
|
||||
"version": "undefined"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -8,4 +8,5 @@ interface SwapFeatureToggles {
|
|||
val isSwapProviderFilterEnabled: Boolean
|
||||
val isSwapRateExperienceEnabled: Boolean
|
||||
val isSwapPredefinedButtonsEnabled: Boolean
|
||||
val isExpressShareButtonEnabled: Boolean
|
||||
}
|
||||
|
|
@ -6,34 +6,45 @@ import com.tangem.features.swap.SwapFeatureToggles
|
|||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultSwapFeatureToggles @Inject constructor(
|
||||
featureTogglesManager: FeatureTogglesManager,
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : SwapFeatureToggles {
|
||||
|
||||
override val isYieldSwapEnabled: Boolean = featureTogglesManager.isFeatureEnabled(
|
||||
toggle = FeatureToggles.TWI_1326_YIELD_MODE_SWAP_ENABLED,
|
||||
)
|
||||
override val isYieldSwapEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(
|
||||
toggle = FeatureToggles.TWI_1326_YIELD_MODE_SWAP_ENABLED,
|
||||
)
|
||||
|
||||
override val isSwapSwitchToTransferEnabled: Boolean = featureTogglesManager.isFeatureEnabled(
|
||||
toggle = FeatureToggles.AND_15207_SWAP_SWITCH_TO_TRANSFER_ENABLED,
|
||||
)
|
||||
override val isSwapSwitchToTransferEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(
|
||||
toggle = FeatureToggles.AND_15207_SWAP_SWITCH_TO_TRANSFER_ENABLED,
|
||||
)
|
||||
|
||||
override val isSwapIntegratedApproveEnabled: Boolean = featureTogglesManager.isFeatureEnabled(
|
||||
toggle = FeatureToggles.AND_15120_SWAP_INTEGRATED_APPROVE,
|
||||
)
|
||||
override val isSwapIntegratedApproveEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(
|
||||
toggle = FeatureToggles.AND_15120_SWAP_INTEGRATED_APPROVE,
|
||||
)
|
||||
|
||||
override val isSwapAbEnabled: Boolean = featureTogglesManager.isFeatureEnabled(
|
||||
toggle = FeatureToggles.SWAP_AB_ENABLED,
|
||||
)
|
||||
override val isSwapAbEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(
|
||||
toggle = FeatureToggles.SWAP_AB_ENABLED,
|
||||
)
|
||||
|
||||
override val isSwapProviderFilterEnabled: Boolean = featureTogglesManager.isFeatureEnabled(
|
||||
toggle = FeatureToggles.AND_15009_SWAP_PROVIDER_FILTER_ENABLED,
|
||||
)
|
||||
override val isSwapProviderFilterEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(
|
||||
toggle = FeatureToggles.AND_15009_SWAP_PROVIDER_FILTER_ENABLED,
|
||||
)
|
||||
|
||||
override val isSwapRateExperienceEnabled: Boolean = featureTogglesManager.isFeatureEnabled(
|
||||
toggle = FeatureToggles.AND_15103_SWAP_RATE_EXPERIENCE_ENABLED,
|
||||
)
|
||||
override val isSwapRateExperienceEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(
|
||||
toggle = FeatureToggles.AND_15103_SWAP_RATE_EXPERIENCE_ENABLED,
|
||||
)
|
||||
|
||||
override val isSwapPredefinedButtonsEnabled: Boolean = featureTogglesManager.isFeatureEnabled(
|
||||
toggle = FeatureToggles.AND_15122_SWAP_PREDEFINED_BUTTONS_ENABLED,
|
||||
)
|
||||
override val isSwapPredefinedButtonsEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(
|
||||
toggle = FeatureToggles.AND_15122_SWAP_PREDEFINED_BUTTONS_ENABLED,
|
||||
)
|
||||
override val isExpressShareButtonEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(
|
||||
toggle = FeatureToggles.AND_15489_EXPRESS_SHARE_BUTTON_ENABLED,
|
||||
)
|
||||
}
|
||||
|
|
@ -3,14 +3,7 @@ package com.tangem.features.tangempay.components.express
|
|||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.common.ui.expressStatus.expressTransactionsItemsLegacy
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressLinkUM
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressStatusItemState
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressStatusItemUM
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressStatusUM
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateIconUM
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateInfoUM
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressTransactionsBlockState
|
||||
import com.tangem.common.ui.expressStatus.state.*
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.onramp.model.OnrampStatus
|
||||
|
|
@ -122,13 +115,17 @@ internal class PreviewEmptyExpressTransactionsComponent : ExpressTransactionsCom
|
|||
onDisposeExpressStatus = {},
|
||||
iconState = iconState,
|
||||
toAmount = TextReference.Str(toAmount),
|
||||
toAmountValue = toAmount.toBigDecimal(),
|
||||
toFiatAmount = null,
|
||||
toAmountSymbol = toSymbol,
|
||||
toCurrencyIcon = CurrencyIconState.Empty(),
|
||||
toAddress = "",
|
||||
fromAmount = TextReference.Str(fromAmount),
|
||||
fromAmountValue = fromAmount.toBigDecimal(),
|
||||
fromFiatAmount = null,
|
||||
fromAmountSymbol = fromSymbol,
|
||||
fromCurrencyIcon = CurrencyIconState.Empty(),
|
||||
fromAddress = "",
|
||||
),
|
||||
providerName = "Preview Provider",
|
||||
providerImageUrl = "",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory
|
||||
|
||||
import com.tangem.common.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
|
||||
import com.tangem.common.ui.expressStatus.state.*
|
||||
import com.tangem.common.ui.expressStatus.toActiveStatusText
|
||||
import com.tangem.common.ui.expressStatus.toIconState
|
||||
|
|
@ -7,7 +8,6 @@ import com.tangem.common.ui.notifications.ExpressNotificationsUM
|
|||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.common.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
|
|
@ -41,6 +41,7 @@ internal class TokenDetailsOnrampTransactionStateConverter(
|
|||
|
||||
override fun convert(value: OnrampTransaction): ExpressTransactionStateUM.OnrampUM {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
val statusValue = cryptoCurrencyStatus?.value
|
||||
val appCurrency = appCurrencyProvider()
|
||||
return ExpressTransactionStateUM.OnrampUM(
|
||||
info = ExpressTransactionStateInfoUM(
|
||||
|
|
@ -63,8 +64,9 @@ internal class TokenDetailsOnrampTransactionStateConverter(
|
|||
toAmount = stringReference(
|
||||
value.toAmount.format { crypto(cryptoCurrency) },
|
||||
),
|
||||
toAmountValue = value.toAmount,
|
||||
toFiatAmount = stringReference(
|
||||
cryptoCurrencyStatus?.value?.fiatRate?.multiply(value.toAmount).format {
|
||||
statusValue?.fiatRate?.multiply(value.toAmount).format {
|
||||
fiat(
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
|
|
@ -73,6 +75,7 @@ internal class TokenDetailsOnrampTransactionStateConverter(
|
|||
),
|
||||
toAmountSymbol = cryptoCurrency.symbol,
|
||||
toCurrencyIcon = iconStateConverter.convert(cryptoCurrency),
|
||||
toAddress = statusValue?.networkAddress?.defaultAddress?.value.orEmpty(),
|
||||
fromAmount = stringReference(
|
||||
value.fromAmount.format {
|
||||
fiat(
|
||||
|
|
@ -81,12 +84,14 @@ internal class TokenDetailsOnrampTransactionStateConverter(
|
|||
)
|
||||
},
|
||||
),
|
||||
fromAmountValue = value.fromAmount,
|
||||
fromFiatAmount = null,
|
||||
fromAmountSymbol = value.fromCurrency.code,
|
||||
fromCurrencyIcon = CurrencyIconState.FiatIcon(
|
||||
url = value.fromCurrency.image,
|
||||
fallbackResId = R.drawable.ic_currency_24,
|
||||
),
|
||||
fromAddress = null,
|
||||
iconState = value.status.toIconState(),
|
||||
onGoToProviderClick = { url ->
|
||||
analyticsEventHandler.send(TokenOnrampAnalyticsEvent.GoToProvider())
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory
|
||||
|
||||
import com.tangem.common.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressLinkUM
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressStatusUM
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateIconUM
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateInfoUM
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.common.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
|
|
@ -17,9 +17,9 @@ import com.tangem.core.ui.utils.mapFormattedDate
|
|||
import com.tangem.core.ui.utils.toDateFormatWithTodayYesterday
|
||||
import com.tangem.core.ui.utils.toTimeFormat
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.quote.QuoteStatus
|
||||
import com.tangem.domain.models.quote.mapData
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.tokens.model.analytics.TokenExchangeAnalyticsEvent
|
||||
import com.tangem.feature.swap.domain.models.domain.ExchangeStatus
|
||||
|
|
@ -34,11 +34,11 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.E
|
|||
import com.tangem.features.tokendetails.impl.R
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
import java.math.BigDecimal
|
||||
|
||||
// Fixme [REDACTED_JIRA]
|
||||
|
|
@ -59,7 +59,7 @@ internal class TokenDetailsSwapTransactionsStateConverter(
|
|||
|
||||
fun convert(
|
||||
savedTransactions: List<SavedSwapTransactionListModel>,
|
||||
quoteStatuses: Set<QuoteStatus>,
|
||||
accountStatuses: Map<Account, CryptoCurrencyStatus>,
|
||||
): PersistentList<ExchangeUM> {
|
||||
val result = mutableListOf<ExchangeUM>()
|
||||
|
||||
|
|
@ -69,23 +69,25 @@ internal class TokenDetailsSwapTransactionsStateConverter(
|
|||
val fromCryptoCurrency = swapTransaction.fromCryptoCurrency
|
||||
val toCryptoCurrencyRawId = swapTransaction.toCryptoCurrency.id.rawCurrencyId
|
||||
val fromCryptoCurrencyRawId = swapTransaction.fromCryptoCurrency.id.rawCurrencyId
|
||||
|
||||
var fromCryptoCurrencyStatus: CryptoCurrencyStatus? = null
|
||||
var toCryptoCurrencyStatus: CryptoCurrencyStatus? = null
|
||||
swapTransaction.transactions.forEach { transaction ->
|
||||
val toAmount = transaction.toCryptoAmount
|
||||
val fromAmount = transaction.fromCryptoAmount
|
||||
var toFiatAmount: BigDecimal? = null
|
||||
var fromFiatAmount: BigDecimal? = null
|
||||
quoteStatuses.forEach { quote ->
|
||||
quote.mapData {
|
||||
if (quote.rawCurrencyId == toCryptoCurrencyRawId) {
|
||||
toFiatAmount = fiatRate.multiply(toAmount)
|
||||
}
|
||||
accountStatuses.forEach { (account, cryptoCurrencyStatus) ->
|
||||
if (cryptoCurrencyStatus.currency.id.rawCurrencyId == fromCryptoCurrencyRawId &&
|
||||
account.userWalletId.stringValue == swapTransaction.fromUserWalletId
|
||||
) {
|
||||
fromFiatAmount = cryptoCurrencyStatus.value.fiatRate?.multiply(fromAmount)
|
||||
fromCryptoCurrencyStatus = cryptoCurrencyStatus
|
||||
}
|
||||
|
||||
quote.mapData {
|
||||
if (quote.rawCurrencyId == fromCryptoCurrencyRawId) {
|
||||
fromFiatAmount = fiatRate.multiply(fromAmount)
|
||||
}
|
||||
if (cryptoCurrencyStatus.currency.id.rawCurrencyId == toCryptoCurrencyRawId &&
|
||||
account.userWalletId.stringValue == swapTransaction.toUserWalletId
|
||||
) {
|
||||
toFiatAmount = cryptoCurrencyStatus.value.fiatRate?.multiply(toAmount)
|
||||
toCryptoCurrencyStatus = cryptoCurrencyStatus
|
||||
}
|
||||
}
|
||||
val statusModel = transaction.status
|
||||
|
|
@ -111,6 +113,8 @@ internal class TokenDetailsSwapTransactionsStateConverter(
|
|||
transaction = transaction,
|
||||
toCryptoCurrency = toCryptoCurrency,
|
||||
fromCryptoCurrency = fromCryptoCurrency,
|
||||
fromCryptoCurrencyStatus = fromCryptoCurrencyStatus,
|
||||
toCryptoCurrencyStatus = toCryptoCurrencyStatus,
|
||||
toFiatAmount = toFiatAmount,
|
||||
fromFiatAmount = fromFiatAmount,
|
||||
),
|
||||
|
|
@ -149,14 +153,19 @@ internal class TokenDetailsSwapTransactionsStateConverter(
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
private fun createStateInfo(
|
||||
transaction: SavedSwapTransactionModel,
|
||||
toCryptoCurrency: CryptoCurrency,
|
||||
fromCryptoCurrency: CryptoCurrency,
|
||||
fromCryptoCurrencyStatus: CryptoCurrencyStatus?,
|
||||
toCryptoCurrencyStatus: CryptoCurrencyStatus?,
|
||||
toFiatAmount: BigDecimal?,
|
||||
fromFiatAmount: BigDecimal?,
|
||||
): ExpressTransactionStateInfoUM {
|
||||
val timestamp = transaction.timestamp
|
||||
val fromStatusValue = fromCryptoCurrencyStatus?.value
|
||||
val toStatusValue = toCryptoCurrencyStatus?.value
|
||||
return ExpressTransactionStateInfoUM(
|
||||
title = resourceReference(R.string.express_exchange_by, wrappedList(transaction.provider.name)),
|
||||
txId = transaction.txId,
|
||||
|
|
@ -169,13 +178,17 @@ internal class TokenDetailsSwapTransactionsStateConverter(
|
|||
timestampAgoFormatted = mapFormattedDate(timestamp),
|
||||
activeStatus = getActiveStatusText(transaction.status?.status),
|
||||
toAmount = getCryptoAmount(transaction.toCryptoAmount, toCryptoCurrency),
|
||||
toAmountValue = transaction.toCryptoAmount,
|
||||
toFiatAmount = getFiatAmount(toFiatAmount),
|
||||
toCurrencyIcon = iconStateConverter.convert(toCryptoCurrency),
|
||||
toAmountSymbol = toCryptoCurrency.symbol,
|
||||
toAddress = toStatusValue?.networkAddress?.defaultAddress?.value.orEmpty(),
|
||||
fromAmount = getCryptoAmount(transaction.fromCryptoAmount, fromCryptoCurrency),
|
||||
fromAmountValue = transaction.fromCryptoAmount,
|
||||
fromFiatAmount = getFiatAmount(fromFiatAmount),
|
||||
fromCurrencyIcon = iconStateConverter.convert(fromCryptoCurrency),
|
||||
fromAmountSymbol = fromCryptoCurrency.symbol,
|
||||
fromAddress = fromStatusValue?.networkAddress?.defaultAddress?.value.orEmpty(),
|
||||
onClick = { clickIntents.onExpressTransactionClick(transaction.txId) },
|
||||
onGoToProviderClick = { url ->
|
||||
analyticsEventsHandler.send(
|
||||
|
|
|
|||
|
|
@ -8,12 +8,13 @@ import com.tangem.datasource.local.swap.SwapTransactionStatusStore
|
|||
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
|
||||
import com.tangem.domain.account.status.usecase.ManageCryptoCurrenciesUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.quote.QuoteStatus
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.quotes.QuotesRepository
|
||||
import com.tangem.domain.pay.usecase.GetPaymentAccountCryptoCurrencyStatusUseCase
|
||||
import com.tangem.domain.tokens.model.analytics.TokenExchangeAnalyticsEvent
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.feature.swap.domain.SwapTransactionRepository
|
||||
|
|
@ -37,8 +38,8 @@ import kotlinx.coroutines.flow.map
|
|||
internal class ExchangeStatusFactory @AssistedInject constructor(
|
||||
private val swapTransactionRepository: SwapTransactionRepository,
|
||||
private val swapRepository: SwapRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
private val getAccountCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase,
|
||||
private val getPaymentAccountCryptoCurrencyStatusUseCase: GetPaymentAccountCryptoCurrencyStatusUseCase,
|
||||
private val manageCryptoCurrenciesUseCase: ManageCryptoCurrenciesUseCase,
|
||||
private val swapTransactionStatusStore: SwapTransactionStatusStore,
|
||||
private val analyticsEventsHandler: AnalyticsEventHandler,
|
||||
|
|
@ -62,19 +63,23 @@ internal class ExchangeStatusFactory @AssistedInject constructor(
|
|||
operator fun invoke(): Flow<PersistentList<ExchangeUM>> {
|
||||
return swapTransactionRepository.getTransactions(
|
||||
userWallet = userWallet,
|
||||
|
||||
cryptoCurrencyId = cryptoCurrency.id,
|
||||
).conflate()
|
||||
.map { savedTransactions ->
|
||||
val quotes = savedTransactions
|
||||
?.flatMap { setOf(it.fromCryptoCurrency.id, it.toCryptoCurrency.id) }
|
||||
?.toSet()
|
||||
?.getQuotesOrEmpty()
|
||||
?: emptySet()
|
||||
val accountStatuses = savedTransactions
|
||||
?.flatMap { swapTransaction ->
|
||||
setOf(
|
||||
swapTransaction.fromAccount to swapTransaction.fromCryptoCurrency,
|
||||
swapTransaction.toAccount to swapTransaction.toCryptoCurrency,
|
||||
)
|
||||
}
|
||||
?.toMap()
|
||||
?.getStatuses()
|
||||
.orEmpty()
|
||||
|
||||
getExchangeStatusState(
|
||||
savedTransactions = savedTransactions,
|
||||
quoteStatuses = quotes,
|
||||
accountStatuses = accountStatuses,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -194,7 +199,7 @@ internal class ExchangeStatusFactory @AssistedInject constructor(
|
|||
|
||||
private fun getExchangeStatusState(
|
||||
savedTransactions: List<SavedSwapTransactionListModel>?,
|
||||
quoteStatuses: Set<QuoteStatus>,
|
||||
accountStatuses: Map<Account, CryptoCurrencyStatus>,
|
||||
): PersistentList<ExchangeUM> {
|
||||
if (savedTransactions == null) {
|
||||
return persistentListOf()
|
||||
|
|
@ -202,7 +207,7 @@ internal class ExchangeStatusFactory @AssistedInject constructor(
|
|||
|
||||
return swapTransactionsStateConverter.convert(
|
||||
savedTransactions = savedTransactions,
|
||||
quoteStatuses = quoteStatuses,
|
||||
accountStatuses = accountStatuses,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -226,12 +231,24 @@ internal class ExchangeStatusFactory @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun Set<CryptoCurrency.ID>.getQuotesOrEmpty(): Set<QuoteStatus> {
|
||||
val rawIds = mapNotNull { it.rawCurrencyId }.toSet()
|
||||
private suspend fun Map<Account?, CryptoCurrency>.getStatuses(): Map<Account, CryptoCurrencyStatus> {
|
||||
return mapNotNull { (account, cryptoCurrency) ->
|
||||
when (account) {
|
||||
is Account.CryptoPortfolio -> {
|
||||
val (cryptoPortfolioAccount, currencyStatus) = getAccountCurrencyStatusUseCase.invokeSync(
|
||||
userWalletId = account.userWalletId,
|
||||
currency = cryptoCurrency,
|
||||
).getOrNull() ?: return@mapNotNull null
|
||||
|
||||
return runCatching { quotesRepository.getMultiQuoteSyncOrNull(currenciesIds = rawIds) }
|
||||
.getOrNull()
|
||||
.orEmpty()
|
||||
cryptoPortfolioAccount to currencyStatus
|
||||
}
|
||||
is Account.Payment -> getPaymentAccountCryptoCurrencyStatusUseCase.invokeSync(
|
||||
userWalletId = account.userWalletId,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
).getOrNull()
|
||||
else -> null
|
||||
}
|
||||
}.toMap()
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.model.ExpressTr
|
|||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.ExchangeStatusNotification
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExchangeUM
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.ExpressStatusBottomSheet
|
||||
import com.tangem.features.swap.SwapFeatureToggles
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.assisted.Assisted
|
||||
|
|
@ -45,6 +46,7 @@ internal class ExpressStatusFactory @AssistedInject constructor(
|
|||
private val analyticsEventsHandler: AnalyticsEventHandler,
|
||||
onrampStatusFactory: OnrampStatusFactory.Factory,
|
||||
exchangeStatusFactory: ExchangeStatusFactory.Factory,
|
||||
private val swapFeatureToggles: SwapFeatureToggles,
|
||||
) {
|
||||
|
||||
private val exchangeStatusFactory by lazy(mode = LazyThreadSafetyMode.NONE) {
|
||||
|
|
@ -212,6 +214,7 @@ internal class ExpressStatusFactory @AssistedInject constructor(
|
|||
is ExpressStatusBottomSheetConfig -> ExpressStatusBottomSheet(
|
||||
config = this,
|
||||
extraContent = extraContent,
|
||||
isExpressShareButtonEnabled = swapFeatureToggles.isExpressShareButtonEnabled,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,18 @@
|
|||
package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig
|
||||
import com.tangem.common.ui.expressStatus.OnrampStatusBottomSheetContent
|
||||
import com.tangem.common.ui.expressStatus.state.*
|
||||
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM
|
||||
import com.tangem.core.ui.components.BottomFade
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExchangeUM
|
||||
|
|
@ -17,15 +21,44 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.e
|
|||
@Composable
|
||||
internal fun ExpressStatusBottomSheet(
|
||||
config: TangemBottomSheetConfig,
|
||||
isExpressShareButtonEnabled: Boolean,
|
||||
extraContent: (@Composable () -> Unit)? = null,
|
||||
) {
|
||||
TangemBottomSheet(
|
||||
config = config,
|
||||
containerColor = TangemTheme.colors.background.tertiary,
|
||||
) { content: ExpressStatusBottomSheetConfig ->
|
||||
when (val state = content.value) {
|
||||
is ExpressTransactionStateUM.OnrampUM -> OnrampStatusBottomSheetContent(state)
|
||||
is ExchangeUM -> ExchangeStatusBottomSheetContent(state = state, extraContent = extraContent)
|
||||
if (isExpressShareButtonEnabled) {
|
||||
Box {
|
||||
when (val state = content.value) {
|
||||
is ExpressTransactionStateUM.OnrampUM -> OnrampStatusBottomSheetContent(
|
||||
state = state,
|
||||
isExpressShareButtonEnabled = true,
|
||||
)
|
||||
is ExchangeUM -> ExchangeStatusBottomSheetContent(
|
||||
state = state,
|
||||
extraContent = extraContent,
|
||||
isExpressShareButtonEnabled = true,
|
||||
)
|
||||
}
|
||||
BottomFade(
|
||||
backgroundColor = TangemTheme.colors.background.tertiary,
|
||||
modifier = Modifier.align(Alignment.BottomCenter),
|
||||
)
|
||||
ExpressShareContent(state = content.value)
|
||||
}
|
||||
} else {
|
||||
when (val state = content.value) {
|
||||
is ExpressTransactionStateUM.OnrampUM -> OnrampStatusBottomSheetContent(
|
||||
state = state,
|
||||
isExpressShareButtonEnabled = false,
|
||||
)
|
||||
is ExchangeUM -> ExchangeStatusBottomSheetContent(
|
||||
state = state,
|
||||
extraContent = extraContent,
|
||||
isExpressShareButtonEnabled = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -43,6 +76,7 @@ private fun PreviewExpressStatusBottomSheet(
|
|||
onDismissRequest = {},
|
||||
content = param,
|
||||
),
|
||||
isExpressShareButtonEnabled = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ class ExpressStatusBottomSheetStateProvider : PreviewParameterProvider<ExpressSt
|
|||
get() = sequenceOf(
|
||||
ExpressStatusBottomSheetConfig(
|
||||
ExchangeUM(
|
||||
fromUserWalletId = UserWalletId("AnyUserWalletID"),
|
||||
fromUserWalletId = UserWalletId("0123456789ABCDEF"),
|
||||
info = ExpressTransactionStateInfoUM(
|
||||
title = TextReference.Str("Transaction Status"),
|
||||
status = ExpressStatusUM(
|
||||
|
|
@ -49,13 +49,17 @@ class ExpressStatusBottomSheetStateProvider : PreviewParameterProvider<ExpressSt
|
|||
onDisposeExpressStatus = {},
|
||||
iconState = ExpressTransactionStateIconUM.None,
|
||||
toAmount = TextReference.Str("0.1 BTC"),
|
||||
toAmountValue = "0.1".toBigDecimal(),
|
||||
toFiatAmount = TextReference.Str("$5000"),
|
||||
toAmountSymbol = "BTC",
|
||||
toCurrencyIcon = CurrencyIconState.Empty(),
|
||||
toAddress = "0x",
|
||||
fromAmount = TextReference.Str("5000 USDT"),
|
||||
fromAmountValue = "5000".toBigDecimal(),
|
||||
fromFiatAmount = TextReference.Str("$5000"),
|
||||
fromAmountSymbol = "USDT",
|
||||
fromCurrencyIcon = CurrencyIconState.Empty(),
|
||||
fromAddress = "0x",
|
||||
),
|
||||
provider = SwapProvider(
|
||||
providerId = "1",
|
||||
|
|
@ -67,14 +71,20 @@ class ExpressStatusBottomSheetStateProvider : PreviewParameterProvider<ExpressSt
|
|||
privacyPolicy = null,
|
||||
slippage = BigDecimal.ZERO,
|
||||
),
|
||||
activeStatus = null,
|
||||
activeStatus = ExchangeStatus.Failed,
|
||||
statuses = persistentListOf(
|
||||
ExchangeStatusState(
|
||||
status = ExchangeStatus.Exchanging,
|
||||
text = stringReference("Exchanging"),
|
||||
isActive = true,
|
||||
isActive = false,
|
||||
isDone = false,
|
||||
),
|
||||
ExchangeStatusState(
|
||||
status = ExchangeStatus.Failed,
|
||||
text = stringReference("Failed"),
|
||||
isActive = true,
|
||||
isDone = true,
|
||||
),
|
||||
),
|
||||
notification = ExchangeStatusNotification.LongTimeExchange(
|
||||
onGoToProviderClick = {},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,409 @@
|
|||
package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawWithCache
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.PathEffect
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.graphics.asAndroidBitmap
|
||||
import androidx.compose.ui.graphics.layer.GraphicsLayer
|
||||
import androidx.compose.ui.graphics.rememberGraphicsLayer
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalInspectionMode
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.tooling.preview.Devices
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.FileProvider
|
||||
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM
|
||||
import com.tangem.core.ui.components.SecondaryButtonIconStart
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.ds.image.TangemIcon
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
import com.tangem.core.ui.res.TangemThemeRedesign
|
||||
import com.tangem.core.ui.utils.toPx
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExchangeUM
|
||||
import com.tangem.features.tokendetails.impl.R
|
||||
import com.tangem.utils.extensions.stripZeroPlainString
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Composable
|
||||
internal fun BoxScope.ExpressShareContent(state: ExpressTransactionStateUM) {
|
||||
val context = LocalContext.current
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
var graphicsLayer = rememberGraphicsLayer()
|
||||
|
||||
val shareText = makeExpressShareContent(state) ?: return
|
||||
|
||||
TangemThemeRedesign {
|
||||
SecondaryButtonIconStart(
|
||||
text = stringResourceSafe(R.string.common_share),
|
||||
iconResId = R.drawable.ic_share_24,
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
if (graphicsLayer.size.width > 0 && graphicsLayer.size.height > 0) {
|
||||
val uri = graphicsLayer.saveAsShareableFile(context)
|
||||
|
||||
val shareIntent = if (uri != null) {
|
||||
Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // Temporary external read grant
|
||||
putExtra(Intent.EXTRA_TEXT, shareText)
|
||||
putExtra(Intent.EXTRA_STREAM, uri)
|
||||
clipData = ClipData.newRawUri(null, uri)
|
||||
|
||||
type = "image/png"
|
||||
}
|
||||
} else {
|
||||
Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
putExtra(Intent.EXTRA_TEXT, shareText)
|
||||
type = "text/plain"
|
||||
}
|
||||
}
|
||||
context.startActivity(Intent.createChooser(shareIntent, null))
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
.align(Alignment.BottomCenter),
|
||||
)
|
||||
ExpressShareImageContent(
|
||||
state = state,
|
||||
onGraphicsLayer = { layer -> graphicsLayer = layer },
|
||||
modifier = Modifier.size(0.dp), // size 0 so that no space is used in the UI
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ExpressShareImageContent(
|
||||
state: ExpressTransactionStateUM,
|
||||
onGraphicsLayer: (GraphicsLayer) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(modifier = modifier.drawForShare(onGraphicsLayer)) {
|
||||
Box(
|
||||
// override the parent size with desired size of the recording
|
||||
modifier = Modifier
|
||||
.wrapContentHeight(unbounded = true, align = Alignment.Top)
|
||||
.wrapContentWidth(unbounded = true, align = Alignment.Start)
|
||||
.requiredSize(525.dp, 580.dp),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.matchParentSize()
|
||||
.background(TangemColorPalette.Black),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(
|
||||
start = 40.dp,
|
||||
end = 40.dp,
|
||||
top = 40.dp,
|
||||
bottom = 20.dp,
|
||||
),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(R.drawable.img_tangem_logo_90_24),
|
||||
contentDescription = null,
|
||||
tint = TangemColorPalette.White,
|
||||
)
|
||||
SpacerH(36.dp)
|
||||
|
||||
ExpressShareImageAmount(
|
||||
prefix = stringResourceSafe(R.string.common_send),
|
||||
amountValue = state.info.fromAmountValue,
|
||||
currencyIconState = state.info.fromCurrencyIcon,
|
||||
currencySymbol = state.info.fromAmountSymbol,
|
||||
)
|
||||
ExpressShareImageAddress(
|
||||
prefix = stringResourceSafe(R.string.common_from),
|
||||
address = state.info.fromAddress,
|
||||
)
|
||||
|
||||
SpacerH(24.dp)
|
||||
ExpressShareImageSeparator()
|
||||
SpacerH(24.dp)
|
||||
|
||||
ExpressShareImageAmount(
|
||||
prefix = stringResourceSafe(R.string.common_receive),
|
||||
amountValue = state.info.toAmountValue,
|
||||
currencyIconState = state.info.toCurrencyIcon,
|
||||
currencySymbol = state.info.toAmountSymbol,
|
||||
)
|
||||
ExpressShareImageAddress(
|
||||
prefix = stringResourceSafe(R.string.common_to),
|
||||
address = state.info.toAddress,
|
||||
)
|
||||
SpacerH(24.dp)
|
||||
|
||||
ExpressShareImageProvider(state)
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.express_transaction_id, state.info.txExternalId.orEmpty()),
|
||||
style = TangemTheme.typography2.bodySemibold16,
|
||||
color = TangemTheme.colors3.text.staticDark.secondary,
|
||||
)
|
||||
}
|
||||
Image(
|
||||
painter = painterResource(R.drawable.img_share_express_background),
|
||||
contentDescription = null,
|
||||
modifier = Modifier,
|
||||
contentScale = ContentScale.Crop,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ExpressShareImageAmount(
|
||||
prefix: String,
|
||||
amountValue: BigDecimal,
|
||||
currencyIconState: CurrencyIconState,
|
||||
currencySymbol: String,
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
Text(
|
||||
text = prefix,
|
||||
color = TangemTheme.colors3.text.staticDark.primary,
|
||||
style = TangemTheme.typography2.bodySemibold16,
|
||||
)
|
||||
Text(
|
||||
text = amountValue.stripZeroPlainString(),
|
||||
color = TangemTheme.colors3.text.staticDark.primary,
|
||||
style = TangemTheme.typography2.bodySemibold16,
|
||||
)
|
||||
CurrencyIcon(
|
||||
state = currencyIconState,
|
||||
iconSize = 20.dp,
|
||||
shouldDisplayNetwork = false,
|
||||
)
|
||||
Text(
|
||||
text = currencySymbol,
|
||||
color = TangemTheme.colors3.text.staticDark.primary,
|
||||
style = TangemTheme.typography2.bodySemibold16,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ExpressShareImageAddress(prefix: String, address: String?) {
|
||||
if (address != null) {
|
||||
Text(
|
||||
text = "$prefix:",
|
||||
color = TangemTheme.colors3.text.staticDark.secondary,
|
||||
)
|
||||
Text(
|
||||
text = address,
|
||||
color = TangemTheme.colors3.text.staticDark.secondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ExpressShareImageSeparator() {
|
||||
val width = 1.dp
|
||||
val height = 39.dp
|
||||
val dashOnInterval = (width * 2).toPx()
|
||||
val dashOffInterval = (width * 2).toPx()
|
||||
|
||||
val pathEffect = PathEffect.dashPathEffect(
|
||||
intervals = floatArrayOf(dashOnInterval, dashOffInterval),
|
||||
)
|
||||
Canvas(modifier = Modifier.size(width = width, height = height)) {
|
||||
drawLine(
|
||||
color = TangemColorPalette.Dark2,
|
||||
start = Offset(0f, 0f),
|
||||
end = Offset(size.width, size.height),
|
||||
strokeWidth = width.toPx(),
|
||||
cap = StrokeCap.Round,
|
||||
pathEffect = pathEffect,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ExpressShareImageProvider(state: ExpressTransactionStateUM) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.express_by_provider),
|
||||
style = TangemTheme.typography2.bodySemibold16,
|
||||
color = TangemTheme.colors3.text.staticDark.primary,
|
||||
)
|
||||
val providerImageUrl = when (state) {
|
||||
is ExchangeUM -> state.provider.imageLarge
|
||||
is ExpressTransactionStateUM.OnrampUM -> state.providerImageUrl
|
||||
else -> null
|
||||
}
|
||||
val providerName = when (state) {
|
||||
is ExchangeUM -> state.provider.name
|
||||
is ExpressTransactionStateUM.OnrampUM -> state.providerName
|
||||
else -> null
|
||||
}
|
||||
val providerType = when (state) {
|
||||
is ExchangeUM -> state.provider.type.providerName
|
||||
is ExpressTransactionStateUM.OnrampUM -> state.providerType
|
||||
else -> null
|
||||
}
|
||||
if (providerImageUrl != null) {
|
||||
TangemIcon(
|
||||
tangemIconUM = TangemIconUM.Url(providerImageUrl, fallbackRes = R.drawable.ic_empty_64),
|
||||
modifier = Modifier.size(20.dp),
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = providerName.orEmpty(),
|
||||
style = TangemTheme.typography2.bodySemibold16,
|
||||
color = TangemTheme.colors3.text.staticDark.primary,
|
||||
)
|
||||
Text(
|
||||
text = providerType.orEmpty(),
|
||||
style = TangemTheme.typography2.bodySemibold16,
|
||||
color = TangemTheme.colors3.text.staticDark.secondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Modifier.drawForShare(onGraphicsLayer: (GraphicsLayer) -> Unit): Modifier {
|
||||
val isInspectionMode = LocalInspectionMode.current
|
||||
|
||||
return drawWithCache {
|
||||
// draw to graphics layer
|
||||
onGraphicsLayer(
|
||||
obtainGraphicsLayer().apply {
|
||||
record(
|
||||
size = IntSize(
|
||||
width = 525.dp.toPx().toInt(),
|
||||
height = 580.dp.toPx().toInt(),
|
||||
),
|
||||
) {
|
||||
drawContent()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
if (isInspectionMode) {
|
||||
// draw only for preview mode
|
||||
onDrawWithContent { drawContent() }
|
||||
} else {
|
||||
// leave blank to skip drawing on the screen
|
||||
onDrawWithContent { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private suspend fun GraphicsLayer.saveAsShareableFile(context: Context): Uri? {
|
||||
// convert to bitmap
|
||||
val bitmap = this.toImageBitmap().asAndroidBitmap()
|
||||
|
||||
// create file
|
||||
val cachePath = File(context.cacheDir, "images")
|
||||
cachePath.mkdir()
|
||||
|
||||
val file = File(cachePath, "shared_image.png")
|
||||
|
||||
// write bitmap to file as PNG
|
||||
file.outputStream().use { out ->
|
||||
bitmap.compress(/* format = */ Bitmap.CompressFormat.PNG, /* quality = */ 100, /* stream = */ out)
|
||||
out.flush()
|
||||
}
|
||||
|
||||
// Generate secure Content URI using the registered authority
|
||||
return FileProvider.getUriForFile(
|
||||
/* context = */ context,
|
||||
/* authority = */ "${context.packageName}.provider",
|
||||
/* file = */ file,
|
||||
)
|
||||
}
|
||||
|
||||
@ReadOnlyComposable
|
||||
@Composable
|
||||
internal fun makeExpressShareContent(state: ExpressTransactionStateUM): String? {
|
||||
val txExternalId = state.info.txExternalId
|
||||
val txId = if (txExternalId != null) {
|
||||
stringResourceSafe(R.string.express_transaction_id, txExternalId)
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
||||
val (providerName, providerType) = when (state) {
|
||||
is ExchangeUM -> state.provider.name to state.provider.type
|
||||
is ExpressTransactionStateUM.OnrampUM -> state.providerName to state.providerType
|
||||
else -> return null
|
||||
}
|
||||
val providerInfo = "${stringResourceSafe(R.string.express_by_provider)} $providerName $providerType"
|
||||
val fromAddress = if (state.info.fromAddress != null) {
|
||||
"${stringResourceSafe(R.string.common_from)}: ${state.info.fromAddress}"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
val text = """
|
||||
${stringResourceSafe(R.string.common_tangem)}
|
||||
|
||||
${stringResourceSafe(R.string.common_send)} ${state.info.fromAmount.resolveReference()}
|
||||
$fromAddress
|
||||
|
||||
${stringResourceSafe(R.string.common_receive)} ${state.info.toAmount.resolveReference()}
|
||||
${stringResourceSafe(R.string.common_to)}: ${state.info.toAddress}
|
||||
|
||||
$providerInfo
|
||||
$txId
|
||||
""".trimIndent()
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
@Preview(showBackground = true, widthDp = 360, device = Devices.PIXEL_7_PRO)
|
||||
@Preview(showBackground = true, widthDp = 360, device = Devices.PIXEL_7_PRO, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun ExpressShareImageContent_Preview(
|
||||
@PreviewParameter(ExpressStatusBottomSheetStateProvider::class) param: ExpressStatusBottomSheetConfig,
|
||||
) {
|
||||
TangemThemePreviewRedesign {
|
||||
ExpressShareImageContent(param.value, {}, modifier = Modifier)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
|
@ -10,10 +10,12 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.common.ui.expressStatus.ExpressEstimate
|
||||
import com.tangem.common.ui.expressStatus.ExpressHideButton
|
||||
import com.tangem.common.ui.expressStatus.ExpressProvider
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.components.SpacerH10
|
||||
import com.tangem.core.ui.components.SpacerH12
|
||||
import com.tangem.core.ui.components.SpacerH16
|
||||
|
|
@ -28,7 +30,11 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.component
|
|||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExchangeUM
|
||||
|
||||
@Composable
|
||||
internal fun ExchangeStatusBottomSheetContent(state: ExchangeUM, extraContent: (@Composable () -> Unit)? = null) {
|
||||
internal fun ExchangeStatusBottomSheetContent(
|
||||
state: ExchangeUM,
|
||||
isExpressShareButtonEnabled: Boolean,
|
||||
extraContent: (@Composable () -> Unit)? = null,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
|
|
@ -87,7 +93,11 @@ internal fun ExchangeStatusBottomSheetContent(state: ExchangeUM, extraContent: (
|
|||
isAutoDisposable = state.activeStatus?.isAutoDisposable == true,
|
||||
onClick = state.info.onDisposeExpressStatus,
|
||||
)
|
||||
SpacerH24()
|
||||
if (isExpressShareButtonEnabled) {
|
||||
SpacerH(80.dp)
|
||||
} else {
|
||||
SpacerH24()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 196 KiB |
|
|
@ -63,6 +63,7 @@ internal class SingleWalletOnrampTransactionConverter(
|
|||
timestampAgoFormatted = mapFormattedDate(value.timestamp),
|
||||
activeStatus = value.status.toActiveStatusText(currency.name),
|
||||
toAmount = stringReference(value.toAmount.format { crypto(currency) }),
|
||||
toAmountValue = value.toAmount,
|
||||
toFiatAmount = stringReference(
|
||||
status.fiatRate?.multiply(value.toAmount).format {
|
||||
fiat(
|
||||
|
|
@ -73,6 +74,7 @@ internal class SingleWalletOnrampTransactionConverter(
|
|||
),
|
||||
toAmountSymbol = currency.symbol,
|
||||
toCurrencyIcon = iconStateConverter.convert(currency),
|
||||
toAddress = status.networkAddress?.defaultAddress?.value.orEmpty(),
|
||||
fromAmount = stringReference(
|
||||
value.fromAmount.format {
|
||||
fiat(
|
||||
|
|
@ -81,12 +83,14 @@ internal class SingleWalletOnrampTransactionConverter(
|
|||
)
|
||||
},
|
||||
),
|
||||
fromAmountValue = value.fromAmount,
|
||||
fromFiatAmount = null,
|
||||
fromAmountSymbol = value.fromCurrency.code,
|
||||
fromCurrencyIcon = CurrencyIconState.FiatIcon(
|
||||
url = value.fromCurrency.image,
|
||||
fallbackResId = R.drawable.ic_currency_24,
|
||||
),
|
||||
fromAddress = null,
|
||||
iconState = value.status.toIconState(),
|
||||
onGoToProviderClick = { url ->
|
||||
analyticsEventHandler.send(TokenOnrampAnalyticsEvent.GoToProvider())
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue