Updated on 2026-08-14
This commit is contained in:
commit
b02ec06761
30 changed files with 222 additions and 92 deletions
|
|
@ -7,6 +7,7 @@ import com.tangem.domain.models.scan.CardDTO
|
|||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.userwallets.UserWalletIdBuilder
|
||||
import com.tangem.tap.common.extensions.stripZeroPlainString
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
class AdditionalFeedbackInfo {
|
||||
|
||||
|
|
@ -36,7 +37,7 @@ class AdditionalFeedbackInfo {
|
|||
var userWalletId: String = ""
|
||||
|
||||
// wallets
|
||||
val walletsInfo = mutableListOf<EmailWalletInfo>()
|
||||
val walletsInfo = CopyOnWriteArrayList<EmailWalletInfo>()
|
||||
var onSendErrorWalletInfo: EmailWalletInfo? = null
|
||||
private set
|
||||
var signedHashesCount: String = ""
|
||||
|
|
@ -74,9 +75,7 @@ class AdditionalFeedbackInfo {
|
|||
@Deprecated("Don't use it directly")
|
||||
fun setWalletsInfo(walletManagers: List<WalletManager>) {
|
||||
walletsInfo.clear()
|
||||
walletManagers.forEach {
|
||||
walletsInfo.add(createEmailWalletInfo(it))
|
||||
}
|
||||
walletsInfo.addAll(elements = walletManagers.map(::createEmailWalletInfo))
|
||||
}
|
||||
|
||||
fun updateOnSendError(
|
||||
|
|
|
|||
|
|
@ -188,8 +188,9 @@ private fun handleAction(action: Action, appState: () -> AppState?) {
|
|||
|
||||
walletManagersFacade
|
||||
.getAll(userWallet.walletId)
|
||||
.distinctUntilChanged()
|
||||
.onEach(infoHolder::setWalletsInfo)
|
||||
.launchIn(scope)
|
||||
.launchIn(mainScope)
|
||||
}
|
||||
}
|
||||
.flowOn(Dispatchers.IO)
|
||||
|
|
|
|||
|
|
@ -108,9 +108,9 @@ class UserTokensRepository(
|
|||
.fold(
|
||||
onSuccess = { response ->
|
||||
response.getOrThrow()
|
||||
.also { storageService.saveUserTokens(userWalletId, it) }
|
||||
.tokens
|
||||
.mapNotNull(Currency.Companion::fromTokenResponse)
|
||||
.also { storageService.saveUserTokens(userWalletId, it.toUserTokensResponse()) }
|
||||
.distinct()
|
||||
},
|
||||
onFailure = { handleGetUserTokensFailure(userWalletId = userWalletId, error = it) },
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ internal fun TokensListScreen(stateHolder: TokensListStateHolder, modifier: Modi
|
|||
val tokens = stateHolder.tokens.collectAsLazyPagingItems()
|
||||
|
||||
TokensListContent(
|
||||
isDifferentAddressesBlockVisible = stateHolder.isDifferentAddressesBlockVisible && !stateHolder.isLoading,
|
||||
isDifferentAddressesBlockVisible = stateHolder.isDifferentAddressesBlockVisible,
|
||||
tokens = tokens,
|
||||
scaffoldPadding = scaffoldPadding,
|
||||
bottomMarginDp = floatingButtonHeight,
|
||||
|
|
@ -125,8 +125,11 @@ private fun TokensListContent(
|
|||
state = state,
|
||||
contentPadding = PaddingValues(bottom = bottomMarginDp),
|
||||
) {
|
||||
if (isDifferentAddressesBlockVisible) {
|
||||
item { DifferentAddressesWarning() }
|
||||
item(
|
||||
key = "DifferentAddressesWarning$isDifferentAddressesBlockVisible",
|
||||
contentType = "DifferentAddressesWarning$isDifferentAddressesBlockVisible",
|
||||
) {
|
||||
if (isDifferentAddressesBlockVisible) DifferentAddressesWarning()
|
||||
}
|
||||
|
||||
tokens.itemKey(TokenItemState::composedId)
|
||||
|
|
|
|||
|
|
@ -111,8 +111,8 @@
|
|||
|
||||
<FrameLayout
|
||||
android:id="@+id/imv_activation_success"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:color/transparent"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/imv_card_background"
|
||||
|
|
@ -121,23 +121,23 @@
|
|||
app:layout_constraintTop_toBottomOf="@+id/imv_card_background">
|
||||
|
||||
<View
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:background="@drawable/shape_success_circle"
|
||||
android:backgroundTint="@color/background_primary" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/imv_success_middle"
|
||||
android:layout_width="55dp"
|
||||
android:layout_height="55dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:background="@android:color/transparent"
|
||||
android:src="@drawable/img_onboarding_success" />
|
||||
|
||||
<View
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:alpha="0.2"
|
||||
android:background="@drawable/shape_success_circle"
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ fun PrimaryButton(
|
|||
text: String,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
size: TangemButtonSize = TangemButtonSize.Default,
|
||||
showProgress: Boolean = false,
|
||||
enabled: Boolean = true,
|
||||
) {
|
||||
|
|
@ -88,6 +89,7 @@ fun PrimaryButton(
|
|||
colors = TangemButtonsDefaults.primaryButtonColors,
|
||||
enabled = enabled,
|
||||
showProgress = showProgress,
|
||||
size = size,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -100,6 +102,7 @@ fun PrimaryButtonIconEnd(
|
|||
@DrawableRes iconResId: Int,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
size: TangemButtonSize = TangemButtonSize.Default,
|
||||
showProgress: Boolean = false,
|
||||
enabled: Boolean = true,
|
||||
) {
|
||||
|
|
@ -111,6 +114,7 @@ fun PrimaryButtonIconEnd(
|
|||
colors = TangemButtonsDefaults.primaryButtonColors,
|
||||
enabled = enabled,
|
||||
showProgress = showProgress,
|
||||
size = size,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -144,6 +148,7 @@ fun SecondaryButton(
|
|||
text: String,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
size: TangemButtonSize = TangemButtonSize.Default,
|
||||
showProgress: Boolean = false,
|
||||
enabled: Boolean = true,
|
||||
) {
|
||||
|
|
@ -155,6 +160,7 @@ fun SecondaryButton(
|
|||
colors = TangemButtonsDefaults.secondaryButtonColors,
|
||||
enabled = enabled,
|
||||
showProgress = showProgress,
|
||||
size = size,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ enum class TangemButtonSize {
|
|||
Selector,
|
||||
Action,
|
||||
RoundedAction,
|
||||
WideAction,
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
@ -25,12 +26,15 @@ internal fun TangemButtonSize.toHeightDp(): Dp = when (this) {
|
|||
TangemButtonSize.Action,
|
||||
TangemButtonSize.RoundedAction,
|
||||
-> TangemTheme.dimens.size36
|
||||
TangemButtonSize.WideAction -> TangemTheme.dimens.size40
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
internal fun TangemButtonSize.toShape(): Shape = when (this) {
|
||||
TangemButtonSize.Default -> TangemTheme.shapes.roundedCornersMedium
|
||||
TangemButtonSize.Default,
|
||||
TangemButtonSize.WideAction,
|
||||
-> TangemTheme.shapes.roundedCornersMedium
|
||||
TangemButtonSize.Text -> TangemTheme.shapes.roundedCornersSmall
|
||||
TangemButtonSize.Selector -> TangemTheme.shapes.roundedCornersSmall
|
||||
TangemButtonSize.Action -> TangemTheme.shapes.roundedCornersMedium
|
||||
|
|
@ -40,7 +44,9 @@ internal fun TangemButtonSize.toShape(): Shape = when (this) {
|
|||
@Composable
|
||||
@ReadOnlyComposable
|
||||
internal fun TangemButtonSize.toIconPadding(): Dp = when (this) {
|
||||
TangemButtonSize.Default -> TangemTheme.dimens.spacing4
|
||||
TangemButtonSize.Default,
|
||||
TangemButtonSize.WideAction,
|
||||
-> TangemTheme.dimens.spacing4
|
||||
TangemButtonSize.Text -> TangemTheme.dimens.spacing8
|
||||
TangemButtonSize.Selector -> 0.dp
|
||||
TangemButtonSize.Action,
|
||||
|
|
@ -80,6 +86,12 @@ internal fun TangemButtonSize.toContentPadding(icon: TangemButtonIconPosition):
|
|||
start = horizontalPadding.first,
|
||||
end = horizontalPadding.second,
|
||||
)
|
||||
TangemButtonSize.WideAction -> PaddingValues(
|
||||
top = TangemTheme.dimens.spacing10,
|
||||
bottom = TangemTheme.dimens.spacing10,
|
||||
start = horizontalPadding.first,
|
||||
end = horizontalPadding.second,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -87,7 +99,9 @@ internal fun TangemButtonSize.toContentPadding(icon: TangemButtonIconPosition):
|
|||
@ReadOnlyComposable
|
||||
internal fun TangemButtonSize.toHorizontalContentPadding(icon: TangemButtonIconPosition): Pair<Dp, Dp> {
|
||||
return when (this) {
|
||||
TangemButtonSize.Default -> TangemTheme.dimens.spacing32 to TangemTheme.dimens.spacing32
|
||||
TangemButtonSize.Default,
|
||||
TangemButtonSize.WideAction,
|
||||
-> TangemTheme.dimens.spacing32 to TangemTheme.dimens.spacing32
|
||||
TangemButtonSize.Text -> when (icon) {
|
||||
is TangemButtonIconPosition.None -> TangemTheme.dimens.spacing16 to TangemTheme.dimens.spacing16
|
||||
is TangemButtonIconPosition.Start -> TangemTheme.dimens.spacing14 to TangemTheme.dimens.spacing16
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
|||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.*
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonSize
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
|
@ -178,6 +179,7 @@ private fun SingleSecondaryButton(config: NotificationButtonsState.SecondaryButt
|
|||
text = config.text.resolveReference(),
|
||||
onClick = config.onClick,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
size = TangemButtonSize.WideAction,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -189,12 +191,14 @@ private fun SinglePrimaryButton(config: NotificationButtonsState.PrimaryButtonCo
|
|||
iconResId = config.iconResId,
|
||||
onClick = config.onClick,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
size = TangemButtonSize.WideAction,
|
||||
)
|
||||
} else {
|
||||
PrimaryButton(
|
||||
text = config.text.resolveReference(),
|
||||
onClick = config.onClick,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
size = TangemButtonSize.WideAction,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -206,12 +210,14 @@ private fun PairButtons(config: NotificationButtonsState.PairButtonsConfig) {
|
|||
text = config.secondaryText.resolveReference(),
|
||||
onClick = config.onSecondaryClick,
|
||||
modifier = Modifier.weight(weight = 1f),
|
||||
size = TangemButtonSize.WideAction,
|
||||
)
|
||||
|
||||
PrimaryButton(
|
||||
text = config.primaryText.resolveReference(),
|
||||
onClick = config.onPrimaryClick,
|
||||
modifier = Modifier.weight(weight = 1f),
|
||||
size = TangemButtonSize.WideAction,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ data class TangemTypography internal constructor(
|
|||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
letterSpacing = TextUnit(value = 0.1f, type = TextUnitType.Sp),
|
||||
lineHeight = TextUnit(value = 16f, type = TextUnitType.Sp),
|
||||
lineHeight = TextUnit(value = 20f, type = TextUnitType.Sp),
|
||||
),
|
||||
val caption1: TextStyle = TextStyle(
|
||||
fontFamily = RobotoFamily,
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ class DefaultWalletManagersFacade(
|
|||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
addressType: AddressType,
|
||||
contractAddress: String?,
|
||||
): String {
|
||||
val blockchain = Blockchain.fromId(network.id.value)
|
||||
|
||||
|
|
@ -148,8 +149,12 @@ class DefaultWalletManagersFacade(
|
|||
"Unable to get a wallet manager for blockchain: $blockchain"
|
||||
}
|
||||
|
||||
val address = walletManager.wallet.addresses.find { it.type == addressType }?.value
|
||||
return walletManager.wallet.getExploreUrl(address)
|
||||
val address = walletManager
|
||||
.wallet
|
||||
.addresses
|
||||
.find { it.type == addressType }
|
||||
?.value ?: walletManager.wallet.address
|
||||
return blockchain.getExploreUrl(address, contractAddress)
|
||||
}
|
||||
|
||||
override suspend fun getTxHistoryState(userWalletId: UserWalletId, network: Network): TxHistoryState {
|
||||
|
|
|
|||
|
|
@ -62,10 +62,16 @@ interface WalletManagersFacade {
|
|||
* @param userWalletId The ID of the user's wallet.
|
||||
* @param network The network.
|
||||
* @param addressType Address type.
|
||||
* @param contractAddress Contract address if currency is Token.
|
||||
*
|
||||
* @return The network explorer URL, maybe empty if the wallet manager was not found.
|
||||
* */
|
||||
suspend fun getExploreUrl(userWalletId: UserWalletId, network: Network, addressType: AddressType): String
|
||||
suspend fun getExploreUrl(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
addressType: AddressType,
|
||||
contractAddress: String?,
|
||||
): String
|
||||
|
||||
/**
|
||||
* Returns transactions count
|
||||
|
|
|
|||
|
|
@ -75,13 +75,13 @@ data class CryptoCurrencyStatus(
|
|||
*/
|
||||
data class NoAccount(
|
||||
val amountToCreateAccount: BigDecimal,
|
||||
override val fiatAmount: BigDecimal?,
|
||||
override val priceChange: BigDecimal?,
|
||||
override val fiatRate: BigDecimal?,
|
||||
override val networkAddress: NetworkAddress?,
|
||||
) : Status(isError = false) {
|
||||
|
||||
override val amount: BigDecimal = BigDecimal.ZERO
|
||||
override val fiatAmount: BigDecimal = BigDecimal.ZERO
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ internal class CurrencyStatusOperations(
|
|||
private fun createNoAccountStatus(status: NetworkStatus.NoAccount): CryptoCurrencyStatus.NoAccount =
|
||||
CryptoCurrencyStatus.NoAccount(
|
||||
amountToCreateAccount = status.amountToCreateAccount,
|
||||
fiatAmount = if (quote == null) null else BigDecimal.ZERO,
|
||||
priceChange = quote?.priceChange,
|
||||
fiatRate = quote?.fiatRate,
|
||||
networkAddress = status.address,
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ internal object MockTokensStates {
|
|||
val tokenState7 = CryptoCurrencyStatus(
|
||||
currency = MockTokens.token7,
|
||||
value = CryptoCurrencyStatus.NoAccount(
|
||||
fiatAmount = BigDecimal.ZERO,
|
||||
priceChange = MockQuotes.quote7.priceChange,
|
||||
fiatRate = MockQuotes.quote7.fiatRate,
|
||||
amountToCreateAccount = MockNetworks.amountToCreateAccount,
|
||||
|
|
@ -70,6 +71,7 @@ internal object MockTokensStates {
|
|||
val tokenState8 = CryptoCurrencyStatus(
|
||||
currency = MockTokens.token8,
|
||||
value = CryptoCurrencyStatus.NoAccount(
|
||||
fiatAmount = BigDecimal.ZERO,
|
||||
priceChange = MockQuotes.quote8.priceChange,
|
||||
fiatRate = MockQuotes.quote8.fiatRate,
|
||||
amountToCreateAccount = MockNetworks.amountToCreateAccount,
|
||||
|
|
@ -80,6 +82,7 @@ internal object MockTokensStates {
|
|||
val tokenState9 = CryptoCurrencyStatus(
|
||||
currency = MockTokens.token9,
|
||||
value = CryptoCurrencyStatus.NoAccount(
|
||||
fiatAmount = BigDecimal.ZERO,
|
||||
priceChange = MockQuotes.quote9.priceChange,
|
||||
fiatRate = MockQuotes.quote9.fiatRate,
|
||||
amountToCreateAccount = MockNetworks.amountToCreateAccount,
|
||||
|
|
@ -90,6 +93,7 @@ internal object MockTokensStates {
|
|||
val tokenState10 = CryptoCurrencyStatus(
|
||||
currency = MockTokens.token10,
|
||||
value = CryptoCurrencyStatus.NoAccount(
|
||||
fiatAmount = BigDecimal.ZERO,
|
||||
priceChange = MockQuotes.quote10.priceChange,
|
||||
fiatRate = MockQuotes.quote10.fiatRate,
|
||||
amountToCreateAccount = MockNetworks.amountToCreateAccount,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.domain.wallets.usecase
|
||||
|
||||
import arrow.core.raise.catch
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.blockchain.common.address.AddressType
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
|
|
@ -12,11 +12,19 @@ class GetExploreUrlUseCase(private val walletsManagersFacade: WalletManagersFaca
|
|||
// FIXME: Handle error
|
||||
suspend operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
currency: CryptoCurrency,
|
||||
addressType: AddressType = AddressType.Default,
|
||||
): String {
|
||||
return catch({ walletsManagersFacade.getExploreUrl(userWalletId, network, addressType) }) {
|
||||
""
|
||||
}
|
||||
return catch(
|
||||
block = {
|
||||
walletsManagersFacade.getExploreUrl(
|
||||
userWalletId = userWalletId,
|
||||
network = currency.network,
|
||||
addressType = addressType,
|
||||
contractAddress = (currency as? CryptoCurrency.Token)?.contractAddress,
|
||||
)
|
||||
},
|
||||
catch = { "" },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -55,6 +55,7 @@ interface SwapInteractor {
|
|||
* @param fromToken [Currency] from which want to swap
|
||||
* @param toToken [Currency] that receive after swap
|
||||
* @param amountToSwap amount you want to swap
|
||||
* @param selectedFee selected fee to swap
|
||||
* @return
|
||||
*/
|
||||
@Throws(IllegalStateException::class)
|
||||
|
|
@ -63,6 +64,7 @@ interface SwapInteractor {
|
|||
fromToken: Currency,
|
||||
toToken: Currency,
|
||||
amountToSwap: String,
|
||||
selectedFee: FeeType = FeeType.NORMAL,
|
||||
): SwapState
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -167,6 +167,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
fromToken: Currency,
|
||||
toToken: Currency,
|
||||
amountToSwap: String,
|
||||
selectedFee: FeeType,
|
||||
): SwapState {
|
||||
syncWalletBalanceForTokens(networkId, listOf(fromToken, toToken))
|
||||
val amountDecimal = toBigDecimalOrNull(amountToSwap)
|
||||
|
|
@ -190,6 +191,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
fromToken = fromToken,
|
||||
toToken = toToken,
|
||||
amount = amount,
|
||||
selectedFee = selectedFee,
|
||||
)
|
||||
} else {
|
||||
loadQuoteData(
|
||||
|
|
@ -454,6 +456,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
fromToken: Currency,
|
||||
toToken: Currency,
|
||||
amount: SwapAmount,
|
||||
selectedFee: FeeType,
|
||||
): SwapState {
|
||||
repository.prepareSwapTransaction(
|
||||
networkId = networkId,
|
||||
|
|
@ -475,10 +478,14 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
derivationPath = derivationPath,
|
||||
)
|
||||
val txFeeState = proxyFeesToFeeState(networkId, feeData)
|
||||
val feeByPriority = when (selectedFee) {
|
||||
FeeType.NORMAL -> txFeeState.normalFee.feeValue
|
||||
FeeType.PRIORITY -> txFeeState.priorityFee.feeValue
|
||||
}
|
||||
val isBalanceIncludeFeeEnough =
|
||||
isBalanceEnough(networkId, fromToken, amount, txFeeState.priorityFee.feeValue)
|
||||
isBalanceEnough(networkId, fromToken, amount, feeByPriority)
|
||||
val isFeeEnough = checkFeeIsEnough(
|
||||
fee = txFeeState.normalFee.feeValue,
|
||||
fee = feeByPriority,
|
||||
spendAmount = amount,
|
||||
networkId = networkId,
|
||||
fromToken = fromToken,
|
||||
|
|
|
|||
|
|
@ -312,7 +312,7 @@ internal class StateBuilder(val actions: UiActions, val isBalanceHiddenProvider:
|
|||
} else {
|
||||
permissionState
|
||||
}
|
||||
return when (val fee = uiState.fee) {
|
||||
val updateState = when (val fee = uiState.fee) {
|
||||
is FeeState.Loaded -> {
|
||||
getUpdatedFeeStateForEnoughFee(uiState, fee, item, newSelectedItem, newPermissionState, isFeeEnough)
|
||||
}
|
||||
|
|
@ -321,6 +321,15 @@ internal class StateBuilder(val actions: UiActions, val isBalanceHiddenProvider:
|
|||
}
|
||||
else -> uiState
|
||||
}
|
||||
return if (isFeeEnough) {
|
||||
updateState.copy(
|
||||
warnings = uiState.warnings.filterNot { it is SwapWarning.InsufficientFunds },
|
||||
)
|
||||
} else {
|
||||
updateState.copy(
|
||||
warnings = uiState.warnings.plus(SwapWarning.InsufficientFunds),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
|
|
@ -348,6 +357,9 @@ internal class StateBuilder(val actions: UiActions, val isBalanceHiddenProvider:
|
|||
return uiState.copy(
|
||||
fee = newFeeState,
|
||||
permissionState = newPermissionState,
|
||||
swapButton = uiState.swapButton.copy(
|
||||
enabled = isFeeEnough,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -376,6 +388,9 @@ internal class StateBuilder(val actions: UiActions, val isBalanceHiddenProvider:
|
|||
return uiState.copy(
|
||||
fee = newFeeState,
|
||||
permissionState = newPermissionState,
|
||||
swapButton = uiState.swapButton.copy(
|
||||
enabled = isFeeEnough,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -205,6 +205,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
fromToken = fromToken,
|
||||
toToken = toToken,
|
||||
amountToSwap = amount,
|
||||
selectedFee = dataState.selectedFee?.feeType ?: FeeType.NORMAL,
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -90,23 +90,33 @@ internal class TokenDetailsLoadedBalanceConverter(
|
|||
return when (status) {
|
||||
is CryptoCurrencyStatus.Loading -> MarketPriceBlockState.Loading(currencySymbol)
|
||||
is CryptoCurrencyStatus.NoQuote -> MarketPriceBlockState.Error(currencySymbol)
|
||||
is CryptoCurrencyStatus.NoAccount -> {
|
||||
if (status.fiatRate == null) {
|
||||
MarketPriceBlockState.Error(currencySymbol)
|
||||
} else {
|
||||
status.toContentConfig(currencySymbol)
|
||||
}
|
||||
}
|
||||
is CryptoCurrencyStatus.Loaded,
|
||||
is CryptoCurrencyStatus.Custom,
|
||||
is CryptoCurrencyStatus.MissedDerivation,
|
||||
is CryptoCurrencyStatus.NoAccount,
|
||||
is CryptoCurrencyStatus.Unreachable,
|
||||
is CryptoCurrencyStatus.NoAmount,
|
||||
-> MarketPriceBlockState.Content(
|
||||
currencySymbol = currencySymbol,
|
||||
price = formatPrice(status, appCurrencyProvider()),
|
||||
priceChangeConfig = PriceChangeState.Content(
|
||||
valueInPercent = formatPriceChange(status),
|
||||
type = getPriceChangeType(status),
|
||||
),
|
||||
)
|
||||
-> status.toContentConfig(currencySymbol)
|
||||
}
|
||||
}
|
||||
|
||||
private fun CryptoCurrencyStatus.Status.toContentConfig(currencySymbol: String): MarketPriceBlockState.Content {
|
||||
return MarketPriceBlockState.Content(
|
||||
currencySymbol = currencySymbol,
|
||||
price = formatPrice(status = this, appCurrency = appCurrencyProvider()),
|
||||
priceChangeConfig = PriceChangeState.Content(
|
||||
valueInPercent = formatPriceChange(status = this),
|
||||
type = getPriceChangeType(status = this),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getPriceChangeType(status: CryptoCurrencyStatus.Status): PriceChangeType {
|
||||
val priceChange = status.priceChange ?: return PriceChangeType.DOWN
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import androidx.compose.runtime.setValue
|
|||
import androidx.lifecycle.*
|
||||
import androidx.paging.cachedIn
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.address.AddressType
|
||||
import com.tangem.common.Provider
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
|
|
@ -397,7 +398,7 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
router.openUrl(
|
||||
url = getExploreUrlUseCase(
|
||||
userWalletId = userWalletId,
|
||||
network = cryptoCurrency.network,
|
||||
currency = cryptoCurrency,
|
||||
addressType = AddressType.Default,
|
||||
),
|
||||
)
|
||||
|
|
@ -429,7 +430,7 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
router.openUrl(
|
||||
url = getExploreUrlUseCase(
|
||||
userWalletId = userWalletId,
|
||||
network = cryptoCurrency.network,
|
||||
currency = cryptoCurrency,
|
||||
addressType = AddressType.valueOf(addressModel.type.name),
|
||||
),
|
||||
)
|
||||
|
|
@ -438,12 +439,18 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onTransactionClick(txHash: String) {
|
||||
router.openUrl(
|
||||
url = getExplorerTransactionUrlUseCase(
|
||||
txHash = txHash,
|
||||
networkId = cryptoCurrency.network.id,
|
||||
),
|
||||
)
|
||||
val blockchain = Blockchain.fromId(cryptoCurrency.network.id.value)
|
||||
// TODO: Fix ton tx urls [REDACTED_TASK_KEY]
|
||||
if (blockchain == Blockchain.TON || blockchain == Blockchain.TONTestnet) {
|
||||
return
|
||||
} else {
|
||||
router.openUrl(
|
||||
url = getExplorerTransactionUrlUseCase(
|
||||
txHash = txHash,
|
||||
networkId = cryptoCurrency.network.id,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRefreshSwipe() {
|
||||
|
|
|
|||
|
|
@ -323,7 +323,7 @@ internal object WalletPreviewData {
|
|||
persistentListOf(
|
||||
WalletManageButton.Buy(enabled = true, onClick = {}),
|
||||
WalletManageButton.Send(enabled = true, onClick = {}),
|
||||
WalletManageButton.Receive(onClick = {}),
|
||||
WalletManageButton.Receive(enabled = true, onClick = {}),
|
||||
WalletManageButton.Sell(enabled = true, onClick = {}),
|
||||
WalletManageButton.Swap(enabled = true, onClick = {}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ import com.tangem.feature.wallet.impl.R
|
|||
@Immutable
|
||||
internal sealed class WalletManageButton(val config: ActionButtonConfig) {
|
||||
|
||||
/** Is click enabled */
|
||||
abstract val enabled: Boolean
|
||||
|
||||
/** Lambda be invoked when manage button is clicked */
|
||||
abstract val onClick: () -> Unit
|
||||
|
||||
|
|
@ -24,7 +27,7 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) {
|
|||
* @property enabled button click availability
|
||||
* @property onClick lambda be invoked when Buy button is clicked
|
||||
*/
|
||||
data class Buy(val enabled: Boolean, override val onClick: () -> Unit) : WalletManageButton(
|
||||
data class Buy(override val enabled: Boolean, override val onClick: () -> Unit) : WalletManageButton(
|
||||
config = ActionButtonConfig(
|
||||
text = TextReference.Res(id = R.string.common_buy),
|
||||
iconResId = R.drawable.ic_plus_24,
|
||||
|
|
@ -39,7 +42,7 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) {
|
|||
* @property enabled button click availability
|
||||
* @property onClick lambda be invoked when Send button is clicked
|
||||
*/
|
||||
data class Send(val enabled: Boolean, override val onClick: () -> Unit) : WalletManageButton(
|
||||
data class Send(override val enabled: Boolean, override val onClick: () -> Unit) : WalletManageButton(
|
||||
config = ActionButtonConfig(
|
||||
text = TextReference.Res(id = R.string.common_send),
|
||||
iconResId = R.drawable.ic_arrow_up_24,
|
||||
|
|
@ -53,12 +56,12 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) {
|
|||
*
|
||||
* @property onClick lambda be invoked when Receive button is clicked
|
||||
*/
|
||||
data class Receive(override val onClick: () -> Unit) : WalletManageButton(
|
||||
data class Receive(override val enabled: Boolean, override val onClick: () -> Unit) : WalletManageButton(
|
||||
config = ActionButtonConfig(
|
||||
text = TextReference.Res(id = R.string.common_receive),
|
||||
iconResId = R.drawable.ic_arrow_down_24,
|
||||
onClick = onClick,
|
||||
enabled = true,
|
||||
enabled = enabled,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -68,7 +71,7 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) {
|
|||
* @property enabled button click availability
|
||||
* @property onClick lambda be invoked when Sell button is clicked
|
||||
*/
|
||||
data class Sell(val enabled: Boolean, override val onClick: () -> Unit) : WalletManageButton(
|
||||
data class Sell(override val enabled: Boolean, override val onClick: () -> Unit) : WalletManageButton(
|
||||
config = ActionButtonConfig(
|
||||
text = TextReference.Res(id = R.string.common_sell),
|
||||
iconResId = R.drawable.ic_currency_24,
|
||||
|
|
@ -83,7 +86,7 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) {
|
|||
* @property enabled button click availability
|
||||
* @property onClick lambda be invoked when Swap button is clicked
|
||||
*/
|
||||
data class Swap(val enabled: Boolean, override val onClick: () -> Unit) : WalletManageButton(
|
||||
data class Swap(override val enabled: Boolean, override val onClick: () -> Unit) : WalletManageButton(
|
||||
config = ActionButtonConfig(
|
||||
text = TextReference.Res(id = R.string.common_swap),
|
||||
iconResId = R.drawable.ic_exchange_vertical_24,
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ internal class WalletCryptoCurrencyActionsConverter(
|
|||
}
|
||||
is TokenActionsState.ActionState.Receive -> {
|
||||
WalletManageButton.Receive(
|
||||
enabled = action.enabled,
|
||||
onClick = { clickIntents.onReceiveClick(cryptoCurrencyStatus) },
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ internal class WalletLockedConverter(
|
|||
is WalletManageButton.Sell -> button.copy(enabled = false)
|
||||
is WalletManageButton.Send -> button.copy(enabled = false)
|
||||
is WalletManageButton.Swap -> button.copy(enabled = false)
|
||||
is WalletManageButton.Receive -> button
|
||||
is WalletManageButton.Receive -> button.copy(enabled = false)
|
||||
}
|
||||
}
|
||||
.toPersistentList()
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ internal class WalletSingleCurrencyLoadedBalanceConverter(
|
|||
|
||||
state.copy(
|
||||
walletsListConfig = getUpdatedSelectedWallet(status = status.value, state = state),
|
||||
marketPriceBlockState = getMarketPriceState(status = status.value, currencyName = currencyName),
|
||||
marketPriceBlockState = getMarketPriceState(status = status.value, currencySymbol = currencyName),
|
||||
)
|
||||
}
|
||||
is WalletMultiCurrencyState.Content,
|
||||
|
|
@ -54,32 +54,27 @@ internal class WalletSingleCurrencyLoadedBalanceConverter(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getMarketPriceState(status: CryptoCurrencyStatus.Status, currencyName: String): MarketPriceBlockState {
|
||||
private fun getMarketPriceState(
|
||||
status: CryptoCurrencyStatus.Status,
|
||||
currencySymbol: String,
|
||||
): MarketPriceBlockState {
|
||||
return when (status) {
|
||||
is CryptoCurrencyStatus.NoQuote -> MarketPriceBlockState.Error(currencyName)
|
||||
is CryptoCurrencyStatus.Loaded,
|
||||
is CryptoCurrencyStatus.NoAmount,
|
||||
-> MarketPriceBlockState.Content(
|
||||
currencySymbol = currencyName,
|
||||
price = formatPrice(status, appCurrencyProvider()),
|
||||
priceChangeConfig = PriceChangeState.Content(
|
||||
valueInPercent = formatPriceChange(status),
|
||||
type = getPriceChangeType(status),
|
||||
),
|
||||
)
|
||||
is CryptoCurrencyStatus.NoAccount -> MarketPriceBlockState.Content(
|
||||
currencySymbol = currencyName,
|
||||
price = formatPrice(status, appCurrencyProvider()),
|
||||
priceChangeConfig = PriceChangeState.Content(
|
||||
valueInPercent = formatPriceChange(status),
|
||||
type = getPriceChangeType(status),
|
||||
),
|
||||
)
|
||||
is CryptoCurrencyStatus.Loading -> MarketPriceBlockState.Loading(currencyName)
|
||||
-> status.toContentConfig(currencySymbol)
|
||||
is CryptoCurrencyStatus.NoAccount -> {
|
||||
if (status.fiatRate == null) {
|
||||
MarketPriceBlockState.Error(currencySymbol)
|
||||
} else {
|
||||
status.toContentConfig(currencySymbol)
|
||||
}
|
||||
}
|
||||
is CryptoCurrencyStatus.Loading -> MarketPriceBlockState.Loading(currencySymbol)
|
||||
is CryptoCurrencyStatus.Custom,
|
||||
is CryptoCurrencyStatus.MissedDerivation,
|
||||
is CryptoCurrencyStatus.Unreachable,
|
||||
-> MarketPriceBlockState.Error(currencyName)
|
||||
is CryptoCurrencyStatus.NoQuote,
|
||||
-> MarketPriceBlockState.Error(currencySymbol)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -138,6 +133,17 @@ internal class WalletSingleCurrencyLoadedBalanceConverter(
|
|||
)
|
||||
}
|
||||
|
||||
private fun CryptoCurrencyStatus.Status.toContentConfig(currencySymbol: String): MarketPriceBlockState.Content {
|
||||
return MarketPriceBlockState.Content(
|
||||
currencySymbol = currencySymbol,
|
||||
price = formatPrice(status = this, appCurrency = appCurrencyProvider()),
|
||||
priceChangeConfig = PriceChangeState.Content(
|
||||
valueInPercent = formatPriceChange(status = this),
|
||||
type = getPriceChangeType(status = this),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getPriceChangeType(status: CryptoCurrencyStatus.Status): PriceChangeType {
|
||||
val priceChange = status.priceChange ?: return PriceChangeType.DOWN
|
||||
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ internal class WalletSkeletonStateConverter(
|
|||
return persistentListOf(
|
||||
WalletManageButton.Buy(enabled = false, onClick = {}),
|
||||
WalletManageButton.Send(enabled = false, onClick = {}),
|
||||
WalletManageButton.Receive(onClick = {}),
|
||||
WalletManageButton.Receive(enabled = false, onClick = {}),
|
||||
WalletManageButton.Sell(enabled = false, onClick = {}),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.ui
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
|
|
@ -57,7 +59,7 @@ internal fun WalletEventEffect(
|
|||
.addOnCompleteListener {
|
||||
handleOnCompleteRequestTask(
|
||||
reviewManager = reviewManager,
|
||||
activity = context as? Activity ?: return@addOnCompleteListener,
|
||||
activity = context.findActivity(),
|
||||
task = it,
|
||||
onDismissClick = value.onDismissClick,
|
||||
)
|
||||
|
|
@ -69,6 +71,15 @@ internal fun WalletEventEffect(
|
|||
)
|
||||
}
|
||||
|
||||
private fun Context.findActivity(): Activity {
|
||||
var context = this
|
||||
while (context is ContextWrapper) {
|
||||
if (context is Activity) return context
|
||||
context = context.baseContext
|
||||
}
|
||||
error("Permissions should be called in the context of an Activity")
|
||||
}
|
||||
|
||||
private fun handleOnCompleteRequestTask(
|
||||
reviewManager: ReviewManager,
|
||||
activity: Activity,
|
||||
|
|
|
|||
|
|
@ -646,10 +646,23 @@ internal class WalletViewModel @Inject constructor(
|
|||
event = TokenScreenAnalyticsEvent.ButtonSend(cryptoCurrencyStatus.currency.symbol),
|
||||
)
|
||||
|
||||
val currency = cryptoCurrencyStatus.currency as? CryptoCurrency.Token ?: return
|
||||
viewModelScope.launch(dispatchers.io) {
|
||||
val userWallet = getWallet(index = state.walletsListConfig.selectedWalletIndex)
|
||||
val userWallet = getWallet(index = state.walletsListConfig.selectedWalletIndex)
|
||||
when (cryptoCurrencyStatus.currency) {
|
||||
is CryptoCurrency.Coin -> {
|
||||
uiState = stateFactory.getStateWithClosedBottomSheet()
|
||||
reduxStateHolder.dispatch(
|
||||
action = TradeCryptoAction.New.SendCoin(
|
||||
userWallet = userWallet,
|
||||
coinStatus = cryptoCurrencyStatus,
|
||||
),
|
||||
)
|
||||
}
|
||||
is CryptoCurrency.Token -> sendToken(userWallet, cryptoCurrencyStatus)
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendToken(userWallet: UserWallet, cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
viewModelScope.launch(dispatchers.io) {
|
||||
getNetworkCoinStatusUseCase(
|
||||
userWalletId = userWallet.walletId,
|
||||
networkId = cryptoCurrencyStatus.currency.network.id,
|
||||
|
|
@ -659,10 +672,11 @@ internal class WalletViewModel @Inject constructor(
|
|||
.take(count = 1)
|
||||
.collectLatest {
|
||||
it.onRight { coinStatus ->
|
||||
uiState = stateFactory.getStateWithClosedBottomSheet()
|
||||
reduxStateHolder.dispatch(
|
||||
action = TradeCryptoAction.New.SendToken(
|
||||
userWallet = userWallet,
|
||||
tokenCurrency = currency,
|
||||
tokenCurrency = requireNotNull(cryptoCurrencyStatus.currency as? CryptoCurrency.Token),
|
||||
tokenFiatRate = cryptoCurrencyStatus.value.fiatRate,
|
||||
coinFiatRate = coinStatus.value.fiatRate,
|
||||
),
|
||||
|
|
@ -805,18 +819,18 @@ internal class WalletViewModel @Inject constructor(
|
|||
|
||||
private fun openExplorer() {
|
||||
val state = uiState as? WalletState.ContentState ?: return
|
||||
val currencyNetwork = singleWalletCryptoCurrencyStatus?.currency?.network ?: return
|
||||
val currency = singleWalletCryptoCurrencyStatus?.currency ?: return
|
||||
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
val userWalletId = getWallet(state.walletsListConfig.selectedWalletIndex).walletId
|
||||
|
||||
val addresses = walletManagersFacade.getAddress(userWalletId = userWalletId, network = currencyNetwork)
|
||||
val addresses = walletManagersFacade.getAddress(userWalletId = userWalletId, network = currency.network)
|
||||
|
||||
if (addresses.size == 1) {
|
||||
router.openUrl(
|
||||
url = getExploreUrlUseCase(
|
||||
userWalletId = userWalletId,
|
||||
network = currencyNetwork,
|
||||
currency = currency,
|
||||
addressType = AddressType.Default,
|
||||
),
|
||||
)
|
||||
|
|
@ -834,7 +848,7 @@ internal class WalletViewModel @Inject constructor(
|
|||
onClick = {
|
||||
onAddressTypeSelected(
|
||||
userWalletId = userWalletId,
|
||||
currencyNetwork = currencyNetwork,
|
||||
currency = currency,
|
||||
addressModel = it,
|
||||
)
|
||||
},
|
||||
|
|
@ -846,14 +860,14 @@ internal class WalletViewModel @Inject constructor(
|
|||
|
||||
private fun onAddressTypeSelected(
|
||||
userWalletId: UserWalletId,
|
||||
currencyNetwork: Network,
|
||||
currency: CryptoCurrency,
|
||||
addressModel: AddressModel,
|
||||
) {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
router.openUrl(
|
||||
url = getExploreUrlUseCase(
|
||||
userWalletId = userWalletId,
|
||||
network = currencyNetwork,
|
||||
currency = currency,
|
||||
addressType = AddressType.valueOf(addressModel.type.name),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ spr-client = "3.6.2"
|
|||
# endregion Other libraries
|
||||
|
||||
# region Tangem
|
||||
tangemBlockchainSdk = "release-app_5.0-365"
|
||||
tangemBlockchainSdk = "release-app_5.0-368"
|
||||
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
|
||||
tangemCardSdk = "release-app_5.0-308"
|
||||
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue